Redis作为高性能缓存中间件早就深入人心,但在Go语言生态里,咱要是想用好Redis,得找个趁手的工具才行。go-redis 这个库就是个不错的选择,不过大伙儿用得最多的就是那几个简单操作,今儿个咱聊点不一样的玩法。
连接池调优小技巧
连接池配置看着简单,但是调优起来还真有点门道。瞧瞧这段代码:
rdb := redis.NewClient(&redis.Options{
PoolSize: 30,
MinIdleConns: 10,
MaxConnAge: 20 * time.Minute,
PoolTimeout: 4 * time.Second,
})
有意思的是,PoolSize
设多大合适完全得看场景。要是设小了,高峰期连接不够用,队列堵得老长;设太大吧,又浪费资源。我一般是按照最大并发量的1.5倍来设置。
温馨提示:MinIdleConns
别设太小,不然每次都要建连接,多费事啊。
Pipeline批处理大杀器
一个一个命令发送?太慢了!用Pipeline批量处理,效率蹭蹭往上涨:
pipe := rdb.Pipeline() var incr *redis.IntCmd for i := 0;i < 100;i++ {
key := fmt.Sprintf(“key:%d”, i)
incr = pipe.Incr(ctx, key)
}
_, err := pipe.Exec(ctx) if err != nil {
panic(err)
}
这么写比一条条执行快好几倍!不过也得当心,攒的命令太多也不好,容易占内存。我觉得每次100到200条比较合适。
分布式锁进阶玩法
分布式锁不是简单的SetNX
就完事了,还得加上过期时间和唯一标识:
lockKey := "order:lock" // 用UUID作为锁值,这样只能自己解锁 lockValue := uuid.New().String()
ok, err := rdb.SetNX(ctx, lockKey, lockValue, 10*time.Second).Result() if !ok { return errors.New("获取锁失败")
} // 解锁要用Lua脚本,保证原子性 unlockScript:= `
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end`
加锁的时候顺手设个过期时间,省得自己忘记解锁了。
事务处理有点绕
Redis事务跟咱常见的数据库事务不太一样,得用Watch
来防止并发修改:
key := "money" err := rdb.Watch(ctx, func(tx *redis.Tx) error {
// 拿到值 n, err := tx.Get(ctx, key).Int()
if err != nil {
return err
}
// 在事务里修改 _, err = tx.TxPipelined(ctx, func(pipe redis.Pipeliner) error {
pipe.Set(ctx, key, n+100, 0)
return nil })
return err
}, key)
写得有点啰嗦?没办法,这就是Redis事务的味道。不过用好了还是挺好使的。
自动重试机制
网络不稳定的时候,自动重试就派上用场了:
rdb := redis.NewClient(&redis.Options{
MaxRetries: 3,
MinRetryBackoff: time.Millisecond * 100,
MaxRetryBackoff: time.Second,
})
不过话说回来,重试也不是万能的,关键命令该加超时控制还得加:
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) defer cancel()
val, err := rdb.Get(ctx, "key").Result()
这样写代码稳当多了,出问题也不至于一直卡着。
其实go-redis还有好多高级玩法,比如集群模式、哨兵模式、读写分离啥的。不过这些都得配合实际场景,盲目上就是给自己找麻烦。写代码讲究的就是实用,用多了自然就知道啥时候该用啥功能。
记住几个关键点:连接池要调优、批量处理提效率、分布式锁要严谨、事务处理要当心。这些都整明白了,基本上就能应付日常开发了。