Skip to content

1. 缓存异常

1.1 缓存雪崩

大量 key 同时过期,导致请求全部打到数据库,数据库瞬间压力激增甚至宕机。

🎯发生原因

大量 key 的 TTL 设置相同(如 EXPIRE key 3600)。

🎯解决方案

  1. 过期时间加随机值,EXPIRE key 3600 + RANDOM(1800),让 key 过期时间分散在 1~1.5 小时之间
  2. 多级缓存架构,使用本地缓存(如 Caffeine)+ Redis 集群,本地缓存可扛住部分穿透流量
  3. 缓存预热,系统上线前,提前将热点数据加载到 Redis,避免冷启动
  4. 服务降级 & 熔断,使用 Hystrix、Sentinel 在 DB 压力过大时自动降级,返回默认值

1.2 缓存击穿

某个热点 key 过期,大量并发请求同时访问该 key,导致数据库被瞬间击垮。

🎯发生原因

某个 key 是超高热点数据(如微博热搜第一条),它的 TTL 到期瞬间,大量并发请求同时查询。

🎯解决方案

  1. 热点数据永不过期,对明确的热点数据(如商品详情)设置永不过期,通过后台任务异步更新。
  2. 互斥锁,当缓存失效时,只允许一个线程去数据库加载数据,其他线程等待。我理解其他线程获取锁的时候再次判断缓存数据是否为空,不为空就返回,不用去数据库查询了。
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 ,每次都打到数据库。

🎯解决方案

  1. 使用布隆过滤器快速判断数据是否存在,避免从数据库中查询数据是否存在,减轻数据库压力。你可以在将数据添加到Redis之前,先将其添加到布隆过滤器中。然后,在查询Redis之前,先通过布隆过滤器判断该数据是否存在。如果不存在,就可以直接返回。虽然布隆过滤器不能判断一个数据是否一定存在,但是可以判断它一定不存在。
  2. 请求入口的前端进行请求检测,例如请求参数不合理、请求参数是非法值、请求字段不存在,直接过滤掉。
  3. 对于那些数据库查询结果为空的情况,可以将这些空结果也缓存起来,但要给对应的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);

Released under the MIT License.