一、先给缓存雪崩定个性:啥是 Redis 缓存雪崩?
你可以把 Redis 缓存想象成超市的自助收银台 ,数据库就是超市的人工收银台 。正常情况下,用户(请求)都走自助收银台(缓存),人工收银台(数据库)压力很小;而缓存雪崩,就是 "所有自助收银台同时坏了"------ 大量缓存数据在同一时间失效,或者 Redis 服务直接挂了,导致所有请求瞬间全冲到数据库上,数据库扛不住这么大压力,直接崩溃,整个系统就瘫痪了。
举个最常见的例子:你给一批商品缓存设置了相同的过期时间(比如都是凌晨 1 点过期),到了 1 点,这批缓存全失效,刚好凌晨 1 点又有一波用户访问,所有请求都去查数据库,数据库直接被打垮。
二、为啥会发生缓存雪崩?(两大核心原因)
-
缓存批量失效(最常见)
- 人为设置了统一的过期时间:比如做活动时,给所有活动商品缓存设了 2 小时过期,2 小时后缓存全失效;
- 缓存 Key 的过期时间 "扎堆":比如用时间戳 + 固定时长设置过期,导致大量 Key 在同一时间段过期。
-
Redis 服务整体挂了
- Redis 集群宕机(比如服务器断电、网络故障、Redis 进程崩溃);
- 突发流量打垮 Redis(比如秒杀活动,请求量远超 Redis 处理能力)。
三、怎么解决缓存雪崩?(从易到难,新手也能落地)
1. 先解决 "缓存批量失效" 的问题
-
过期时间加随机值(最简单有效)给每个 Key 的过期时间都加一个随机数,避免 "扎堆失效"。比如原本要设 2 小时过期,就改成 "2 小时 ± 10 分钟",让缓存失效时间分散开。代码示例(Java):
java
运行
// 原过期时间:7200秒(2小时) int baseExpire = 7200; // 随机加0-600秒(10分钟) int randomExpire = new Random().nextInt(600); // 最终过期时间:7200~7800秒 redisTemplate.opsForValue().set("goods:1001", goodsInfo, baseExpire + randomExpire, TimeUnit.SECONDS); -
核心数据永不过期对系统核心、访问量极高的数据(比如首页轮播图、热门商品),不设置过期时间,由程序手动更新缓存(比如商品价格变了,再更新缓存),避免这些数据失效。
2. 解决 "Redis 挂了" 的问题
-
Redis 集群化部署(高可用)不要只部署一台 Redis,用主从复制 + 哨兵模式,或者 Redis Cluster 集群。就算一台 Redis 挂了,其他节点能立刻顶上,不会导致缓存服务整体不可用。
-
添加缓存降级 / 熔断机制当 Redis 挂了,不是直接把所有请求丢给数据库,而是做 "降级处理":
- 比如返回兜底数据("当前系统繁忙,请稍后再试");
- 用熔断器(比如 Sentinel)限制访问数据库的请求量,避免数据库被冲垮。
3. 终极兜底:保护数据库
- 数据库加读写分离 / 分库分表:提升数据库本身的抗压力;
- 加分布式锁 / 限流:限制同一时间访问数据库的请求数,比如用 Guava 的 RateLimiter 做接口限流;
- 开启缓存预热:在流量高峰前(比如秒杀开始前),提前把热点数据加载到缓存里,避免高峰时缓存为空。
四、缓存雪崩 vs 缓存击穿 vs 缓存穿透(新手别搞混)
很多新手会把这三个概念弄混,简单区分一下:
| 问题 | 核心场景 | 影响范围 |
|---|---|---|
| 缓存雪崩 | 大量缓存同时失效 / Redis 挂了 | 整个系统(数据库崩溃) |
| 缓存击穿 | 单个热点 Key 失效(比如秒杀商品) | 单个 Key 对应的数据库请求 |
| 缓存穿透 | 请求不存在的 Key(比如查 ID=-1 的商品) | 直接穿透缓存到数据库 |
总结
- 缓存雪崩的核心:大量缓存同时失效 / Redis 宕机,导致请求全冲数据库,系统瘫痪;
- 预防关键:过期时间加随机值分散失效、Redis 集群保证高可用、给数据库加限流 / 降级兜底;
- 新手优先落地:先给过期时间加随机值(成本最低),再部署 Redis 主从集群(提升可用性)。