redis 是应对数据库性能瓶颈的刚需组件,而非可选优化:必须直连缓存、命中即返,未命中才查库并写缓存;需设带前缀与版本的 key、显式 ttl、空值缓存防穿透、ttl 加随机偏移防雪崩;生产用哨兵/集群;删缓存比写缓存更安全;客户端须配合理连接池与超时机制。

redis 不是用来“锦上添花”的,而是当 SELECT * FROM users WHERE id = ? 开始拖慢接口响应、数据库连接池频繁告警时,你该立刻动手的地方。
缓存读取逻辑必须绕过数据库直连
常见错误是把缓存当成“可选附加层”:先查 DB,再写缓存。这完全没减轻压力,反而多了一次 DB 查询 + 一次 Redis 写入。
正确路径只有一条:请求进来 → 查 GET user:123 → 命中就直接返回 → 未命中才查 DB,且必须带写缓存动作。
- 用
context.WithTimeout控制 Redis 操作超时,避免卡死 goroutine - 缓存 key 要带业务前缀和版本号,比如
user:v2:456,避免字段变更后旧缓存污染新逻辑 - 不要用
SET简单覆盖,优先用SETEX或SET的EX参数显式设 TTL,防止脏数据长期滞留
缓存未命中时必须防穿透和雪崩
当大量请求同时查一个不存在的 user:999999,DB 会瞬间被打爆——这就是缓存穿透;如果所有缓存 key 在同一秒过期,也会引发类似问题(雪崩)。
简单有效做法不是堆复杂方案,而是两步堵漏:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 对空结果也缓存,但用特殊值(如
"null")+ 较短 TTL(比如 2 分钟),避免反复穿透 - TTL 加随机偏移,比如基础 30 分钟,再
+ rand.Intn(600)秒,打散集中过期 - 不依赖单点 Redis 实例:生产环境必须用哨兵或集群模式,
go-redis/v8客户端默认支持自动重连和节点发现
写缓存和删缓存,选哪个更稳?
很多人纠结“更新用户信息时,该 SET 新值还是 DEL 旧 key”。答案很现实:除非你能保证写 DB 和写 Redis 100% 原子成功,否则 DEL 更安全。
DEL 是幂等操作,失败了重试无副作用;而 SET 失败会导致缓存和 DB 不一致,且难以修复。
- 推荐流程:DB 更新成功 → 异步触发
DEL user:v2:456(哪怕用 goroutine + retry) - 如果必须同步写缓存,务必用
redis.TxPipeline包裹DEL+SETEX,但要注意事务在 Redis 集群下不跨 slot 生效 - 别信“先删缓存再更新 DB”,那是经典 race condition:删完缓存、DB 还没更新完,此时并发读就会把旧数据重新塞进缓存
Go 客户端初始化必须配连接池和健康检查
go-redis/redis/v8 默认连接池大小是 10,对微服务远远不够。更麻烦的是,没人主动告诉你:连接池耗尽时,错误日志只会打印 "context deadline exceeded",根本看不出是 Redis 连接问题。
- 至少设
MinIdleConns: 5、MaxIdleConns: 50、MaxConnAge: 30 * time.Minute - 加一行
Ping()检查,在服务启动时验证 Redis 可达性,而不是等第一个请求失败才报警 - 别忽略
ReadTimeout和WriteTimeout,它们和 context timeout 是两回事:前者控制单次网络 IO,后者控制整个操作生命周期
缓存不是加个 GET 就万事大吉的事。真正压测时暴露的问题,往往藏在 TTL 设置是否合理、连接池是否撑得住突发流量、空值是否被缓存、以及删缓存那 10 毫秒的竞态里。










