🚀 引言
在高并发系统中,Redis 作为缓存层,大幅度提升了查询速度,减轻了数据库压力。但是,在特殊情况下,Redis 可能会失效,导致大量请求直接打到数据库,造成系统崩溃。
本篇文章将深入解析缓存穿透、缓存击穿和缓存雪崩的问题,并提供实际解决方案和代码示例,助你打造高可用的缓存架构。
🔥 1. 常见缓存问题
📌 1.1 缓存穿透
现象:查询一个数据库中不存在的数据,导致请求绕过缓存直接访问数据库,形成 数据库级 DDoS。
示例:用户请求 id=-1 或 id=99999999 的数据,而数据库中并没有该记录。
解决方案:
- 布隆过滤器(Bloom Filter)
- 缓存空值
// 使用 Guava 创建布隆过滤器,存储数据库中已有的 key
BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), 1000000);
if (!bloomFilter.mightContain(id)) {
return null; // 直接返回,避免查询数据库
}
📌 1.2 缓存击穿
现象:某个热点 key 过期的瞬间,大量请求涌入,导致数据库瞬时压力巨大。
示例:某明星的微博详情页数据,缓存过期后,短时间内数百万请求直接打到数据库。
解决方案:
- 设置热点数据永不过期
- 加互斥锁
- 异步重建缓存
// Redis 分布式锁
String lockKey = "lock:product:" + productId;
boolean lock = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
if (!lock) {
return "系统繁忙,请稍后再试"; // 只允许一个线程更新缓存
}
try {
Product product = db.getProductById(productId); // 查询数据库
redisTemplate.opsForValue().set("product:" + productId, product, 30, TimeUnit.MINUTES);
} finally {
redisTemplate.delete(lockKey); // 释放锁
}
📌 1.3 缓存雪崩
现象:大量缓存数据同时失效,导致数据库瞬间扛不住,系统崩溃。
示例:凌晨 0 点,所有缓存数据统一过期,导致数据库负载飙升。
解决方案:
- 设置缓存随机过期时间,避免同时失效
- Redis 主从架构 + 哨兵
- 限流 + 降级
// 设置随机过期时间,防止缓存同时失效
int expireTime = 60 * 60 + new Random().nextInt(300); // 60 分钟 + 0~5 分钟随机时间
redisTemplate.opsForValue().set("user:" + userId, user, expireTime, TimeUnit.SECONDS);
🛠 2. 代码实战:Spring Boot + Redis
完整实现:
@RestController
@RequestMapping("/cache")
public class CacheController {
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private ProductService productService;
private static final String LOCK_PREFIX = "lock:product:";
@GetMapping("/product/{id}")
public Product getProduct(@PathVariable("id") Integer id) {
String key = "product:" + id;
// 先查询 Redis
String productJson = redisTemplate.opsForValue().get(key);
if (productJson != null) {
return JSON.parseObject(productJson, Product.class);
}
// 加锁防止缓存击穿
String lockKey = LOCK_PREFIX + id;
boolean isLock = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
if (!isLock) {
return null; // 其他线程正在更新缓存
}
try {
// 查询数据库
Product product = productService.getProductById(id);
if (product == null) {
redisTemplate.opsForValue().set(key, "", 60, TimeUnit.SECONDS); // 缓存空值
return null;
}
// 设置缓存,并加随机过期时间
int expireTime = 3600 + new Random().nextInt(300);
redisTemplate.opsForValue().set(key, JSON.toJSONString(product), expireTime, TimeUnit.SECONDS);
return product;
} finally {
redisTemplate.delete(lockKey); // 释放锁
}
}
}
🎯 3. 总结
问题 | 现象 | 解决方案 |
---|---|---|
缓存穿透 | 查询不存在的数据 | 布隆过滤器、缓存空值 |
缓存击穿 | 热点 key 过期后瞬时请求量大 | 分布式锁、预加载 |
缓存雪崩 | 大量 key 同时失效 | 随机过期时间、限流 |
📢 4. 进阶提升方向
✅ Redis Cluster 高可用架构实战
✅ 分布式锁的实现方式对比(Redis、ZooKeeper、Redisson)
✅ Spring Cloud + Redis 限流与熔断策略
📌 如果你觉得文章有帮助,欢迎点赞👍 + 关注,后续会持续分享高并发架构实战! 🚀🚀🚀