目前 Go 生态里,最炙手可热的 Redis 客户端非 go-redis 莫属。
亮点满满:
- 原生 Go 实现,性能杠杠的,跑得飞快
- 内置连接池,高并发也不怵
- 全面特性支持:集群、哨兵、管道、事务,应有尽有
安装
go get github.com/redis/go-redis/v9
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| # 快速上手:Go 与 Redis 来一场高效约会
## 连接 Redis
~~~yaml package main
import ( "context" "fmt" "github.com/redis/go-redis/v9" )
var ctx = context.Background()
func main() { rdb := redis.NewClient(&redis.Options{ Addr: "localhost:6379", Password: "", // no password DB: 0, // use default DB }) _, err := rdb.Ping(ctx).Result() if err != nil { panic(err) } fmt.Println("Redis 连接成功!") }
|
## 手到擒来:Redis 常用操作速成
~~~bash
// 写入键值
rdb.Set(ctx, "username", "chihqiang", 0)
// 读取键值
val, _ := rdb.Get(ctx, "username").Result()
fmt.Println("username:", val)
// 自增计数器
rdb.Incr(ctx, "counter")
// 设置过期时间
rdb.Set(ctx, "token:chihqiang", "123456", time.Minute)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
| # 实战演练:Redis 典型业务场景全揭秘
## 缓存掌控术:Cache Aside 模式详解
~~~yaml package main
import ( "context" "log" "time"
"github.com/redis/go-redis/v9" )
var ctx = context.Background()
// GetUserUnified 一个方法搞定单个/批量获取和缓存刷新 // ids: 用户 ID 列表 // refresh: 是否强制刷新缓存 // dbQuery: 数据库查询函数,外部传入 func GetUserUnified(rdb *redis.Client, ids []string, refresh bool, dbQuery func(id string) string) (map[string]string, error) { result := make(map[string]string)
for _, id := range ids { key := "user:" + id var user string var err error
if !refresh { // 尝试从缓存获取 user, err = rdb.Get(ctx, key).Result() if err == nil { log.Println("缓存命中,直接返回:", key) result[id] = user continue } if err != redis.Nil { log.Println("Redis 出错:", err) continue } log.Println("缓存未命中,开始从数据库查询:", key) } else { log.Println("强制刷新缓存:", key) }
// 从数据库查询(调用外部传入的函数) user = dbQuery(id)
// 回写缓存 if err := rdb.Set(ctx, key, user, 10*time.Minute).Err(); err != nil { log.Println("缓存回写失败:", key, err) } else { log.Println("缓存已回写 Redis:", key) }
result[id] = user }
return result, nil }
func main() { // 初始化 Redis 客户端 rdb := redis.NewClient(&redis.Options{ Addr: "localhost:6379", Password: "", DB: 0, })
// 自定义数据库查询函数 dbQuery := func(id string) string { // 模拟查询数据库 return "db_user_" + id }
// 单个用户获取 userMap, _ := GetUserUnified(rdb, []string{"1001"}, false, dbQuery) log.Println("用户 1001:", userMap["1001"])
// 批量用户获取 ids := []string{"1001", "1002", "1003"} users, _ := GetUserUnified(rdb, ids, false, dbQuery) log.Println("批量用户:", users)
// 强制刷新缓存 usersRefresh, _ := GetUserUnified(rdb, []string{"1002"}, true, dbQuery) log.Println("刷新缓存后的用户 1002:", usersRefresh["1002"]) }
|
核心思路
**Cache Aside(旁路缓存)模式**:
1. 先尝试从缓存获取数据。
2. 缓存未命中 → 回源数据库查询。
3. 查询到数据库数据后回写缓存(设置过期时间)。
4. 返回最终结果。
## 锁定风险:Cache Aside + 分布式锁防超卖
~~~javascript
import (
"context"
"log"
"time"
"github.com/redis/go-redis/v9"
)
var ctx = context.Background()
var rdb *redis.Client // 假设已初始化 Redis 客户端
// AcquireLock 尝试获取分布式锁
func AcquireLock(key, value string, ttl time.Duration) bool {
ok, err := rdb.SetNX(ctx, key, value, ttl).Result()
if err != nil {
log.Println("获取锁出错:", err)
return false
}
if ok {
log.Println("锁获取成功:", key)
} else {
log.Println("锁获取失败,已被占用:", key)
}
return ok
}
// ReleaseLock 释放分布式锁
func ReleaseLock(key, value string) {
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{key}, value).Result()
if err != nil {
log.Println("释放锁出错:", err)
return
}
if res.(int64) > 0 {
log.Println("锁已释放:", key)
} else {
log.Println("未释放锁(可能不是自己的锁):", key)
}
}
1 2 3 4 5 6 7 8
| **👉 高并发护盾**:通过 SETNX + EXPIRE 或 Redlock 算法,轻松防止多个服务同时操作同一资源。
**🔐 分布式锁流程图**:
```bash Client A 尝试获取锁 → SETNX 成功 → 执行业务 → 释放锁 Client B 尝试获取锁 → SETNX 失败 → 等待或重试
|
## 分数风云榜:Redis 排行榜典型业务场景
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| package main
import ( "context" "fmt" "log"
"github.com/redis/go-redis/v9" )
var ctx = context.Background()
func main() { // 初始化 Redis 客户端 rdb := redis.NewClient(&redis.Options{ Addr: "localhost:6379", DB: 0, })
// 🌟 添加玩家分数到排行榜(ZSet) players := []struct { name string score float64 }{ {"Alice", 100}, {"Bob", 200}, {"Charlie", 150}, }
for _, p := range players { if err := rdb.ZAdd(ctx, "rank", redis.Z{Score: p.score, Member: p.name}).Err(); err != nil { log.Println("添加分数失败:", p.name, err) } else { log.Printf("已添加分数: %s -> %f\n", p.name, p.score) } }
// 🏆 获取排行榜前 10 名 res, err := rdb.ZRevRangeWithScores(ctx, "rank", 0, 9).Result() if err != nil { log.Println("获取排行榜失败:", err) return }
fmt.Println("🏅 当前排行榜前 10 名:") for i, z := range res { fmt.Printf("第%d名: %s - %.0f 分\n", i+1, z.Member, z.Score) } }
|
核心思路
- 使用 **Redis 有序集合(ZSet)** 来存储玩家和分数。
- **Score** 字段表示分数,**Member** 字段表示玩家名称。
- 利用 ZSet 的 **自动排序** 特性,可以快速获取排行榜前 N 名。
## 小巧消息队列:Redis 队列应用场景
~~~yaml
package main
import (
"context"
"fmt"
"log"
"github.com/redis/go-redis/v9"
)
var ctx = context.Background()
func main() {
// 初始化 Redis 客户端
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
DB: 0,
})
// 🌟 生产者:往队列中推送任务
tasks := []string{"task1", "task2", "task3"}
for _, t := range tasks {
if err := rdb.LPush(ctx, "queue", t).Err(); err != nil {
log.Println("推送任务失败:", t, err)
} else {
log.Println("已推送任务到队列:", t)
}
}
// 🏃 消费者(阻塞式)
log.Println("开始消费队列任务...")
for {
// BRPop 阻塞式弹出队列任务,队列为空时等待
result, err := rdb.BRPop(ctx, 0, "queue").Result()
if err != nil {
log.Println("消费任务失败:", err)
continue
}
// result[0] 是队列名,result[1] 是任务内容
task := result[1]
fmt.Println("消费任务:", task)
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| 核心思路
- 利用 **Redis 列表(List)** 实现消息队列,支持生产者-消费者模型。 - **LPUSH**:生产者向队列头部添加任务。 - **BRPOP**:消费者从队列尾部阻塞式获取任务,队列为空时自动等待。
# 性能飞跃:Redis 优化技巧与最佳实践
## 合理设置过期时间,避免缓存雪崩
1. **使用随机过期时间(错峰过期)** - 给缓存设置略微不同的 TTL,避免大量 key 同时过期导致瞬间流量打到数据库。 - 示例:`10min + rand(0~5min)` 2. **结合本地缓存** - 在应用端使用本地缓存(如 Go map、LRU)保存热点数据,减少对 Redis 的瞬时压力。 - 热点数据先从本地缓存读取,再回退到 Redis,最后查数据库。
### 使用连接池,提升 Redis 性能
- **Go-Redis 默认内置连接池** - 自动管理连接复用,支持高并发请求。 - **常见配置项**: 1. `PoolSize`:连接池最大连接数,默认根据 CPU 核心数调整。 2. `MinIdleConns`:最小空闲连接数,保证请求高峰时有可用连接。 3. `IdleTimeout`:空闲连接超时时间,超过则关闭。 4. `DialTimeout / ReadTimeout / WriteTimeout`:控制连接建立和读写超时,防止请求阻塞。
```go // 初始化 Redis 客户端(带连接池配置) rdb := redis.NewClient(&redis.Options{ Addr: "localhost:6379", // Redis 地址 Password: "", // Redis 密码(如有) DB: 0, // 使用的数据库 PoolSize: 50, // 最大连接数,根据并发量调整 MinIdleConns: 10, // 最小空闲连接,保证高峰期有可用连接 DialTimeout: 5 * time.Second, // 建立连接超时 ReadTimeout: 3 * time.Second, // 读操作超时 WriteTimeout: 3 * time.Second, // 写操作超时 })
|
### 批量操作:使用 Pipeline 或 MGet 减少 RTT
1. **Pipeline(管道)**
- 可以一次性发送多条命令,Redis 批量执行,减少网络往返次数(RTT)。
- 适合批量写入或更新操作。
2. **MGet / MSet**
- 批量获取或设置多个 key,一次请求返回多条结果,避免多次单独请求。
- 适合批量读取缓存数据,提高吞吐量。
1 2 3 4 5
| pipe := rdb.Pipeline() for i := 0; i < 1000; i++ { pipe.Set(ctx, fmt.Sprintf("key%d", i), i, 0) } _, err := pipe.Exec(ctx)
|
### 热点 Key 处理技巧
1. **给 Key 加随机后缀分片**
- 将高频访问的热点 Key 拆分为多个分片,例如 `user:123:1`、`user:123:2`……
- 避免大量请求集中到同一个 Key,缓解 Redis 单点压力。
2. **结合本地缓存**
- 使用应用端缓存(如 `freecache`、`ristretto`)存储热点数据,先读本地缓存再回退到 Redis。
- 减少对 Redis 的请求频率,提高系统吞吐量和响应速度。