redis缓存三大问题分析与解决方案
什么是缓存?
缓存(Cache)是一种将热点数据缓存在内存中(如 Redis)以加快访问速度、减轻数据库压力的技术。
但引入缓存后可能出现 三大核心问题:
- 缓存穿透(Cache Penetration)
- 缓存击穿(Cache Breakdown)
- 缓存雪崩(Cache Avalanche)
一、缓存穿透(Cache Penetration)
问题描述
缓存穿透指:请求的数据既不在缓存中,也不在数据库中,导致请求每次都打到数据库。
常见场景
- 恶意攻击:传入大量随机 ID,绕过缓存层直击数据库
- 用户访问非法 ID,如
/user?id=-1
举例
用户频繁访问一个 不存在的商品 ID:99999999:
- Redis 无此数据 → 查询数据库
- 数据库无 → 返回 null
- 下次再次请求 ID=99999999,又重复上述过程 → DB 被压垮
解决方案
1. 缓存空值
if (dbData == null) {redis.set("shop:99999999", "", 2分钟);
}
- 空值也缓存,避免重复查数据库
- 设较短 TTL(避免缓存过期数据太久)
2. 参数校验拦截非法请求
- 如:ID 不能为负数或超过最大值
- 在请求层面做过滤,不进 DB 或 Redis
3. 布隆过滤器(适用于大数据量)
- 将所有合法 ID 加入布隆过滤器
- 请求前先判断是否命中布隆过滤器,不在则直接拒绝
二、缓存击穿(Cache Breakdown)
问题描述
缓存击穿指:某个热点 Key 刚好失效时,大量并发请求打到数据库,导致数据库瞬时压力激增。
常见场景
- 热点数据正好在高峰期过期
- 比如:商品详情页、秒杀商品、抢购库存
举例
商品 ID=1
每天百万访问量,缓存过期瞬间,大量用户同时访问导致:
- Redis 查不到 → 并发查询 DB → 数据库压力飙升
解决方案
1. 互斥锁方式:单线程缓存重建
if (redis.get("shop:1") == null) {if (tryLock("lock:shop:1")) {// 从 DB 读取 → 缓存写回 Redisunlock();} else {// 其他线程等待或返回默认值}
}
- 缓存重建交给首个拿到锁的线程,其它线程等待或快速失败
2. 逻辑过期 + 异步重建(推荐)
{"data": {...},"expireTime": "2025-06-30 12:00:00"
}
- 缓存提前设置一个逻辑过期时间(保存在 value 中)
- 判断已过期 → 异步线程后台刷新 → 返回旧数据不中断用户体验
适合热点数据缓存更新
三、缓存雪崩(Cache Avalanche)
问题描述
大量缓存同时过期,导致所有请求同时访问数据库,引发系统雪崩。
常见场景
- 设置了相同 TTL 的大量缓存同时过期
- Redis 重启或崩溃,缓存瞬间全部丢失
举例
- 秒杀系统中 10 万商品都设置
TTL=24小时
- 恰好第二天凌晨失效 → 所有请求打到数据库
解决方案
1. 缓存过期时间加随机
int ttl = 3600 + RandomUtil.randomInt(0, 600);
redis.set("shop:" + id, value, ttl, TimeUnit.SECONDS);
- 避免所有 key 同一时间过期,均匀错开时间点
2. 热点数据永不过期 + 后台异步刷新
- 逻辑过期方案 + 后台定时更新
- 热点数据维持高可用
3. 多级缓存(本地 + 分布式)
- 如:Caffeine + Redis + MySQL 三层缓存
- Redis 崩溃时,先从本地缓存兜底
4. 限流+降级
- 接口层加限流、熔断、降级返回默认值,避免雪崩扩大化
项目中 Redis 缓存策略总结
问题 | 定义 | 解决方案 |
---|---|---|
缓存穿透 | 请求数据既不在缓存也不在数据库 | 缓存空值、参数校验、布隆过滤器 |
缓存击穿 | 热点 key 在高并发下刚好失效 | 加锁互斥、逻辑过期 + 异步刷新 |
缓存雪崩 | 大量 key 同时过期、或 Redis 故障 | 加 TTL 随机值、热点永不过期、多级缓存、限流降级 |
实战建议
- 所有缓存数据 务必设置 TTL,默认不要永久存在
- 区分冷数据(短 TTL)与热点数据(长 TTL 或逻辑过期)
- 高并发业务使用异步线程池或消息队列缓冲请求
- 建立统一的缓存封装组件(CacheClient),集中处理这些问题