一、缓存穿透:*“查无此物”的攻击*

** **

**缓存穿透是指客户端请求的**数据在缓存中和数据库中都不存在**,这样缓存永远不会生效,*这些请求都会打到数据库*

img

** **

1.1 原因分析

  • 恶意请求:黑客暴力扫描不存在的ID(如-1、0等非法值)
  • 逻辑缺陷:业务未校验参数有效性,直接透传至数据库

** **

1.2 解决方案

方案一:布隆过滤器(Bloom Filter)

原理:预加载所有可能存在的数据哈希值到布隆过滤器中,查询时先判断数据是否存在。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 使用Guava实现布隆过滤器
BloomFilter<Long> bloomFilter = BloomFilter.create(
Funnels.longFunnel(),
1000000, // 预期元素量
0.01 // 误判率
);
// 预热数据
for (long id : validIds) {
bloomFilter.put(id);
}
// 查询拦截
public String getProduct(Long id) {
if (!bloomFilter.mightContain(id)) {
return "非法ID";
}
// 继续查询流程...
}

****✅ 优点**:内存占用少(1百万数据仅需1MB)
*❌ 缺点*:存在误判率(可配置)

方案二:空值缓存

原理:将查询结果为空的键也存入缓存,设置较短过期时间(如5分钟)。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public String getProduct(Long id) {    
String key = "product:" + id;
String value = redis.get(key);
if ("NULL".equals(value)) {
return "商品不存在"; // 命中空缓存
}
if (value == null) {
value = db.query(id);
if (value == null) {
redis.setex(key, 300, "NULL");
// 空值缓存5分钟
} else {
redis.setex(key, 3600, value);
}
} return value;
}

✅ 优点:实现简单
❌ 缺点:可能缓存大量无效Key

1.3 适用场景

  • 电商商品ID扫描攻击
  • 社交平台查询已注销用户

二、缓存雪崩:*“集体罢工”的灾难*

2.1 原因分析

  • 批量过期:大量Key设置相同TTL,同时失效
  • Redis宕机:集群故障导致所有请求压到数据库

2.2 解决方案

方案一:随机过期时间

1
2
3
4
5
6
7
public void setProductCache(Long id, Product product) {    
int baseTTL = 3600;
// 基础1小时
int randomTTL = baseTTL + new Random().nextInt(600);
// 增加0-10分钟随机值
redis.setex("product:" + id, randomTTL, product.toString());
}

✅ 优点:分散压力
❌ 缺点:无法应对Redis宕机

方案二:多级缓存架构

img

2.3 适用场景

  • 双11整点秒杀
  • 新闻APP热点事件推送

三、缓存击穿:*“顶流明星”的暴击*

3.1 原因分析

  • 热点Key失效:高并发访问的Key突然过期
  • 无保护机制:所有请求直接穿透到数据库

3.2 解决方案

方案一:分布式锁(Redisson实现)

原理:当缓存失效时,通过分布式锁控制仅一个线程重建缓存。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public String getHotNews(String newsId) {    
String key = "news:" + newsId;
String value = redis.get(key);
if (value == null) {
RLock lock = redisson.getLock(key + ":lock");
try {
if (lock.tryLock(3, 30, TimeUnit.SECONDS)) {
// 双重检查
value = redis.get(key);
if (value == null) {
value = db.query(newsId);
redis.setex(key, 600, value);
}
}
} finally {
lock.unlock();
}
} return value;
}

✅ 优点:强一致性
❌ 缺点:性能损耗约10%

**
**

方案二:逻辑过期时间

原理:缓存永不过期,但存储数据时附加逻辑过期时间。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 缓存数据包装类
@Datapublic class CacheWrapper {
private String data;
private Long expireTime; // 逻辑过期时间}
public String getHotNews(String newsId) {
CacheWrapper wrapper = redis.get(newsId);
if (wrapper == null) {
return loadAndCache(newsId);
}
if (wrapper.getExpireTime() < System.currentTimeMillis()) {
// 异步更新
threadPool.submit(() -> loadAndCache(newsId));
} return wrapper.getData();
}

✅ 优点:零等待时间
❌ 缺点:数据短暂不一致

3.3 适用场景

  • 微博顶流明星发帖
  • 小米手机秒杀活动

四、总结对比表

问题类型 触发条件 核心方案 性能影响 适用场景
穿透 查询不存在数据 布隆过滤器 防恶意攻击
雪崩 批量Key失效/Redis宕机 随机TTL+多级缓存 大促活动
击穿 单个热点Key失效 分布式锁+逻辑过期 秒杀/顶流事件

综合应用

img