Redis 缓存击穿与雪崩防护
时间:2026-11-14 07:18 作者:emer 分类: 无
Redis 缓存击穿与雪崩防护
一、三个高频缓存故障概念
缓存是高并发系统的第一道防线,但使用不当反而会把压力全部打回数据库。日常最容易混淆三个概念:缓存穿透、缓存击穿、缓存雪崩。穿透是查根本不存在的数据,请求每次都落到数据库;击穿是某个热点 key 突然过期,大量并发同时回源;雪崩是大量 key 在同一时间集中过期,数据库瞬间被压垮。三者触发条件不同,防护手段也不一样。
二、缓存击穿:热点 key 过期
某个被频繁访问的热点数据(如首页推荐、爆款商品)缓存到期,此刻成千上万请求同时发现缓存为空,一起去查数据库,瞬间把数据库打满。
解决方案一:互斥锁重建
第一个发现缓存失效的请求拿锁去查库重建,其余请求等待或短暂返回旧值:
$key = 'hot:goods:1001';
$data = $redis->get($key);
if ($data === false) {
// 尝试获取重建锁,过期时间 10 秒防止死锁
if ($redis->set('lock:' . $key, 1, ['NX', 'EX' => 10])) {
$data = $db->query('...'); // 查库
$redis->setex($key, 300, $data);
$redis->del('lock:' . $key);
} else {
usleep(50000); // 等待后重试读缓存
$data = $redis->get($key);
}
}
解决方案二:热点 key 永不过期
对真正的热点数据不设 TTL,由后台异步定时刷新,物理上不过期、逻辑上更新,从根本上避免到期瞬间回源。
三、缓存雪崩:批量集中失效
大量 key 相同时间过期,或者 Redis 实例整体宕机,所有请求同时压向数据库。
方案一:过期时间加随机偏移
不要给一批 key 设置相同 TTL,在基础时间上叠加随机值,让失效时间错开:
$ttl = 300 + random_int(0, 120); // 300~420 秒随机
$redis->setex($key, $ttl, $data);
方案二:多级缓存与降级
本地缓存(如 PHP 的 APCu、进程内数组)作为二级兜底,Redis 失效时先读本地缓存;数据库侧配置限流、熔断,压力过大时返回兜底页面,保证核心服务不被拖垮。
方案三:集群高可用
使用 Redis 哨兵或 Cluster 保证单点故障不影响整体,配合持久化 RDB/AOF 快速恢复,避免整实例宕机引发雪崩。
四、缓存穿透补充防护
不存在的数据也要缓存空值,并设置较短 TTL,同时在入口层做参数校验和布隆过滤器,拦截明显非法的查询,避免无效请求直达数据库。
五、常见误区
- 把击穿和雪崩混为一谈,前者是单个热点 key,后者是批量或整体;
- 互斥锁不设过期时间,持锁进程崩溃造成死锁;
- 所有 key 统一 TTL,高峰期同时失效;
- 只加缓存不做降级兜底,数据库一被压垮全站瘫痪。
六、总结
缓存防护核心思路:热点 key 用互斥锁或永不过期防击穿,批量 key 加随机过期时间防雪崩,不存在的数据用空值缓存和布隆过滤器防穿透。再配合本地多级缓存、限流熔断和 Redis 高可用,才能让缓存真正成为数据库的缓冲,而不是故障放大器。