必须显式配置PoolSize、MinIdleConns、超时及前缀等参数并验证连通性,否则QPS超200即现连接池超时;所有操作须带context.WithTimeout,键需加业务前缀,空值用"null"占位防穿透,结构体序列化需json tag。

直接用 redis.NewClient 初始化后就往 handler 里塞 Get/Set,线上大概率在 QPS 超 200 后开始卡住、报 redis: connection pool timeout——这不是 Redis 慢,是连接池没调、超时没设、键没前缀、序列化不一致,全在初始化阶段漏掉了。
初始化 redis.Client 必须显式配 PoolSize 并验证连通性
默认 PoolSize=10 在中等并发下就会排队;不调 rdb.Ping(ctx).Err(),第一个缓存请求失败才暴露配置错误,排查成本翻倍。
-
PoolSize设为预估并发请求数的 2–4 倍(如 300 QPS → 60–120),但别超 Redis 的maxclients(通常 ≤5000) - 必须显式设
MinIdleConns: 5和MaxConnAge: 30 * time.Minute,防 NAT/SLB 静默断连后连接假死 - 地址、密码、DB 编号从环境变量(如
REDIS_ADDR)读,别硬编码"localhost:6379" - 初始化后立刻执行
rdb.Ping(ctx).Err(),超时用context.WithTimeout(context.Background(), 2*time.Second)
所有 Redis 操作必须带 context.WithTimeout
用 context.Background() 调 rdb.Get,Redis 卡 5 秒,你的 handler 就卡 5 秒,runtime.NumGoroutine() 暴涨、QPS 断崖下跌,日志里只看到连接池超时,找不到根因。
- HTTP handler 中统一用
ctx, cancel := context.WithTimeout(c.Request.Context(), 300*time.Millisecond),300ms 是缓存场景合理上限 -
Set、Del、Expire等写操作同样要超时,尤其分布式锁逻辑缺超时会导致锁假死 - 别在包级或
init()里声明var ctx = context.Background()——它不是“默认”,是“永生”
缓存未命中必须显式判断 redis.Nil 错误
rdb.Get(ctx, key).Result() 返回值语义容易被忽略:命中返回值,未命中返回 redis.Nil 错误(不是 nil 指针)。不判断就直接解包,会导致空结构体、panic 或缓存写入无效数据。
立即学习“go语言免费学习笔记(深入)”;
- 正确写法:
val, err := rdb.Get(ctx, key).Result(); if errors.Is(err, redis.Nil) { /* 缓存穿透处理 */ } - 结构体字段没加
json:"xxx"tag,json.Marshal会序列化出零值,缓存写入的是空对象 - 键必须加业务前缀(如
"user:1001"),避免不同模块 key 冲突,也方便运维用redis-cli KEYS "user:*"查
穿透/击穿/雪崩必须在 Go 层兜底
Redis 自身不解决这三类问题。布隆过滤器太重,多数业务用轻量策略更稳。
- 穿透:查 DB 返回空,缓存层存
rdb.Set(ctx, key, "null", 60*time.Second);业务解包前先判if val == "null" { return nil, nil } - 击穿:热点 key 过期瞬间大量请求打穿 DB,用
singleflight.Group包一层Do,同一 key 并发请求只放行一个去查 DB - 雪崩:TTL 加随机扰动,如
baseTTL + time.Duration(rand.Int63n(int64(2*time.Minute))),每次生成前用time.Now().UnixNano()做 seed
真正难的不是连上 Redis,而是连接池参数、context 超时、错误判别、键设计、空值策略这些细节堆叠起来的稳定性。漏掉任意一环,高并发下都会集中爆发——而且错误日志不会直接告诉你哪错了,只会卡、慢、掉帧。


















