跳到主要内容

Golang 中使用 Redis

客户端库

在 Golang 中使用 Redis,通常会使用第三方库来简化与 Redis 服务器的交互。最常用的库是 redisgogo-redis ,两者对比如下:

特性redisgogo-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 被打爆”的情况。