Golang 中使用 Redis
客户端库
在 Golang 中使用 Redis,通常会使用第三方库来简化与 Redis 服务器的交互。最常用的库是 redisgo 和 go-redis ,两者对比如下:
| 特性 | redisgo | go-redis |
|---|---|---|
| API 设计 | 更接近 redis-cli 的命令行操作,使用一个 Do 方法执行所有命令。 | 提供类型安全的 API,包装了大量函数来执行 Redis 命令,更面向对象。 |
| 连接管理 | 需要显式管理连接池。 | 自动使用连接池。 |
| 集群支持 | 不支持集群,需要额外实现。 | 原生支持集群模式和哨兵模式。 |
| 功能特点 | 包装精练,上手快,但功能相对基础。 | 功能更丰富,支持高级 API 封装和数据类型安全。 |
| 使用场景 | 适合对 Redis 命令比较熟悉,不需要集群功能,且对代码简洁度要求高的场景。 | 适合需要集群、哨兵、更高级功能以及更健壮类型安全的场景。 |
go-redis
go-redis 是社区最活跃的 Go Redis 客户端,支持 Standalone、哨兵、Cluster、Ring、多节点分片等模式。下面整理一些常用能力与伪代码。
安装
go get github.com/redis/go-redis/v9
连接与上下文
// 初始化全局客户端,自动复用连接池
rdb := redis.NewClient(&redis.Options{
Addr: "127.0.0.1:6379",
Password: "", // 无密码就留空
DB: 0, // 默认 DB
PoolSize: 200, // 连接池大小
MinIdleConns: 10, // 预热空闲连接
})
ctx := context.Background()
if err := rdb.Ping(ctx).Err(); err != nil {
panic(err)
}
基础命令
// String
_ = rdb.Set(ctx, "user:1:name", "Alice", 15*time.Minute).Err()
name, err := rdb.Get(ctx, "user:1:name").Result()
// Hash
_ = rdb.HSet(ctx, "user:1", "name", "Alice", "age", 20).Err()
age, _ := rdb.HGet(ctx, "user:1", "age").Int()
// List
_ = rdb.RPush(ctx, "todo", "task1", "task2").Err()
task, _ := rdb.BLPop(ctx, 0, "todo").Result()
Pipeline
当需要批量执行多条独立命令时,可以用 Pipeline 减少 RTT:
pipe := rdb.Pipeline()
get1 := pipe.Get(ctx, "key:1")
get2 := pipe.Get(ctx, "key:2")
_, err := pipe.Exec(ctx) // 发送并等待所有命令
v1, _ := get1.Result()
v2, _ := get2.Result()
事务(TxPipeline)
使用 TxPipeline 可以在 MULTI/EXEC 中批处理命令。
_, err := rdb.TxPipeline().
Incr(ctx, "counter").
Expire(ctx, "counter", time.Hour).
Exec(ctx)
Lua 脚本
const luaScript = `
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end`
res, err := rdb.Eval(ctx, luaScript, []string{"lock:demo"}, "request-id").Int()
Pub/Sub
pubsub := rdb.Subscribe(ctx, "cache:invalidate")
defer pubsub.Close()
for msg := range pubsub.Channel() {
fmt.Println("receive", msg.Payload)
}
// 发布
_ = rdb.Publish(ctx, "cache:invalidate", `{"key":"user:1"}`).Err()
分布式锁(伪代码)
token := uuid.NewString()
ok, _ := rdb.SetNX(ctx, "lock:job", token, 30*time.Second).Result()
if !ok {
return // 已被占用
}
defer func() {
lua := `if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("DEL", KEYS[1]) end`
_, _ = rdb.Eval(ctx, lua, []string{"lock:job"}, token).Result()
}()
常用调优
| 选项 | 说明 |
|---|---|
PoolSize | 连接池最大连接数,默认 CPUx10 |
MinIdleConns | 预热空闲连接数,防止突发流量连接抖动 |
DialTimeout | 建连超时,防止阻塞 |
ReadTimeout | 读超时 |
WriteTimeout | 写超时 |
OnConnect | 钩子,可在建立新连接时执行认证/日志等逻辑 |
组合场景示例
缓存失效广播
// 发布端
_ = rdb.Publish(ctx, "cache:invalidate", `{"key":"user:1"}`)
// 订阅端
pubsub := rdb.Subscribe(ctx, "cache:invalidate")
for msg := range pubsub.Channel() {
var payload Payload
_ = json.Unmarshal([]byte(msg.Payload), &payload)
localCache.Delete(payload.Key)
}
异步事件队列(List + BRPOP)
// 生产者
_ = rdb.RPush(ctx, "job:email", "{"user":1}")
// 消费者
for {
job, err := rdb.BLPop(ctx, 0, "job:email").Result()
if err != nil { continue }
process(job[1])
}
小结
- 读写高频业务建议封装一个
RedisClient包装器,集中收敛上下文、日志、错误。 - 合理利用 Pipeline、TxPipeline、Lua 来减少 RTT、保证原子性。
- Pub/Sub、Stream 适合做轻量事件通知;复杂可靠队列建议组合 List + ack 或者使用专用消息队列。
- 配置项(超时、连接池大小)需要结合业务 QPS 与 Redis 实例配置调优,避免“业务层没瓶颈、Redis 被打爆”的情况。