缓存失效不是删了就完事
很多人觉得,缓存失效就是简单地把旧数据从缓存里删掉,等下次请求再重新加载。听起来没错,但在实际系统中,这种“删一下”的操作可能埋着不少坑。比如你在一个电商网站做商品详情页优化,用户刚下单成功,页面却还显示“库存充足”,这就是典型的缓存没及时失效。
缓存穿透:查不到的数据反复打穿缓存
当请求一个数据库里根本不存在的数据时,缓存也不会存它。每次请求都直接落到数据库上,等于缓存形同虚设。这种情况在恶意攻击或接口被刷时特别明显。
解决办法之一是使用“空值缓存”:即使查不到,也往缓存里塞个 null 值,设置较短过期时间,比如 1 分钟。
if (data == null) {
cache.set(key, "null_placeholder", 60); // 缓存空占位符 60 秒
}
缓存雪崩:大量 key 同时过期
想象一下,你在做秒杀活动,所有商品缓存都设置了 1 小时过期,结果一到整点,成千上万个 key 集体失效,瞬间请求全压到数据库,系统直接卡死。
避免方法是给过期时间加个随机偏移。比如原本设 3600 秒,改成 3600 + random(1, 600),让失效时间分散开。
缓存击穿:热点 key 突然失效
某个爆款商品的详情页被疯狂访问,缓存一失效,大量并发请求同时涌向数据库。这和雪崩类似,但只针对单个热门 key。
可以用互斥锁(mutex)控制:第一个发现缓存失效的线程去查数据库并重建缓存,其他线程等着用新数据。
String data = cache.get(key);
if (data == null) {
if (acquireLock(key)) {
data = db.query(key);
cache.set(key, data, 3600);
releaseLock(key);
} else {
// 等待锁释放后重新读缓存
Thread.sleep(50);
data = cache.get(key);
}
}
双写不一致:数据库和缓存更新不同步
先更新数据库还是先删缓存?这个问题经常吵翻天。常见的做法是“先更库,再删缓存”,但中间仍有短暂不一致窗口。
比如用户改了头像,数据库更新了,但缓存还没删,别人看到的还是旧图。虽然时间很短,但在高并发下能被捕捉到。
进阶方案可以引入消息队列,把缓存删除操作异步化,保证最终一致。
懒加载 vs 主动刷新
很多系统采用懒加载——缓存失效后等下次查询时再重建。好处是省资源,坏处是用户可能碰到“卡一下”的情况。
另一种是主动刷新,在缓存快到期前自动后台更新,用户体验更平滑。适合对延迟敏感的服务,比如金融行情、实时榜单。
别忘了缓存本身也可能出问题
Redis 挂了怎么办?不能因为缓存不可用就连数据库也不查了。降级策略要提前想好:比如临时关闭缓存层,所有请求直连数据库,等缓存恢复后再切回来。
同时监控缓存命中率。如果突然下降,可能是失效策略出了问题,也可能是业务流量变了。