> 技术文档 > Redis + SpringBoot:缓存穿透解决方案实战

Redis + SpringBoot:缓存穿透解决方案实战

技术栈: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 雪崩
Redis + SpringBoot:缓存穿透解决方案实战

二、六大解决方案深度实战

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@PostConstructpublic 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二级缓存@Beanpublic 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 结果对比
Redis + SpringBoot:缓存穿透解决方案实战

四、生产环境注意事项

4.1 布隆过滤器维护

// 数据变更时同步更新@Transactionalpublic 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