公司内部系统突然变慢,用户投诉接口响应要等好几秒。查看日志发现,原本应该从缓存读取的数据,全跑去查数据库了。这就是典型的缓存机制失效问题。别慌,先理清思路,一步步排查。
确认是不是真的失效
有时候不是缓存挂了,而是数据压根没进缓存。比如某个接口在查询时忘了写 set 到 Redis 的逻辑,或者缓存 key 设置错误,导致每次请求都 miss。可以先在代码里加个临时日志,打印缓存的 get 和 set 操作,看看有没有被正确调用。
检查缓存过期策略
常见的一种情况是 TTL(Time To Live)设置太短,数据刚写入没多久就过期了。另一种是用了 LRU 策略但内存不够,老数据被挤出去了。这时候需要评估业务需求,适当延长过期时间,或者扩容缓存实例。
比如有个商品详情页接口,缓存设了 60 秒过期。高峰期每秒上千请求,相当于每分钟都要穿透到数据库重建缓存,数据库压力自然扛不住。改成 5 分钟,并配合主动刷新机制,情况就好多了。
注意缓存击穿和雪崩
如果大量 key 在同一时间过期,就会引发雪崩,所有请求直接打到数据库。可以在设置过期时间时加个随机偏移量,避免集体失效。
// 伪代码示例:添加随机过期时间
int expireTime = 300 + rand(0, 300); // 5 分钟到 10 分钟之间
redis.setex(key, expireTime, data);
而缓存击穿是指某个热点 key 失效瞬间,大量并发请求同时去加载数据。可以用互斥锁控制,只让一个线程去重建缓存,其他等待。
网络或服务异常
有时候缓存服务本身出问题,比如 Redis 实例宕机、主从切换中、网络抖动,都会导致连接失败。这时候应用如果没有降级策略,可能直接抛错。建议加上熔断和本地缓存兜底,比如用 Caffeine 先顶一阵。
代码层面的问题
有次上线后发现用户登录状态频繁丢失,查了一圈才发现是缓存 key 拼接出了问题。原本应该用用户 ID,结果误用了会话 ID,导致 key 不一致,set 和 get 根本对不上。这种低级错误其实很常见,单元测试里得多覆盖这类场景。
// 错误示例
String key = "session:" + sessionId; // 应该是 userId
// 正确写法
String key = "user:login:" + userId;
监控和告警不能少
线上系统一定要有缓存命中率的监控。如果命中率突然掉到 70% 以下,就得警惕了。结合慢查询日志和接口响应时间,能更快定位问题。我们之前就是靠 Grafana 看板发现某接口缓存命中率归零,才及时止损。
缓存机制失效并不可怕,关键是要有清晰的排查路径。从代码到配置,从策略到基础设施,一层层筛过去,问题总会浮出水面。