缓存穿透、击穿、雪崩与项目里 Redis 的用法
Redis 面几乎必问三个「击」字辈名词,还会问「你们项目 Redis 干啥」。我按现象 → 原因 → 方案 → 自己项目用法记,避免只背定义。
题目
- 缓存穿透、缓存击穿、缓存雪崩分别是什么?怎么解决?
- 在项目里你用 Redis 做什么?
参考答案(口述要点)
- 穿透:查不存在的数据,缓存和 DB 都没有,请求一直打 DB → 布隆过滤器、缓存空值、接口校验
- 击穿:热点 key 过期瞬间,大量请求打 DB → 互斥重建、逻辑过期、热点 key 不过期
- 雪崩:大量 key 同时过期或 Redis 宕机 → 过期时间加随机、集群高可用、限流降级
- 项目:登录 token/session、验证码、接口限流、热点数据缓存、分布式锁、排行榜等
详细解析
| 问题 | 现象 | 常见方案 |
|---|---|---|
| 穿透 | id=-1 反复查,缓存永不命中 | 布隆过滤器;非法 id 直接挡;空值缓存短 TTL |
| 击穿 | 秒杀商品缓存过期,瞬时 DB 压力 | 单线程重建(setnx);逻辑过期异步刷新 |
| 雪崩 | 凌晨批量过期 / Redis 全挂 | TTL 打散;多级缓存;熔断降级 |
击穿:互斥重建示例(概念代码)
public Product getProduct(Long id) {
String key = "product:" + id;
String json = redis.get(key);
if (json != null) {
if ("NULL".equals(json)) return null;
return parse(json);
}
String lockKey = "lock:product:" + id;
if (redis.set(lockKey, "1", "NX", "EX", 10)) {
try {
json = redis.get(key);
if (json != null) return "NULL".equals(json) ? null : parse(json);
Product p = productMapper.selectById(id);
redis.setex(key, 3600, p == null ? "NULL" : toJson(p));
return p;
} finally {
redis.del(lockKey);
}
} else {
Thread.sleep(50);
return getProduct(id);
}
}代码与实践:项目里 Redis 做什么
1. 用户登录 / Token(最常见)
// 登录成功
String token = UUID.randomUUID().toString();
redis.setex("login:token:" + token, 7200, userId.toString());
// 网关或拦截器里校验
String uid = redis.get("login:token:" + token);可配合 刷新续期:快过期时滑动窗口延长 TTL;登出则 del。
2. 验证码、防刷
redis.setex("captcha:" + mobile, 300, code);
// 校验后删除,防止重复使用3. 接口限流
Long n = redis.incr("rate:" + userId + ":" + minute);
if (n == 1) redis.expire("rate:" + userId + ":" + minute, 60);
if (n > 100) throw new BizException("请求过于频繁");4. 业务数据缓存
订单详情、字典表、配置项 —— 注意 和 DB 一致性(先更 DB 再删缓存,或 Canal 订阅 binlog 删缓存)。
延伸 / 易错点
- 缓存和 DB 双写顺序搞反会出现脏读
- 大 key、热 key 要监控;集群用 hash tag 时注意槽
- 登录态别只放 JVM 内存,水平扩展会话会丢
写在最后
面试时把 三个「击」 和 登录、限流、业务缓存 各举一条真实或合理的项目场景,比背概念分高。没做过布隆也可以说「查不到的数据短缓存空值,我们线上这么干的」。
创作不易,转载请注明出处和作者。
