1.加锁
互斥锁
java
// 缓存不存在
if (cache == null) {
// 尝试获取分布式锁,比如setnx
if (lock("lock:goods:1001")) {
try {
// 查DB
data = db.query();
// 回写缓存
redis.set(key, data, expire);
return data;
} finally {
unlock();
}
} else {
// 没抢到锁,短暂休眠后重试
sleep(50ms);
return getGoodsFromCache();
}
}
区分 互斥锁 & X锁
这里的互斥锁指在redis加分布式锁:
SET lock:goods:1001 唯一标识 NX EX 过期时间这里边的lock()是包装了redis里面加锁的命令
解锁不能随便解锁随便DEL,必须 Lua 脚本原子校验 + 删除:
Lua-- 判断value是自己的,才删 if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end
2.逻辑过期
物理上不设置 TTL,在 value 里存逻辑过期时间,后台异步更新
java
CacheItem item = redis.get(key);
if (item == null) {
// 兜底,初始化
if (lock()) {
data = db.query();
redis.set(key, new CacheItem(data, now+30s));
unlock();
}
return redis.get(key).data;
}
// 判断逻辑过期
if (item.expireTime < now) {
if (tryLock()) {
// 异步线程更新,主线程直接返回旧值
threadPool.submit(() -> {
newData = db.query();
redis.set(key, new CacheItem(newData, now+30s));
unlock();
});
}
}
return item.data;
3.提前刷新
定时任务提前刷新
适合:热点固定、能提前预知的 key(首页爆款商品)
缺点:资源占用高;热点不固定时很难覆盖
4.限流(不推荐:单纯延长过期时间,只能缓解,不能根治,依然有击穿风险)
对该热点接口加单机 / 分布式限流,一旦检测到缓存失效,限制 QPS,保护数据库。一般配合上面方案一起用,不作为主方案。