缓存失效策略常见问题解析

缓存失效不是删了就完事

很多人觉得,缓存失效就是简单地把旧数据从缓存里删掉,等下次请求再重新加载。听起来没错,但在实际系统中,这种“删一下”的操作可能埋着不少坑。比如你在一个电商网站做商品详情页优化,用户刚下单成功,页面却还显示“库存充足”,这就是典型的缓存没及时失效。

缓存穿透:查不到的数据反复打穿缓存

当请求一个数据库里根本不存在的数据时,缓存也不会存它。每次请求都直接落到数据库上,等于缓存形同虚设。这种情况在恶意攻击或接口被刷时特别明显。

解决办法之一是使用“空值缓存”:即使查不到,也往缓存里塞个 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 挂了怎么办?不能因为缓存不可用就连数据库也不查了。降级策略要提前想好:比如临时关闭缓存层,所有请求直连数据库,等缓存恢复后再切回来。

同时监控缓存命中率。如果突然下降,可能是失效策略出了问题,也可能是业务流量变了。