知识卡片
"读时失效":把集中的缓存失效成本分散到后续每次读
内容
缓存和反范式化数据库设计面临同一类问题:同一份数据在多处留了副本, 原始数据一变,就要想办法让这些副本不再被当成”新鲜数据”用。常见做法 “写时失效”(数据一变就立刻标记或替换所有相关缓存项)直观、易实现,但 有一个容易被忽视的代价:如果一次原始数据变更牵涉到大量派生缓存对象 (书中举例:一个对象被一百万个缓存项依赖),写时失效需要在写操作发生 的那一刻集中处理完全部一百万个失效,即使有高效的定位方法,这个操作 本身也会造成一次明显的负载和延迟尖峰。”读时失效”提供了另一种时间上的 分摊方式:写操作发生时不去主动清理任何缓存,而是立即完成;缓存项在 被存入时,额外附带一份它所依赖数据的版本号或时间戳,只有在后续被 读取时,才拿这份版本号跟”当前依赖数据的最新版本号”比对,判断这份缓存 是否已经过期、要不要重新生成。这样一来,”失效一百万个缓存对象”的总 工作量并没有减少,但被拆散到了后续一百万次独立的读请求里各自完成一 小份,而不是压在写操作那一瞬间集中爆发,从而避免了负载和延迟的尖峰。 最简单的读时失效实现方式是”对象版本控制”:给每份被依赖的数据维护一个 版本号,任何依赖它的缓存对象都存一份”生成时的版本号快照”,读取时比对 版本号即可判断新鲜度。这个机制本质上和 [[读写分离的脏数据处理策略从简单粗暴到基于会话逐级精细]]里”基于版本 分离”判断备库数据是否够新的思路是同一种模式——用一个单调递增的版本 标记代替昂贵的主动通知,把”数据是否还新鲜”的判断权从写方转移到每次 读取时按需检查。它的代价是判断粒度较粗(一个版本号往往代表”依赖这份 数据的所有比特”,可能因为无关的字段变化而误判失效),这是用简单性换 取效率必须接受的权衡。
参考来源
- 位置:《高性能MySQL:第3版》第14章"应用层优化"14.3.3节"缓存控制
策略"(源文件:_epub-src/OEBPS/Text/part0021.xhtml)
- 结论依据:原文明确"假设要失效一个有一百万缓存对象依赖的对象,如果
采用写时失效,需要一次在缓存中失效一百万个对象……如果采用读时
失效,写操作可以立即完成,但后续这一百万对象的读操作可能会有略微
的延迟。这样就把失效一百万对象的开销分散了……一种最简单的读时
失效的办法是采用对象版本控制",直接说明写时失效与读时失效在成本
分布上的差异及对象版本控制这一具体实现方式。
- 原始内容:如果采用写时失效,需要一次在缓存中失效一百万个对象……
如果采用读时失效,写操作可以立即完成,但后续这一百万对象的读操作
可能会有略微的延迟。这样就把失效一百万对象的开销分散了。