易学社
第二套高阶模板 · 更大气的阅读体验

缓存失效策略防止重复查询数据库

发布时间:2025-12-14 09:14:15 阅读:439 次

缓存失效带来的问题

在做Web开发时,很多人会用Redis这类缓存来减轻数据压力。比如一个商品详情页,访问量大,但数据变化不频繁,直接从缓存拿比每次都查MySQL快得多。

可问题来了:缓存总有过期的时候。一旦缓存失效,大量请求同时发现“没缓存了”,就会一窝蜂冲向数据库。这种情况在高并发场景下特别危险,轻则响应变慢,重则数据库直接被打挂。

经典场景:缓存雪崩

想象一下,你做的电商系统里,几千个商品的缓存都设了10分钟过期。如果刚好这波请求都在第10分钟集中到达,缓存集体失效,数据库瞬间收到成千上万的查询请求,这就是典型的“缓存雪崩”。

更麻烦的是,数据库一慢,接口响应延迟,请求堆积,整个服务可能就卡住了。

怎么防止重复查库?

核心思路是:只让一个请求去查数据库,其他请求等着用结果。

最常用的做法是“互斥锁 + 双重检查”。当缓存失效时,先尝试加一个分布式锁(比如Redis的SETNX),只有拿到锁的那个线程才能去查数据库、更新缓存,其他线程等几毫秒,再去缓存里取值。

String data = redis.get("product:123");
if (data == null) {
// 尝试获取锁
String lockKey = "lock:product:123";
Boolean locked = redis.setnx(lockKey, "1", 10); // 过期10秒防死锁
if (locked) {
try {
data = db.query("SELECT * FROM products WHERE id=123");
redis.setex("product:123", 300, data); // 缓存5分钟
} finally {
redis.del(lockKey);
}
} else {
// 没抢到锁,短暂休眠后重试
Thread.sleep(50);
data = redis.get("product:123");
}
}

给缓存过期时间加点“随机性”

另一个简单有效的办法是别让所有缓存同时失效。比如原本打算统一设300秒过期,可以改成300秒 ± 随机几十秒。这样即使访问量大,失效时间也是错开的,不会集中打垮数据库。

就像公司食堂吃饭,如果所有人12点整准时冲,窗口肯定挤爆。但如果大家时间错开几分钟,压力就平滑多了。

提前续命:缓存永不过期?

还有种做法叫“逻辑过期”。缓存本身不设TTL,而是把过期时间存在value里。每次读的时候判断逻辑是否过期,如果快过期了,由当前请求异步触发更新,但返回的还是旧值。这样避免了集中重建缓存。

这种方式适合对数据一致性要求不高的场景,比如首页轮播图、公告栏这种。

小结一下实用建议

面对缓存失效,不能放任不管。加锁防止并发重建、设置随机过期时间、使用逻辑过期机制,都是实际项目中验证过的办法。关键是要结合业务场景选合适的方案,别等到数据库报警才想起来优化。