缓存穿透、缓存击穿、缓存雪崩及解决方案

1. 缓存穿透(Cache Penetration)

问题描述

  • 定义:大量请求查询数据库中不存在的数据,绕过缓存直接访问数据库。
  • 原因:恶意攻击(如随机伪造 ID)或业务逻辑缺陷(如查询未校验的参数)。
  • 影响:数据库压力骤增,可能引发宕机。

解决方案

方案实现方式优缺点
布隆过滤器(Bloom Filter)在缓存层前加布隆过滤器,预先存储所有可能存在的 key,拦截非法请求。- 优点:高效拦截无效请求。
- 缺点:存在误判率,需定期更新过滤器数据。
缓存空值(Cache Null)对数据库中不存在的 key,缓存一个空值(如 NULL),并设置较短的过期时间。- 优点:简单易实现。
- 缺点:可能缓存大量无效 key,占用内存空间。
请求参数校验在业务层对请求参数进行合法性校验(如 ID 范围、格式)。- 优点:直接过滤非法请求。
- 缺点:无法完全覆盖所有非法参数场景。

2. 缓存击穿(Cache Breakdown)

问题描述

  • 定义:某个热点 key 突然过期,大量并发请求直接击穿缓存,访问数据库。
  • 原因:高并发场景下,热点 key 过期后重建缓存耗时较长。
  • 影响:数据库瞬时压力极大,可能导致服务雪崩。

解决方案

方案实现方式优缺点
互斥锁(Mutex Lock)使用分布式锁(如 Redis 的 SETNX),保证同一时间只有一个线程重建缓存。- 优点:避免并发重建。
- 缺点:锁竞争可能增加延迟,需处理锁超时问题。
逻辑过期(Logical Expire)缓存数据不设置物理过期时间,而是存储逻辑过期时间,由后台异步更新缓存。- 优点:缓存永不过期,避免击穿。
- 缺点:需维护异步更新逻辑,复杂性高。
热点数据永不过期对极热点 key 设置为永不过期,通过定时任务或事件驱动更新缓存。- 优点:彻底避免击穿。
- 缺点:数据更新延迟,需额外维护更新逻辑。

3. 缓存雪崩(Cache Avalanche)

问题描述

  • 定义:大量缓存 key 同时过期缓存服务宕机,导致请求直接访问数据库。
  • 原因:缓存 key 过期时间集中,或 Redis 集群故障。
  • 影响:数据库压力过大,可能引发级联故障。

解决方案

方案实现方式优缺点
随机过期时间为每个 key 的过期时间添加随机值(如基础时间 + 随机分钟),避免集中过期。- 优点:简单有效。
- 缺点:无法完全避免部分 key 同时过期。
多级缓存架构结合本地缓存(如 Caffeine)和分布式缓存(如 Redis),分散缓存失效风险。- 优点:提高系统容错性。
- 缺点:架构复杂性增加,数据一致性需处理。
熔断降级机制使用熔断器(如 Hystrix)或限流工具(如 Sentinel),在缓存失效时降级处理请求。- 优点:保护数据库不被压垮。
- 缺点:可能影响用户体验。
集群高可用部署 Redis 集群(如哨兵模式、Cluster 模式),确保缓存服务的高可用性。- 优点:避免单点故障。
- 缺点:运维成本增加。

4. 综合方案与最佳实践

防御组合

  • 穿透:布隆过滤器 + 缓存空值 + 参数校验。
  • 击穿:互斥锁 + 逻辑过期 + 热点数据预热。
  • 雪崩:随机过期时间 + 多级缓存 + 熔断降级。

代码示例一(Redis 实现互斥锁)

public String getData(String key) {
    String value = redis.get(key);
    if (value == null) {
        if (redis.setnx(key + ":lock", "1", 10)) { // 获取分布式锁
            try {
                value = db.query(key);           // 查询数据库
                redis.setex(key, 3600, value);    // 写入缓存
            } finally {
                redis.del(key + ":lock");         // 释放锁
            }
        } else {
            Thread.sleep(100);                   // 等待后重试
            return getData(key);
        }
    }
    return value;
}

代码示例二(Redisson 实现互斥锁)

public String getData(String key) {
    RLock lock = redissonClient.getLock(LOCK_KEY);
    lock.lock();
    try {
        ······
    } finally {
        if (lock.isLocked() && lock.isHeldByCurrentThread()) {
            lock.unlock();
        }
    }
    return value;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值