本文目录导读:

这是一个非常经典且具有挑战性的分布式系统问题,开源缓存(如 Redis、Memcached)的失效问题,核心在于数据一致性和缓存穿透/雪崩的防护。
解决思路分为两大层面:缓存本身的失效策略 和 应对失效后的并发/流量冲击。
下面梳理出主流的解决方案和最佳实践。
核心问题:缓存为什么会失效?
- 内存淘汰:缓存满了,根据 LRU(最近最少使用)、LFU(最不经常使用)等算法主动删除。
- 过期删除:设置了 TTL(生存时间),时间到了自动删除。
- 主动失效:数据更新时,为了保持一致性,主动删除或更新缓存。
- 服务宕机:缓存服务挂了,所有缓存丢失。
针对不同失效场景的解决方案
场景1:缓存雪崩(大量缓存同时失效 + 高并发流量)
表现:大量缓存集中在同一时间过期,或缓存服务宕机,所有请求瞬间打到数据库,导致数据库崩溃。
解决方案:
- 过期时间加随机值:避免大量 key 在同一时间过期,比如原本设置 1 小时,实际设置为
60min + random(0, 5min)。 - 多级缓存:使用本地缓存(如 Caffeine、Guava Cache)作为第一道防线,Redis 作为第二道防线,数据库作为最后防线,本地缓存即使失效,也能拦截掉一部分流量。
- 缓存高可用:使用 Redis Sentinel 或 Redis Cluster,避免单点故障。
- 限流 & 降级:在缓存失效后,对访问数据库的请求进行限流(如使用令牌桶、漏桶算法),部分请求直接降级返回默认值或错误提示(如“活动火爆,请稍后再试”)。
场景2:缓存穿透(查询一个根本不存在的数据)
表现:大量请求查询一个在数据库和缓存中都不存在的 key,每次都会绕过缓存直接打到数据库,恶意攻击常用手段。
解决方案:
- 缓存空对象:如果数据库查询结果为空,也把这个“空结果”缓存起来(TTL 设置短一些,5 分钟),这样后续相同的请求会直接返回空值,保护数据库。
- 缺点:占用内存,且可能导致短期数据不一致(真实的 key 被写入后,空缓存还未过期)。
- 布隆过滤器:在缓存前端加一层布隆过滤器,如果一个 key 不存在,布隆过滤器直接返回不存在,请求不再穿透到缓存和数据库,这是最有效的方案。
- 缺点:有一定误判率(不存在的可能误判为存在),需要维护布隆过滤器的数据同步。
场景3:缓存击穿(一个热点 key 过期)
表现:一个非常热点的 key 在过期的一瞬间,大量并发请求同时发现这个 key 失效了,全部去重建缓存,瞬间打垮数据库。
解决方案:
- 互斥锁(Mutex Lock):当缓存失效时,只有第一个请求能获得锁并去数据库查询,其他请求等待,第一个请求查询成功后,将数据写回缓存并释放锁,后续请求直接从缓存读取。
- 实现方式:Redis 的
SETNX(SET if Not eXists)命令,或 Redisson 的分布式锁。 - 优点:保证数据强一致性。
- 缺点:可能引入死锁风险,效率降低(请求串行化)。
- 实现方式:Redis 的
- 逻辑过期:在缓存对象中额外存储一个逻辑过期时间,而不是让 Redis 物理过期。
- 开启一个后台线程,定期检查逻辑时间是否过期,如果过期则异步去更新缓存。
- 实现方式:
String value, Long timer,后台线程使用互斥锁控制只更新一次。 - 优点:不会阻塞用户请求,用户体验好。
- 缺点:数据在逻辑过期和后台更新完成之间存在短暂的不一致(最终一致性)。
数据库与缓存数据一致性问题(缓存更新策略)
除了失效,更重要的是如何更新,这是最难的部分,因为涉及分布式事务。
常用策略(推荐组合)
-
Cache Aside Pattern(旁路缓存)—— 最常用
- 读:先读缓存,有则返回;没有则读数据库,写入缓存,返回。
- 写:先更新数据库,再删除缓存。
- 为什么不用“先更新缓存”? :因为并发写可能导致脏数据;数据库和缓存是两个不同的事务,难以保证原子性。
- “先删缓存,再更新数据库”的坑:高并发下,A 删缓存后,B 查到旧数据并写入缓存,导致缓存中是旧数据。
- 最佳实践:先更新数据库,再删除缓存,即使删除失败,后续读请求会从数据库拉取并更新(有一定的短暂不一致),可以使用延时双删(更新前删一次,更新后等几百毫秒再删一次)来降低风险,但建议用 binlog(二进制日志)监听。
-
Read/Write Through Pattern(读写穿透)
- 缓存层封装了数据源的逻辑,应用程序只和缓存交互,缓存负责与数据库同步。
- 优点:对应用层透明。
- 缺点:实现复杂,需要改造缓存中间件。
-
异步缓存更新(最终一致性方案)
- 方案:应用只更新数据库,不直接操作缓存,通过监听 MySQL 的 binlog(如使用 Canal 中间件),解析变更日志,然后异步更新 Redis。
- 优势:
- 解耦:应用无需关心缓存逻辑。
- 可靠:即使更新缓存失败,可以通过消息队列重试,保证最终一致性。
- 高性能:写操作只需一次数据库写入。
- 劣势:引入 Canal 和 MQ(消息队列),系统复杂度增加;存在短暂的最终不一致(秒级延迟)。
最佳实践组合拳
在实际的生产环境中,通常不会只用一种方案,而是组合使用。
| 问题 | 解决策略 | 核心工具/思想 |
|---|---|---|
| 雪崩 | 过期时间加随机值 + 多级缓存 + Redis 高可用 + 限流 | 打散过期时间、分层、冗余 |
| 穿透 | 缓存空对象 + 布隆过滤器 | 防止无效请求直达 DB |
| 击穿 | 互斥锁 或 逻辑过期 | 防止热点 key 重建风暴 |
| 一致性 | 更新 DB 后删除缓存 + 延时双删 / 异步 binlog 监听 | 保证最终一致性 |
一个推荐的“防失效能打满”架构:
- 前端:使用布隆过滤器拦截掉绝大多数穿透请求。
- 网关/应用层:配置限流(如基于漏桶的 Nginx + Lua 或 Sentinel)。
- 应用层:
- 使用本地缓存(Caffeine)作为一级缓存,TTL(生存时间)极短(如秒级)。
- 使用 Redis 作为二级缓存。
- 热点 key 防击穿:使用逻辑过期或互斥锁(建议逻辑过期,因为不阻塞)。
- 缓存更新:使用 Canal + MQ 异步监听 binlog 更新缓存(保证最终一致性)。
- 底层:MySQL 数据库 做好读写分离、连接池、慢查询优化。
这套组合拳可以在大多数场景下,将缓存失效对后端数据库的冲击降低到最小。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播