Redis中缓存的穿透,击穿,雪崩

splend21 Lv1

1. 缓存穿透

现象: 指查询一个根本不存在的数据。缓存中没有,数据库中也没有。由于数据库里查不到,也就没法写回缓存,导致每次请求这个“不存在”的数据都要去冲击数据库。

  • 诱因: 恶意攻击(故意查询不存在的 ID)或业务逻辑失误。
  • 后果: 数据库压力激增,甚至崩溃。

解决方案:

  • 布隆过滤器 (Bloom Filter): 在缓存之前增加一个屏障,记录所有可能存在的 Key。如果布隆过滤器判断 Key 不存在,直接返回,不查询数据库。
  • 缓存空对象: 如果数据库返回空,也把这个 null 结果写进缓存,但设置一个较短的过期时间(如 5 分钟),防止同一 Key 持续攻击。

2. 缓存击穿

现象: 指一个热点 Key(比如微博热搜、秒杀商品)在过期的瞬间,有海量请求同时涌入。

这个 Key 此时在缓存中失效了,所有请求会同时打到数据库去尝试加载数据并回写缓存。

  • 特点: 针对“单一”热点数据。
  • 后果: 数据库瞬时负载过高,甚至卡死。

解决方案:

  • 设置逻辑过期: 不给 Key 设置物理过期时间,而在 Value 中存储一个过期字段。发现逻辑过期后,后台异步更新。
  • 互斥锁 (Mutex Lock): 只有获得锁的那个线程能去查数据库并写缓存,其他线程等待或重试,确保数据库只被查一次。

3. 缓存雪崩

现象: 指在同一时段,大量的缓存 Key 同时失效,或者 Redis 服务宕机。

原本由缓存承担的请求全部涌向数据库。

  • 区别: 击穿是“点”,雪崩是“面”。
  • 后果: 数据库发生连锁反应,整个系统直接瘫痪。

解决方案:

  • 过期时间加随机偏移: 为每个 Key 的过期时间加上一个随机数(如 1-5 分钟),防止 Key 集体同时到期。
  • 搭建高可用集群: 使用 Redis Sentinel(哨兵)或 Redis Cluster 避免单点故障。
  • 熔断与限流: 如果数据库压力过大,直接触发熔断,返回友好提示,保护底层数据库不被压垮。

4.布隆过滤器

专门用于判断一个元素是否在一个集合中。用极小的内存,换取极快的查询速度,但是会牺牲一点准确性。

在解决缓存击穿的问题中,布隆过滤器位于请求到达Redis之前,对于key查得到就放行,否则直接返回

布隆过滤器的底层是一个 位数组(初始全为0) 和一组哈希函数。

写入过程:

  1. 当一个 Key(如 user:123)进入时,会通过 k 个不同的哈希函数计算出 k 个哈希值。
  2. 将位数组中这 k 个哈希值对应位置的 0 改为 1

查询过程:

  1. 对要查询的 Key 进行同样的 k 次哈希计算。
  2. 查看位数组中对应的 k 个位置。
  3. 结果判定:
    • 只要有任意一个位置是 0,说明该 Key 肯定没被存过
    • 如果所有位置都是 1,说明该 Key 可能被存过
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;

@Service
public class BloomFilterService {

@Autowired
private RedissonClient redissonClient;

private RBloomFilter<String> userBloomFilter;

@PostConstruct
public void init() {
// 1. 获取布隆过滤器实例
userBloomFilter = redissonClient.getBloomFilter("user-exists-filter");

// 2. 初始化布隆过滤器
// 参数1:预期插入量 (expectedInsertions)
// 参数2:期望误判率 (falseProbability),通常设为 0.01 或 0.03
userBloomFilter.tryInit(1000000L, 0.03);
}

/**
* 向布隆过滤器添加数据
*/
public void addUserId(String userId) {
userBloomFilter.add(userId);
}

/**
* 判断数据是否存在
*/
public boolean userIdExists(String userId) {
return userBloomFilter.contains(userId);
}
}

核心区别对比

问题 触发原因 涉及数据 重点对策
穿透 数据压根不存在 非法/不存在的数据 布隆过滤器、缓存空值
击穿 热点 Key 到期 单个极热点数据 互斥锁、逻辑不过期
雪崩 批量 Key 到期或服务掉线 广范围的数据 随机过期时间、高可用集群
Comments
On this page
Redis中缓存的穿透,击穿,雪崩