Redis缓存三大问题为穿透、击穿、雪崩:穿透是查不存在数据致请求直击数据库,可用空值缓存或布隆过滤器拦截;击穿是热点key过期瞬间并发压库,可用互斥锁或逻辑过期应对;雪崩是大量key同时过期或Redis宕机,需加随机过期时间、多级缓存及限流降级。

Redis 缓存不是“加了就灵”,关键在策略是否匹配真实请求模式。盲目缓存反而会放大数据库压力,比如缓存击穿时瞬间涌向 DB 的并发查询,或缓存雪崩导致连接池耗尽。
缓存未命中时怎么避免数据库被压垮
直接查库 + 写缓存的朴素逻辑,在高并发下极易触发“缓存穿透”或“缓存击穿”。尤其当 id 是恶意构造或不存在的值时,每次请求都穿透到 DB。
- 对空结果也缓存:用
setex存一个空字符串或特殊标记(如"null"),TTL 设为 1–2 分钟,防止重复穿透 - 前置过滤:对 ID 类键值,用布隆过滤器(
BloomFilter)在缓存层拦截 99% 的非法查询,Java 可用 Guava 或 RedisBloom 模块 - 拒绝兜底:若业务允许,对明显异常的请求(如负数 ID、超长字符串)直接返回 400,不进缓存也不查库
热点 key 过期瞬间怎么防击穿
GET 返回 null 后多个线程同时执行 SELECT + SET,是典型击穿场景。靠应用层 if-else 锁不住并发。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 用
SETNX+ 过期时间实现轻量互斥锁:先setnx cache:shop:123 lock 1,成功者查库并写缓存,失败者等待后重试 - 更稳的做法是“逻辑过期”:缓存值里嵌入时间戳(如 JSON 中加
"expireAt": 1720848000),读取时只判断逻辑过期,过期则异步刷新,不阻塞主流程 - 慎用“永不过期”:虽能防击穿,但数据一致性难保障;若真要用,必须搭配主动更新机制(如监听 binlog 或 MQ)
大量 key 同时失效怎么防雪崩
所有商品缓存统一设 EXPIRE 3600,整点一到,DB 瞬间被打满——这不是理论风险,是真实发生过的线上事故。
- TTL 加随机偏移:写入时用
setex key (3600 + random(0, 600)) value,把过期时间打散在 ±10 分钟内 - 预热 + 延续:大促前用脚本批量加载热点 key,并设置较长 TTL;运行中对高频访问 key,每次读取后用
EXPIRE延长其生命周期 - 降级开关:监控到 DB QPS 突增时,自动开启“缓存只读”模式,拒绝写缓存,避免雪崩恶化
本地缓存和 Redis 要不要一起用
单靠 Redis 无法解决全部问题:网络延迟、Redis 单点故障、连接数限制,都会让缓存层本身成为瓶颈。
- 本地缓存(如 Caffeine)适合扛住 90% 的重复请求:设
maximumSize=1000+expireAfterWrite(1, TimeUnit.MINUTES),内存占用可控 - 两级缓存不是简单叠加:本地缓存 miss 后才查 Redis;Redis miss 后才查 DB;且本地缓存不承担写操作,只读
- 注意一致性陷阱:DB 更新后,必须清除本地缓存(通过消息通知或定时轮询),否则会出现“刚改完查出来还是旧值”的情况
最常被忽略的是缓存粒度——缓存整张用户表不如缓存 user:profile:1001 和 user:stats:1001 两个独立 key;大 key 不仅拖慢 Redis,还让更新变得笨重。实际压测中,拆分后单 key 平均响应从 8ms 降到 0.3ms,这才是缓存该有的样子。

















