一、缓存不是把查询结果塞进去
Redis 在国内后端项目里几乎是标配,但缓存事故也集中:缓存击穿打满数据库,热 key 打爆单核,大 key 阻塞主线程,过期策略随意导致旧数据。缓存设计的核心不是“加一层 Redis”,而是明确一致性、失效方式和降级路径。
常见缓存对象可分三类:低频变更的配置数据、高频读取的详情页数据、需要快速计数或排序的临时数据。不同对象不能用同一套 TTL。配置数据可长 TTL 加主动刷新;商品详情适合短 TTL 加随机抖动;排行榜、计数器要考虑落库和重放。
| 场景 | 推荐策略 | 风险点 |
|---|---|---|
| 商品详情 | Cache Aside + TTL 抖动 | 更新后短时间旧值 |
| 计数器 | Redis 增量 + 异步落库 | 失败重试要幂等 |
| 热门榜 | ZSET + 定时重算 | 大 key 和全量读取 |
二、Cache Aside 要写完整
最常用的是 Cache Aside:读时先查缓存,未命中再查数据库并回填;写时先更新数据库,再删除缓存。这里容易犯两个错误:把删除缓存写成更新缓存,或删除失败不处理,导致旧值长期存在。
下面是 Go 写法示例,重点是空值缓存、TTL 抖动和删除失败记录。示例省略了数据库实现,但保留了关键分支。
funcGetProduct(ctx context.Context,idint64)(*Product,error){key:=fmt.Sprintf("product:%d",id)val,err:=rdb.Get(ctx,key).Result()iferr==nil{ifval=="__nil__"{returnnil,sql.ErrNoRows}varp Productreturn&p,json.Unmarshal([]byte(val),&p)}iferr!=redis.Nil{log.Printf("redis get failed: %v",err)}p,err:=repo.FindProduct(ctx,id)iferrors.Is(err,sql.ErrNoRows){_=rdb.Set(ctx,key,"__nil__",time.Minute).Err()returnnil,err}iferr!=nil{returnnil,err}bs,_:=json.Marshal(p)ttl:=10*time.Minute+time.Duration(rand.Intn(120))*time.Second_=rdb.Set(ctx,key,bs,ttl).Err()returnp,nil}funcUpdateProduct(ctx context.Context,p*Product)error{iferr:=repo.UpdateProduct(ctx,p);err!=nil{returnerr}key:=fmt.Sprintf("product:%d",p.ID)returnrdb.Del(ctx,key).Err()}空值缓存能缓解穿透,但 TTL 必须短,否则新数据可能被旧空值挡住。TTL 抖动能避免同批 key 同时过期。写后删缓存仍有短暂不一致窗口,核心链路要结合版本号、消息补偿或直接绕过缓存。
三、热 key 和大 key 要提前治理
Redis 单个命令很快,但主线程模型决定了大 key 和慢命令会影响其他请求。几 MB 的 String、几万个元素的 Hash、一次KEYS *,都可能让延迟突然上升。生产业务禁止使用KEYS,排查也应使用SCAN。
# 查慢日志redis-cli slowlog get10# 观察命令统计redis-cli info commandstats redis-cli--scan--pattern'product:*'|head热 key 通常用本地缓存、读副本、拆 key 处理。首页配置可放进进程内缓存,设置 5 到 30 秒 TTL;计数类可按用户或时间片分桶再汇总。不要把所有压力都交给 Redis Cluster,集群不能消除单个热 key 的集中访问。
四、故障恢复要能演练
Redis 宕机时,最怕业务把所有请求原样打到数据库。连接池要设置超时,缓存访问要有熔断,关键接口要有降级值。对缓存来说,恢复也不是简单重启实例:如果大量 key 同时失效,恢复后会出现冷缓存,数据库仍然可能被打满。
redis:addr:redis.internal:6379pool_size:100min_idle_conns:10dial_timeout:200msread_timeout:100mswrite_timeout:100msmax_retries:1实践建议:先列出业务里最重要的 20 个缓存 key,逐个确认 TTL、最大体积、回源成本、删除策略和 Redis 不可用时的行为。命中率只是结果指标,真正要长期维护的是 key 设计、过期策略、慢命令治理和故障演练。