1. 缓存异常
1.1 缓存雪崩
大量 key 同时过期,导致请求全部打到数据库,数据库瞬间压力激增甚至宕机。
🎯发生原因
大量 key 的 TTL 设置相同(如 EXPIRE key 3600)。
🎯解决方案
- 过期时间加随机值,EXPIRE key 3600 + RANDOM(1800),让 key 过期时间分散在 1~1.5 小时之间
- 多级缓存架构,使用本地缓存(如 Caffeine)+ Redis 集群,本地缓存可扛住部分穿透流量
- 缓存预热,系统上线前,提前将热点数据加载到 Redis,避免冷启动
- 服务降级 & 熔断,使用 Hystrix、Sentinel 在 DB 压力过大时自动降级,返回默认值
1.2 缓存击穿
某个热点 key 过期,大量并发请求同时访问该 key,导致数据库被瞬间击垮。
🎯发生原因
某个 key 是超高热点数据(如微博热搜第一条),它的 TTL 到期瞬间,大量并发请求同时查询。
🎯解决方案
- 热点数据永不过期,对明确的热点数据(如商品详情)设置永不过期,通过后台任务异步更新。
- 互斥锁,当缓存失效时,只允许一个线程去数据库加载数据,其他线程等待。我理解其他线程获取锁的时候再次判断缓存数据是否为空,不为空就返回,不用去数据库查询了。
properties
String key = "hot:product:1001";
String data = redis.get(key);
if (data == null) {
// 尝试获取锁
if (redis.setnx("lock:" + key, "1", 10)) {
try {
data = db.load(); // 查数据库
redis.setex(key, 3600, data); // 重新设置
} finally {
redis.del("lock:" + key); // 释放锁
}
} else {
// 等待一会儿重试(或返回旧数据)
Thread.sleep(50);
return getData(key); // 递归重试
}
}
1.3 缓存穿透
查询一个根本不存在的数据,缓存不存,数据库也没有,每次请求都打到 数据库。
🎯发生原因
恶意用户反复请求一个不存在的 key ,每次都打到数据库。
🎯解决方案
- 使用布隆过滤器快速判断数据是否存在,避免从数据库中查询数据是否存在,减轻数据库压力。你可以在将数据添加到Redis之前,先将其添加到布隆过滤器中。然后,在查询Redis之前,先通过布隆过滤器判断该数据是否存在。如果不存在,就可以直接返回。虽然布隆过滤器不能判断一个数据是否一定存在,但是可以判断它一定不存在。
- 请求入口的前端进行请求检测,例如请求参数不合理、请求参数是非法值、请求字段不存在,直接过滤掉。
- 对于那些数据库查询结果为空的情况,可以将这些空结果也缓存起来,但要给对应的key设置一个较短的过期时间。
properties
String data = redis.get(key);
if (data != null) {
return data;
}
data = db.load(key);
if (data == null) {
// 缓存空值,防止穿透
redis.setex(key, 60, ""); // 60秒后重试
return null;
}
redis.setex(key, 3600, data);