1. 概述

在高并发系统中,缓存是提升性能、降低数据库压力的重要组件。然而,在实际应用中,缓存可能会遇到三种典型问题:缓存穿透缓存击穿缓存雪崩。这些问题如果不加以妥善处理,可能会导致数据库负载激增,甚至影响整个系统的稳定性。本文将详细介绍这三种问题的区别,并探讨相应的解决方案。

2. 什么是缓存穿透、缓存击穿和缓存雪崩?

2.1 缓存穿透(Cache Penetration)

现象:指的是缓存和数据库中都没有的数据,但却被大量请求访问,导致请求直接落到数据库上,增加数据库的压力。

速记口诀恶意请求绕过缓存,访问数据库中不存在的数据,导致数据库负载增加。

2.2 缓存击穿(Cache Breakdown)

现象:某个热点数据在缓存中过期的瞬间,刚好有大量请求同时到达,此时所有请求都会直接访问数据库,可能会导致数据库压力激增。

速记口诀缓存数据突然失效,大量请求瞬间涌向数据库。

2.3 缓存雪崩(Cache Avalanche)

现象大量缓存在同一时间过期,使得所有原本应命中缓存的请求都直接访问数据库,造成数据库负载飙升,甚至引发全局性故障。

速记口诀大量缓存同时失效,流量直击数据库,引发系统风险。

3. 解决方案

3.1 缓存穿透解决方案

方案 1:使用布隆过滤器(Bloom Filter)

  • 在缓存层引入布隆过滤器,提前存储可能存在的数据标识,对于不存在的数据,直接拦截请求,避免打到数据库。

方案 2:缓存空值

  • 针对数据库和缓存均不存在的数据,可以在缓存中存储一个短时间有效的空值(如 5 分钟),减少对数据库的冲击。

3.2 缓存击穿解决方案

方案 1:设置热点数据永不过期

  • 对于高频访问的热点数据,可以设置永不过期,确保缓存始终存在,避免瞬间失效造成数据库负载激增。

方案 2:互斥锁机制

  • 在缓存数据为空时,采用互斥锁(如 setnx 方式),确保只有一个请求能查询数据库并更新缓存,其他请求需等待或短暂休眠后重试。
public static String getProductDescById(String id) {
    String desc = redis.get(id);
    if (desc == null) {
        if (redis.setnx("lock_id", 1, 60) == 1) {
            try {
                desc = getFromDB(id);
                redis.set(id, desc, 60 * 60 * 24);
            } catch (Exception ex) {
                LogHelper.error(ex);
            } finally {
                redis.del("lock_id");
                return desc;
            }
        } else {
            Thread.sleep(200);
            return getProductDescById(id);
        }
    }
    return desc;
}

3.3 缓存雪崩解决方案

方案 1:设置缓存过期时间随机化

  • 避免大量缓存同时过期,可以在设定缓存时增加一个随机波动值。例如,原定 10 分钟 过期的缓存,可以随机增加 1~3 分钟,使过期时间分布在 7~13 分钟 之间。
redis.set(id, value, 60 * 60 + Math.random() * 1000);

方案 2:热点数据分片存储

  • 通过分布式缓存架构(如 Redis Cluster),将不同热点数据分布存储在不同的节点/机房,防止单点故障引发缓存雪崩。

方案 3:双层缓存机制(A/B 缓存)

  • 维护两层缓存:A 缓存(正常过期)B 缓存(不过期)。当 A 缓存失效时,读取 B 缓存,并异步更新 **A** 缓存,确保数据一致性。

4. 总结

问题定义触发条件影响解决方案
缓存穿透访问数据库中不存在的数据,导致所有请求直接打到数据库缓存和数据库都无数据数据库负载增加布隆过滤器、缓存空值
缓存击穿热点数据过期的瞬间,大量请求同时访问数据库高并发下,某个热点数据突然失效数据库瞬时压力飙升设置永不过期、互斥锁机制
缓存雪崩大量缓存数据同时过期,导致数据库负载剧增大量缓存数据设定相同的过期时间可能影响整个系统随机化过期时间、热点数据分片、双层缓存

缓存作为高并发架构中的重要组成部分,合理的缓存设计可以极大提升系统的性能,降低数据库负载。然而,面对缓存穿透、缓存击穿和缓存雪崩等问题,我们需要根据具体场景采取相应的防范措施,确保系统的稳定性和可靠性。