Redis 缓存用得好,系统性能翻倍;用不好,缓存雪崩、穿透、击穿三连暴击。本文总结了三种经典缓存策略和它们各自的适用场景与坑点。
1
应用请求数据,先查 Redis 缓存
GET user:1001
2
缓存命中 → 直接返回(跳过数据库)
HIT → return
3
缓存未命中 → 查数据库
MISS → SELECT * FROM users
4
将结果写入缓存(设置 TTL)
SET user:1001 ... EX 3600
5
返回数据给应用
return data
一、Cache-Aside(旁路缓存)
最常用的策略。应用自己管理缓存的读写逻辑。
async function getUser(id) {
// 1. 先查缓存
const cached = await redis.get(`user:${id}`);
if (cached) return JSON.parse(cached);
// 2. 查数据库
const user = await db.query('SELECT * FROM users WHERE id = ?', [id]);
// 3. 写入缓存(TTL 1小时)
if (user) {
await redis.set(`user:${id}`, JSON.stringify(user), 'EX', 3600);
}
return user;
}
二、三大经典问题
2.1 缓存穿透
查询一个根本不存在的数据,缓存和数据库都查不到,每次请求都打到数据库。
🚨 场景:攻击者用随机 ID 批量请求
GET /api/user/{random},所有请求穿透缓存直达数据库,直接打挂。
解决方案:缓存空值 + 布隆过滤器。
async function getUserSafe(id) {
const cached = await redis.get(`user:${id}`);
// 命中缓存(包括空值标记)
if (cached !== null) {
return cached === '__NULL__' ? null : JSON.parse(cached);
}
const user = await db.query('SELECT * FROM users WHERE id = ?', [id]);
if (user) {
await redis.set(`user:${id}`, JSON.stringify(user), 'EX', 3600);
} else {
// 缓存空值,短 TTL 防穿透
await redis.set(`user:${id}`, '__NULL__', 'EX', 60);
}
return user;
}
2.2 缓存击穿
某个热点 key 过期的瞬间,大量并发请求同时打到数据库。
解决方案:互斥锁(分布式锁)。
async function getHotData(key) {
let data = await redis.get(key);
if (data) return JSON.parse(data);
// 尝试获取锁
const lockKey = `lock:${key}`;
const locked = await redis.set(lockKey, 1, 'NX', 'EX', 10);
if (locked) {
// 拿到锁,查数据库并回填
data = await queryDb(key);
await redis.set(key, JSON.stringify(data), 'EX', 3600);
await redis.del(lockKey);
return data;
} else {
// 没拿到锁,等一下重试
await sleep(100);
return getHotData(key);
}
}
2.3 缓存雪崩
大量 key 同时过期,或 Redis 宕机,导致所有请求同时打到数据库。
解决方案:TTL 加随机偏移 + 多级缓存 + 熔断降级。
// TTL 加随机偏移,避免同时过期
const baseTTL = 3600;
const jitter = Math.floor(Math.random() * 600);
await redis.set(key, value, 'EX', baseTTL + jitter);
三、策略选择指南
- 读多写少(用户信息、配置)→ Cache-Aside
- 写频繁(计数器、状态更新)→ Write-Through
- 允许短暂不一致(Feed 流、排行榜)→ Write-Behind
四、总结
- 缓存穿透 → 缓存空值,简单有效
- 缓存击穿 → 分布式锁,保证单线程回源
- 缓存雪崩 → TTL 随机偏移 + 熔断降级
- 永远给缓存设置 TTL,没有永不过期的缓存