Lovi.space

Java 后端

Redis 缓存三大问题:把缓存做成系统防线

从设备档案高频查询出发,拆解缓存穿透、击穿、雪崩与一致性窗口的工程取舍。

·1 min read
#Java#Redis#缓存#高并发

核心洞察:缓存不是数据库前面的一层加速器,而是一套针对读压力、热点和故障传播的控制系统。 霓虹港首页需要展示数万台设备的摘要。绝大部分请求读取少量热点设备,Cache Aside 可以显著减少数据库压力;但一旦把“有 Redis”误认为“系统安全”,流量尖峰会迅速暴露三个不同问题。

穿透:查询根本不存在的数据

攻击或异常客户端持续查询不存在的设备编号。缓存没有命中,数据库也没有命中,每次请求都会穿过两层。解决并非只有布隆过滤器:数据规模小、变更频繁时,短 TTL 的空值缓存更简单;数据量大且键集合稳定时,布隆过滤器能在最前面拦住绝大多数无效请求。

DeviceSummary value = redis.get(key);
if (value != null) return value;
if (redis.hasNullMarker(key)) return null;
DeviceSummary loaded = repository.findSummary(id);
redis.cache(key, loaded, ttl);

空值缓存必须设短过期时间,避免新设备刚创建却长期被判定不存在。

击穿:一个热点 Key 同时失效

某个重点设备页面过期时,数千并发请求同时回源。它们请求的是同一份数据,但数据库收到的是数千次相同查询。互斥重建让一个请求负责加载,其他请求短暂等待;逻辑过期则始终返回旧值,并异步刷新。前者一致性更强,后者延迟更稳定。

策略 首次过期体验 一致性 适合场景
互斥锁 少量请求等待 较强 更新敏感的详情
逻辑过期 返回旧值 最终一致 高可用读模型
锁必须有过期时间和失败兜底。否则加载线程异常时,“防击穿”的锁会变成新的单点故障。 ## 雪崩:大量 Key 在同一时刻失效 批量预热如果统一设置一小时 TTL,那么下一小时会把数据库暴露给集中回源。解决方式是给 TTL 加随机扰动、进行分批预热、为数据库设置限流和熔断,并准备降级响应。雪崩处理的目标不是让每个请求成功,而是防止局部失效扩散为全站不可用。 ## 一致性先于技巧 典型 Cache Aside 写路径是:先更新数据库,再删除缓存。删除而不是更新缓存,是因为复杂对象的写后重建更容易漏字段;缓存缺失后由读请求重建即可。双删、消息队列或订阅 binlog 都有适用条件,但不能替代对一致性窗口的明确认知。 ## 观测指标 至少记录命中率、回源 QPS、热点 Key、锁等待时间、缓存加载失败率和过期分布。命中率高不等于健康:一个错误地缓存很久的旧值,同样会带来漂亮的命中率。 ## 结语 缓存设计的核心不是背“穿透、击穿、雪崩”三个名词,而是回答:谁会回源、何时回源、失败时系统牺牲什么。只有把这三个问题写进设计,Redis 才是一道可靠的防线。

读到这里,说明你也在认真对待这个问题。

讨论与反应

⌘ K