导读:在电商大促期间,某平台因缓存穿透导致数据库崩溃,直接损失超千万。本文通过复现事故场景,剖析7种缓存穿透解决方案的底层原理。无论面试还是实战,这篇文章都将成为你的终极防穿透指南。
一、穿透场景复现与问题诊断
1.1 高并发穿透场景模拟
// 危险代码示例:未做防护的查询方法
public Product getProduct(String id) {
String cacheKey = "product:" + id;
Product product = redisTemplate.opsForValue().get(cacheKey);
if (product != null) {
return product;
}
product = productMapper.selectById(id); // 直接查库
if (product != null) {
redisTemplate.opsForValue().set(cacheKey, product, 30, TimeUnit.MINUTES);
}
return product;
}
漏洞分析:恶意攻击者通过构造随机ID(如UUID)发起海量查询,所有请求穿透Redis直达MySQL。当QPS达到2万时,数据库连接池瞬间耗尽。
二、7大解决方案深度解析
2.1 布隆过滤器(Bloom Filter)
// 初始化布隆过滤器(Redisson实现)
RBloomFilter<String> bloomFilter = redisson.getBloomFilter("product_filter");
bloomFilter.tryInit(1000000L, 0.01); // 百万数据量,1%误判率
// 查询时优先校验
public Product getProductWithBloom(String id) {
if (!bloomFilter.contains(id)) {
return null; // 快速拦截非法请求
}
// ...原有查询逻辑
}
核心参数:
- 位数组大小:
-n*ln(p)/(ln2)^2
(n=元素数量,p=误判率) - 哈希函数数量:
k = m/n * ln2
2.2 缓存空对象(Cache Null)
public Product getProductWithNullCache(String id) {
String cacheKey = "product:" + id;
Product product = redisTemplate.opsForValue().get(cacheKey);
if (product != null) {
return product instanceof NullProduct ? null : product;
}
product = productMapper.selectById(id);
if (product != null) {
redisTemplate.opsForValue().set(cacheKey, product, 30, TimeUnit.MINUTES);
} else {
redisTemplate.opsForValue().set(cacheKey, new NullProduct(), 2, TimeUnit.MINUTES); // 短时间缓存空值
}
return product;
}
注意事项:
<