Redis 缓存三大问题实战:穿透、击穿、雪崩

📅 2026/7/11 5:55:56 👁️ 阅读次数 📝 编程学习
Redis 缓存三大问题实战:穿透、击穿、雪崩

日期:2025-08-15 | 作者:枝莺 | 分类:后端开发

问题背景

暑假在开发记者团设备管理小程序时,发现某些时间段接口响应特别慢。排查后发现是 Redis 缓存使用不当导致的。今天系统整理下缓存三大经典问题。

1. 缓存穿透

现象:查询一个根本不存在的数据,缓存中没有,每次请求都打到数据库。

用户查 id=99999(不存在) → 缓存没有 → 查数据库 → 数据库也没有 → 缓存不写 → 下次再来,继续打数据库

解决方案

// 方案1: 缓存空值(简单有效) public Device getDevice(Long id) { String key = "device:" + id; Device device = (Device) redisTemplate.opsForValue().get(key); if (device != null) return device; // 检查是否是缓存空值标记 String nullMark = (String) redisTemplate.opsForValue().get("device:null:" + id); if ("NULL".equals(nullMark)) return null; device = deviceMapper.selectById(id); if (device != null) { redisTemplate.opsForValue().set(key, device, 30, TimeUnit.MINUTES); } else { // 缓存空值,短期过期 redisTemplate.opsForValue().set("device:null:" + id, "NULL", 3, TimeUnit.MINUTES); } return device; }
// 方案2: 布隆过滤器(更优雅) @Configuration public class BloomFilterConfig { @Bean public BloomFilter<Long> deviceBloomFilter(DeviceMapper mapper) { List<Long> allIds = mapper.selectAllIds(); BloomFilter<Long> filter = BloomFilter.create( Funnels.longFunnel(), allIds.size(), 0.01); allIds.forEach(filter::put); return filter; } }

2. 缓存击穿

现象:热点key在过期瞬间,大量并发请求同时打到数据库。

热点key "device:hot:100" 过期 → 瞬间1000个请求同时查DB → 数据库压力暴增

解决方案

// 互斥锁(最常用) public Device getHotDevice(Long id) { String key = "device:hot:" + id; Device device = (Device) redisTemplate.opsForValue().get(key); if (device != null) return device; // 加锁,只有一个请求去查DB String lockKey = "lock:device:" + id; try { if (redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS)) { // 双重检查 device = (Device) redisTemplate.opsForValue().get(key); if (device != null) return device; device = deviceMapper.selectById(id); redisTemplate.opsForValue().set(key, device, 30, TimeUnit.MINUTES); return device; } else { // 没拿到锁,等一会儿重试 Thread.sleep(100); return getHotDevice(id); // 递归重试 } } finally { redisTemplate.delete(lockKey); } }

3. 缓存雪崩

现象:大量key在同一时间过期,或者Redis挂了。

解决方案

  • 过期时间加随机值EXPIRE + random(0~300)秒

  • 多级缓存:本地Caffeine + Redis

  • Redis 高可用:哨兵/集群

// 过期时间加随机 int expireTime = 1800 + ThreadLocalRandom.current().nextInt(300); redisTemplate.opsForValue().set(key, value, expireTime, TimeUnit.SECONDS);

总结

问题根本原因核心方案
穿透查不存在的数据布隆过滤器
击穿热点key过期互斥锁
雪崩大量key同时过期过期时间随机化

这些方案在我自己的项目里全部落地了,实际效果很好。特别是互斥锁方案,把热点数据查询的DB压力从峰值1000QPS降到了1QPS。