导言

在分布式系统中,Redis 作为高性能分布式缓存,可有效减轻数据库压力、提升接口响应速度,但高并发场景下,缓存雪崩缓存击穿缓存穿透三类异常易引发系统稳定性问题。

一、缓存击穿 —— 热点数据 “过期瞬间” 的单点突破

1. 核心定义

缓存击穿是指某一 “热点数据”(高并发访问的单一数据)的缓存过期失效瞬间,大量并发请求同时查询该数据,由于缓存已无对应数据,所有请求直接穿透至数据库,导致数据库针对该热点数据的查询压力骤增、甚至出现查询超时的现象。

核心特征:区别于缓存雪崩,其关键在于 “单个热点数据失效”,影响范围为 “局部”(仅该热点数据相关查询)。

2. 触发条件

缓存击穿的发生需同时满足两个条件:

  • 热点数据缓存过期:某一数据访问量极高(如每秒数千次查询),且其 Redis 缓存恰好过期;

  • 无预热机制的突发热点:突发高并发请求集中访问某一数据(如临时热门内容),而该数据未提前缓存,导致请求直接打向 DB。

3. 解决方案对比

解决方案 实现逻辑 优点 缺点 适用场景
互斥锁(分布式锁) 缓存失效时,仅允许一个请求获取锁并查询 DB,更新缓存;其他请求等待锁释放后,从缓存获取数据 保证数据一致性(同一时间仅一次 DB 查询);避免 DB 压力激增 存在锁竞争,请求会短暂等待(增加响应时间);需处理锁超时、死锁问题 对数据一致性要求高的热点场景
热点数据逻辑过期 不设置 Redis 物理过期时间,仅在数据中嵌入逻辑过期时间;业务层判断逻辑过期后,异步线程更新缓存,当前请求仍返回旧数据 无请求等待,响应速度快;避免 DB 瞬时压力 数据存在短暂不一致(更新完成前返回旧数据);需维护异步更新线程 对数据一致性要求低、追求响应速度的热点场景
热点数据永不过期 直接为热点数据设置 “永不过期”(不设置 Redis 过期时间),通过业务触发更新(如数据变更时主动更新缓存) 无过期风险,请求 100% 命中缓存;DB 无压力 需手动维护缓存更新(易遗漏);长期占用 Redis 内存 热点数据更新频率低、数据量小的场景

4. 代码示例(互斥锁・Java)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 1. 尝试获取分布式锁(以Redisson为例)
RLock lock = redissonClient.getLock("lock:hot:key:" + id);
try {
// 2. 缓存查询:命中则直接返回
Object data = redisTemplate.opsForValue().get("cache:hot:key:" + id);
if (data != null) {
return data;
}

// 3. 获取锁(等待10秒,锁过期30秒)
boolean isLocked = lock.tryLock(10, 30, TimeUnit.SECONDS);
if (isLocked) {
// 4. 锁内查询DB,更新缓存
Object dbData = dbMapper.selectById(id);
redisTemplate.opsForValue().set("cache:hot:key:" + id, dbData, 30, TimeUnit.MINUTES);
return dbData;
} else {
// 5. 未获取锁,重试(或返回默认值)
Thread.sleep(50);
return getHotData(id); // 递归重试
}
} finally {
// 6. 释放锁(仅持有锁的线程可释放)
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}

5. 关键监控指标

  • 单 key 访问量:热点数据的 Redis key 每秒访问量≥1000 次,需重点监控;

  • 热点 key 缓存命中率:正常应≥99%,击穿时会瞬间降至 0%;

  • 热点 key 对应 DB 查询量:击穿时,该数据的 DB 查询量会从基线 0(或极低)飙升至与 Redis 访问量持平;

  • 锁竞争次数:若使用互斥锁,锁等待次数≥100 次 / 秒,需优化锁策略。