案例速成GO操作redis,个人笔记
更多个人笔记:(仅供参考,非盈利)
gitee: https://gitee.com/harryhack/it_note
github: https://github.com/ZHLOVEYY/IT_note
安装redis客户端:go get github.com/redis/go-redis/v9
注意go mod tidy的时候不要import成了"github.com/go-redis/redis" 需要检查一下
基础操作,连接redis和CRUD
package mainimport ("context""fmt""log""time""github.com/redis/go-redis/v9"
)func main() {client := redis.NewClient(&redis.Options{Addr: "localhost:6379",Password: "", // no password setDB: 0, // use default //password和db如果都是默认值可以不设置,这里展示一下})ctx := context.Background() //创建上下文//测试连接_,err := client.Ping(ctx).Result()if err != nil {log.Fatal("连接失败",err)}fmt.Println("连接成功")///设置键值对err = client.Set(ctx,"user1","Alice",0).Err() //0表示永不过期if err!=nil {log.Fatal("设置键值对失败",err)}fmt.Println("设置了user1=Alice")//获取键值对name,err := client.Get(ctx,"user1").Result()if err!=nil {log.Fatal("获取键值对失败",err)}fmt.Println("user1的值是",name)//设置过期时间(10秒)err = client.SetEx(ctx,"session1","123456",10*time.Second).Err()if err!=nil {log.Fatal("设置过期时间失败",err)}fmt.Println("设置了session1=123456,过期时间为10秒")//等待2秒fmt.Println("等待2秒...")time.Sleep(2*time.Second)// 获取 session 的剩余时间ttl, err := client.TTL(ctx, "session1").Result()if err != nil {log.Fatal("获取过期时间失败", err)}fmt.Printf("session1 剩余时间: %v\n", ttl)//删除键_,err = client.Del(ctx,"user1").Result()if err!=nil {log.Fatal("删除键失败",err)}fmt.Println("删除了user1")//再次获取键值对name, err = client.Get(ctx, "user1").Result()if err == redis.Nil { //如果不存在会返回redis.Nilfmt.Println("user1 不存在") } else if err != nil {log.Fatal("获取键值对失败", err)} else {fmt.Printf("user1的值是: %s\n", name)}
}
- 注意需要传入ctx上下文
- 注意最后有.Result()或者.Err()的特点
- 主题语法和Redis其实是比较像的
操作复杂数据结构(Hash 和 List)
package mainimport ("context""fmt""log""github.com/redis/go-redis/v9"
)func main() {client := redis.NewClient(&redis.Options{Addr: "localhost:6379",})ctx := context.Background() //创建上下文//操作Hash(存储用户对象)user := map[string]interface{}{ //interface{} 表示任意类型,就很方便"name": "Bob","age": 30,}err := client.HSet(ctx, "user2", user).Err()if err != nil {log.Fatal("Hset失败", err)}fmt.Println("存储用户user2")//获取Hashname, err := client.HGet(ctx, "user2", "name").Result()if err != nil {log.Fatal("Hget失败", err)}fmt.Printf("获取用户user2的name:%s\n", name)//操作List(消息队列)queue := "message_queue"err = client.RPush(ctx, queue, "msg1", "msg2", "msg3").Err() //Rpush从列表右端(尾部)插入元素,和Lpush相反if err != nil {log.Fatal("RPush失败", err)}fmt.Println("消息入队到message_queue")//弹出msg, err := client.LPop(ctx, queue).Result()if err != nil {log.Fatal("LPop失败", err)}fmt.Printf("从message_queue弹出消息:%s\n", msg)
}
(一些设置要是输出怪可能是和已经存在的变量名有关,改一下就可以了)
- Hash:HSet 和 HGet 适合存储结构化数据(如用户信息)。
- List:RPush 和 LPop 实现 FIFO 队列,常用作任务队列。
- 实际项目中,Hash 常用于缓存对象,List 用于异步任务处理
发布/订阅(Pub/Sub)
package mainimport ("context""fmt""log""time""github.com/redis/go-redis/v9"
)func main() {client := redis.NewClient(&redis.Options{Addr: "localhost:6379",})ctx := context.Background() //创建上下文//订阅者go func(){ //通过协程持续监听pubsub := client.Subscribe(ctx,"channel1")defer pubsub.Close()for msg := range pubsub.Channel(){ //调用channel方法,获取消息fmt.Printf("收到消息:%s(频道:%s)\n",msg.Payload,msg.Channel)}}()//发布者time.Sleep(1*time.Second) //确保订阅者启动err := client.Publish(ctx,"channel1","hello,Redis").Err()if err != nil{log.Fatal("发布失败",err)}fmt.Println("发布消息成功")//保持运行time.Sleep(2 * time.Second)
}
分布式锁
通过lua脚本等简单了解分布式锁的概念
package mainimport ("context""fmt""log""time""github.com/redis/go-redis/v9"
)func acquireLock(client *redis.Client, ctx context.Context, lockKey string, value string, ttl time.Duration) bool {// 使用 SETNX 命令尝试获取锁result, err := client.SetNX(ctx, lockKey, value, ttl).Result()if err != nil {log.Println("Failed to acquire lock:", err)return false}return result//和SetNX返回值有关,redis中当键不存在时,设置成功,返回 1//SetNX:set if not exist
}func releaseLock(client *redis.Client, ctx context.Context, lockKey, value string) bool {// Lua 脚本确保原子性释放script := `if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("DEL", KEYS[1])elsereturn 0end ` // KEYS[1] 是锁的键,ARGV[1] 是锁的值,检查锁的值是否匹配,如果匹配则删除锁result, err := client.Eval(ctx, script, []string{lockKey}, value).Int() //[]string{lockKey} 是 Lua 脚本的参数,value 是锁的值if err != nil {return false}return result == 1
}func main() {client := redis.NewClient(&redis.Options{Addr: "localhost:6379",})ctx := context.Background()lockKey := "mylock"value := "unique_123"ttl := 20 * time.Secondif acquireLock(client, ctx, lockKey, value, ttl) {fmt.Println("获取锁成功")// 模拟业务操作time.Sleep(2 * time.Second)//释放锁if releaseLock(client, ctx, lockKey, value) {fmt.Println("释放锁成功")} else {fmt.Println("释放锁失败")}}else {fmt.Println("获取锁失败")}
}
- 关键在于做到只能释放自己的锁,防止竞争
- 可以用于库存扣减、分布式任务调度
连接池和性能优化
简单了解
包括通过pipeline进行批量输入
package mainimport ("context""fmt""log""sync""time""github.com/redis/go-redis/v9"
)func main() {client := redis.NewClient(&redis.Options{Addr: "localhost:6379",PoolSize: 10,MinIdleConns: 2,MaxRetries: 3,DialTimeout: time.Second * 5, ReadTimeout: time.Second * 3, WriteTimeout: time.Second * 3, })ctx := context.Background()// 批量写入(使用 Pipeline 提升性能)pipe := client.Pipeline()for i := 0; i < 100; i++ { //将命令加入到管道中,但不立即执行pipe.Set(ctx, fmt.Sprintf("key:%d", i), i, time.Second*60)//sprinf形成新的变量比如key:0,key:1,key:2}_, err := pipe.Exec(ctx) //一次性执行所有命令if err != nil {log.Fatal("Pipeline 执行失败:", err)}fmt.Println("批量写入 100 个键完成")// 并发读取var wg sync.WaitGroupfor i := 0; i < 10; i++ {wg.Add(1) // 增加等待组中的计数go func(id int) {defer wg.Add(-1)val, err := client.Get(ctx, fmt.Sprintf("key:%d", i)).Result()if err != nil {log.Printf("读取 key:%d 失败: %v", id, err)return}fmt.Printf("读取 key:%d = %s\n", id, val)}(i)}wg.Wait() // 等待所有协程完成
}
- 接池:PoolSize 和 MinIdleConns 控制连接复用,适合高并发。
- Pipeline:批量执行命令,减少网络开销。
- 并发:使用 goroutine 并发操作,结合 sync.WaitGroup 同步。
- 超时配置:防止网络问题导致阻塞。