Redis 缓存三大问题实战:穿透、击穿、雪崩
📅 2026/7/11 5:55:56
👁️ 阅读次数
📝 编程学习
日期: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。
编程学习
技术分享
实战经验