尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Redis 系列(八):缓存设计模式与一致性——从 Cache Aside 到防雪崩

Redis 系列(八):缓存设计模式与一致性——从 Cache Aside 到防雪崩 核心目标掌握 Cache Aside 等主流缓存模式理解先删缓存还是先更新库的时序窗口用真实实验复现缓存不一致、击穿重建风暴与穿透能设计带防护的缓存架构。前置知识完成 Part 6过期/淘汰——雪崩的机制基础与 Part 7Lua/锁——防击穿的武器。验证环境Redis 8.10.0cygwin 移植版127.0.0.1:6379、redis-py 8.1.0、Python 3.11.6、Windows 11一致性实验用 Python 线程 内存 dict 模拟数据库。最后复核日期2026-08-07。0. 本篇问题场景三个缓存事故都是真事数据对不上用户明明下单了页面却显示旧库存——DB 是新的缓存是旧的一查就崩某个冷门 id 被疯狂查询每次都打到数据库——缓存形同虚设零点雪崩所有商品缓存同一时刻过期数据库瞬间被打挂。三个事故对应缓存的三类经典问题一致性、穿透/击穿、雪崩。本篇先用实验把每一个复现出来再给防护方案——先看到病再吃药。1. 缓存模式为什么是 Cache Aside主流缓存模式四种区别在谁负责把数据放进缓存模式读写评价Cache Asidemiss 时应用回源 DB 并写缓存应用更新 DB 后删缓存简单可控默认选择Read Through缓存层负责回源应用只写 DB缓存组件要支持Write Through同左应用写 DB 时同步写缓存写延迟变高Write Back同左只写缓存异步刷 DB有丢数据风险Redis 本身不支持Read/Write Through需要客户端库实现如 Spring Cache 注解所以绝大多数团队用 Cache Aside。它的核心规则只有两条读缓存命中返回miss 回源 DB写缓存带 TTL写更新 DB删除缓存而不是更新缓存。第二条为什么删不更新——因为更新缓存会把不一致窗口从一次删操作变成两次写操作的竞态见 §2。2. 一致性先删缓存还是先更新库2.1 经典复现先删缓存再更新库 必现不一致用线程模拟真实并发时序删缓存与写库之间有时间差defwriter():r.delete(cache:item:1)# 1. 删缓存time.sleep(0.3)# 模拟耗时的 DB 写db[item:1]new-value# 3. 写库defreader():time.sleep(0.15)# 在删缓存之后、写库之前读valr.get(cache:item:1)ifvalisNone:valdb[item:1]# 2. 读到 DB 旧值r.set(cache:item:1,val)# 4. 把旧值写回缓存实测结果DB 值: new-value 缓存值: old-value 不一致: True ← 缓存被旧值污染时序删缓存 → 读请求 miss → 读 DB旧值→ 写缓存旧值→ DB 被更新为新值。缓存里永远躺着旧值直到 TTL 到期。这就是先删缓存再更新库的致命窗口。2.2 先更新库再删缓存窗口更小但不是零T1: 写 DB new → 此刻缓存里还是 old T2: 读缓存 → 命中 old ← 不一致窗口直到 T1 删缓存 T1: 删缓存窗口只有写库完成到删缓存完成之间通常毫秒级。风险点如果 T1 写库成功后在删缓存前崩溃缓存永远旧——需要重试/延迟兜底。所以两种方案都不是银弹工程上的取舍是方案不一致窗口主要风险先删缓存再更新库大读-写-回填竞态缓存被旧值长时间污染先更新库再删缓存小毫秒级删缓存失败/崩溃 → 永久旧值先更新库再删缓存 延迟双删更小复杂度高双删窗口仍需评估订阅 binlog 异步失效CDC秒级引入消息组件架构变重共识多数团队选先更新库再删缓存配合删缓存失败重试重试队列、过期时间兜底即使删失败TTL 后自愈、必要时 CDC。而强一致场景根本不该用缓存——先想清楚能不能接受秒级最终一致。3. 缓存穿透 / 击穿 / 雪崩三兄弟的防护3.1 穿透查一个不存在的东西请求一个 DB 里没有、缓存里也没有的 key → 每次都回源 → 攻击者可以靠遍历不存在的 id 打垮数据库。防护按成本递增参数校验非法的 id负数、超长直接拒绝空值缓存查询结果为空也缓存一个占位值短 TTL布隆过滤器写 DB 时把 id 加入过滤器读前先查——过滤器说不存在就绝不回源。实测空值缓存第二次不再回源第一次查询不存在 key: None 第二次命中空值缓存不再回源: None 缓存内容: __NULL__3.2 击穿热点 key 过期的瞬间一个热点 key过期后大量并发同时 miss、同时回源——“重建风暴”。实测 20 个并发读一个刚过期的热点无锁重建: 20 并发 → DB 查询 20 次击穿 互斥锁重建: 20 并发 → DB 查询 1 次只有 1 次回源防护的两种主流方案方案机制优缺点互斥锁重建miss 时先SET NX抢锁只有锁主回源其余等待后读缓存简单可靠锁失败路径要兜底逻辑过期缓存值里存逻辑过期时间异步线程重建读时发现逻辑过期返回旧值触发重建不阻塞读但数据短暂不新鲜本系列 shop-lab 采用互斥锁方案§5。3.3 雪崩大量 key 同时过期击穿是一个热点雪崩是一批 key同时过期或被淘汰Part 6 的 maxmemory 洗库。防护过期时间加随机抖动TTL base random(0, 300)打散集中失效多级缓存本地缓存进程内兜 RedisRedis 失效不直接打 DB限流降级回源侧限流超限返回旧数据或降级文案热点预加载活动前预热缓存错峰重建。抖动是最便宜的一招生产缓存写入务必带 jitter。4. shop-lab 实战商品缓存落地shop-lab/src/shop_lab/cache.py实现了三合一的读路径Cache Aside 空值缓存 互斥锁defget_product(product_id:str,with_rebuild_lock:boolTrue)-str|None:cachedr.get(key)ifcachedisnotNone:returnNoneifcached__NULL__elsecached# 空值占位ifwith_rebuild_lock:lock_keyf{key}:locklock_tokenuuid.uuid4().hexifnotr.set(lock_key,lock_token,nxTrue,exLOCK_TTL):# 抢重建锁# 等锁主重建完成后重读缓存带超时兜底...try:return_rebuild(r,product_id,key)finally:# 只释放自己的锁Lua 校验 tokenPart 9 原则防误删他人重建锁r.eval(_RELEASE_LOCK_SCRIPT,1,lock_key,lock_token)else:return_rebuild(r,product_id,key)def_rebuild(r,product_id,key):value_load_from_db(product_id)ifvalueisNone:r.set(key,__NULL__,exNULL_TTL)# 防穿透空值也缓存returnNoner.set(key,value,exCACHE_TTL)# 缓存重建写库侧负责 invalidatereturnvalue配套测试tests/test_cache.py覆盖命中后不再回源、空值缓存拦截第二次查询、10 并发只回源 1 次防击穿、失效后无锁模式会回源。$ cd redis/shop-lab python -m pytest ................................ [100%] 32 passed in 30.49sPart 1-7 累计 27 个 本篇 5 个。5. 版本与环境差异差异点说明锁实现本文SET NX EX用法在 6.x 一致redis-py 8.x 的set(nxTrue, ex)为推荐写法布隆过滤器Redis 原生需 RedisBloom 模块Redis 8.0 已并入核心纯 Redis 可用 Bitmap 多个哈希自行实现空值缓存无版本差异注意占位值如__NULL__不能与真实值冲突一致性问题的本质是分布式系统的时序与 Redis 版本无关——实验结论在 7.x/8.x 上同样成立。6. 测试与验收一致性实验线程时序复现不可靠的断言结果依赖调度用能稳定复现的 sleep 控制即可生产回归用测试替身防击穿测试用 mock 统计 DB 调用次数断言有锁 ≤ 1 次回源。本篇验收清单能画出先删缓存再更新库的不一致时序并解释窗口能说出先更新库再删缓存的残余风险与兜底手段能区分穿透/击穿/雪崩并各给出至少两种防护能解释互斥锁重建与逻辑过期两种防击穿方案的取舍生产缓存写入必须带 TTL 抖动能说出为什么cd redis/shop-lab python -m pytest32 passed。7. 常见误区“删缓存改成更新缓存更好”——更糟两次写操作的竞态窗口更大§1。“延迟双删能根治不一致”——只是把窗口挪走双删本身也有新窗口与复杂度§2.2。“布隆过滤器能当缓存用”——它只回答可能存在误判率 0且不能删除它是防穿透的哨兵不是存储。“击穿和雪崩是一回事”——击穿是单热点瞬时雪崩是批量同时§3.2/3.3。“缓存一致性可以做到强一致”——分布式环境下只能最终一致 窗口可控强一致场景别用缓存。8. 本篇小结回到开篇三个事故数据对不上缓存一致性没有银弹——默认先更新库再删缓存 重试 TTL 兜底接受秒级最终一致一查就崩穿透用空值缓存/布隆过滤器击穿用互斥锁/逻辑过期零点雪崩过期打散 多级缓存 限流降级。下一篇 Part 9分布式锁与限流 把并发治理工具化分布式锁的正确实现与 Redlock 争议、令牌桶/滑动窗口限流以及锁 幂等 限流三层防护在 shop-lab 的落地。9. 官方资料Cache Aside 模式https://docs.microsoft.com/en-us/azure/architecture/patterns/cache-asideRedis SET 命令NX/EXhttps://redis.io/docs/latest/commands/set/Bloom filter 模块https://redis.io/docs/latest/develop/data-types/probabilistic/bloom-filter/Keyspace notifications失效通知https://redis.io/docs/latest/develop/notifications/
返回列表