技术栈:Spring Boot 2.7 + Redis 6 + Redisson 3.17
压力测试:JMeter模拟10万QPS场景下的性能对比
一、缓存穿透的致命危害
1.1 典型事故案例
某电商平台遭遇恶意攻击,攻击者持续请求不存在的商品ID:
数据库QPS:从500激增至12万
CPU负载:达到800%
恢复时间:38分钟(手动介入后)
1.2 穿透 vs 击穿 vs 雪崩
二、六大解决方案深度实战
2.1 方案一:空对象缓存(基础防御)
实现代码:
@Cacheable(value = "products", key = "#id",
unless = "#result == null") // 不缓存null结果
public Product getProduct(Long id) {
Product product = productDao.findById(id);
if (product == null) {
// 缓存空对象(设置较短TTL)
redisTemplate.opsForValue().set(
"product:null:" + id,
new NullValue(),
5, TimeUnit.MINUTES);
}
return product;
}
优化技巧:
使用特殊标记对象(如NullValue)而非字符串"null"
动态调整空对象TTL:夜间时段延长至30分钟
2.2 方案二:布隆过滤器(终极防御)
Guava实现:
// 初始化布隆过滤器(预期元素量100万,误判率1%)
private static final BloomFilter<Long> productBloomFilter =
BloomFilter.create(Funnels.longFunnel(), 1_000_000, 0.01);
// 启动时加载所有有效ID
@PostConstruct
public void initBloomFilter() {
productDao.findAllIds().forEach(productBloomFilter::put);
}
// 查询拦截
public Product getProductWithBloom(Long id) {
if (!productBloomFilter.mightContain(id)) {
throw new IllegalArgumentException("商品不存在");
}
return getProduct(id); // 走正常缓存逻辑
}
Redisson分布式布隆过滤器:
RBloomFilter<Long> filter = redissonClient.getBloomFilter("productFilter");
filter.tryInit(1_000_000L, 0.01); // 分布式环境下使用
2.3 方案三:互斥锁重建(防击穿)
public Product getProductWithLock(Long id) {
String lockKey = "product:lock:" + id;
try {
// 尝试获取分布式锁
boolean locked = redissonClient.getLock(lockKey).tryLock(3, 10, TimeUnit.SECONDS);
if (locked) {
// 双重检查
Product product = getFromCache(id);
if (product == null) {
product = loadFromDB(id);
updateCache(id, product);
}
return product;
}
// 未获取锁则短暂休眠后重试
Thread.sleep(50);
return getProductWithLock(id);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("获取商品信息中断");
}
}
2.4 方案四:缓存预热(防雪崩)
@Scheduled(cron = "0 0 3 * * ?") // 每天凌晨3点执行
public void preloadHotProducts() {
List<Long> hotIds = productDao.findHotProductIds(1000);
hotIds.parallelStream().forEach(id -> {
Product product = productDao.findById(id);
redisTemplate.opsForValue().set(
"product:" + id,
product,
12 + ThreadLocalRandom.current().nextInt(6), // 12-18小时随机过期
TimeUnit.HOURS);
});
}
2.5 方案五:多级缓存架构
// Caffeine本地缓存 + Redis二级缓存
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
return new CaffeineRedisCacheManager(
Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(5, TimeUnit.MINUTES),
RedisCacheWriter.nonLockingRedisCacheWriter(factory),
RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(1))
);
}
2.6 方案六:弹性过期时间
// 动态TTL策略
public void setWithRandomTtl(String key, Object value) {
int baseTtl = 3600; // 基础1小时
int randomTtl = ThreadLocalRandom.current().nextInt(600); // 0-10分钟随机值
redisTemplate.opsForValue().set(
key,
value,
baseTtl + randomTtl,
TimeUnit.SECONDS);
}
三、性能压测对比
3.1 测试环境
硬件:4核8G云服务器
数据量:100万商品数据
攻击模式:90%请求为不存在的随机ID
3.2 结果对比
四、生产环境注意事项
4.1 布隆过滤器维护
// 数据变更时同步更新
@Transactional
public void updateProduct(Product product) {
productDao.update(product);
if (productBloomFilter != null) {
productBloomFilter.put(product.getId());
}
}
4.2 监控指标配置
management:
metrics:
export:
redis:
enabled: true
distribution:
percentiles:
redis.command.execution: 0.5,0.95,0.99
五、终极解决方案组合
推荐架构:
前端:请求参数基础校验(如ID范围检查)
网关层:频率限制+黑名单过滤
服务层:布隆过滤器+空对象缓存
存储层:多级缓存+弹性TTL