老王开了一家面馆,生意好到爆棚。为了让顾客不用每次跑到后厨问“今天有什么面”,他在前台挂了一块小黑板,写上招牌菜和价格——顾客抬眼就能看到,点餐速度快了十倍。这块小黑板,就是计算机里的缓存;后厨那本记录食材库存与价格的台账,就是源数据库。可问题随之而来:当番茄涨价、牛腩卖完,台账改了而小黑板没改,顾客就会指着黑板质问:“上面明明写 15 块,结账怎么变 18 块?”这就是缓存一致性问题的雏形:缓存里的副本数据,如何与源数据保持同步。 AI 推理服务同样被困在这个问题里:大模型输出结果、中间特征、热点请求的答案,每次重新计算既费时又费算力;但一旦模型更新、业务规则调整,旧缓存立刻变成“过期答案”。本篇文章用写穿透、写回、失效三种策略,拆解“源变之后缓存怎么跟上”的经典命题,再延伸到 Redis、CDN 与模型输出缓存构成的多级架构,帮你搭建一套又快又不“货不对板”的缓存体系。
掌握缓存一致性策略,是设计高吞吐、低延迟 AI 推理服务的必修课。它能让你在“数据新鲜度”与“响应速度”之间做出有理有据的工程决策,直接决定线上服务的稳定性与用户体验。
可视化演示
动态展示写穿透/写回/写失效/写广播四种缓存一致性策略下,写操作与缓存状态(MESI)在多缓存节点间的传播过程,切换策略观察差异。
本节配有交互动效(进入视口后自动加载,约 3KB)。
缓存一致性基础
老王面馆的生意越来越红火,他发现顾客点餐太慢了——每个人都得把服务员拉到后厨门口问“今天还有牛肉吗?”于是他挂出前台小黑板,把菜品和价格写上去,顾客抬眼就能看到,点餐速度快了十倍。这块小黑板,就是计算机世界里的“缓存”。缓存的本质是“副本”:它复制源数据的一部分,放在离读者更近的地方,用空间换时间。
但副本有个天生的隐患:它会“漂移”。小黑板上的价格是台账的快照,只要台账变了而黑板没变,两者就对不上。计算机系统里同理:数据库是权威源,缓存是它的快照,源数据一更新,快照就成了旧数据。所谓缓存一致性,就是一套保证“副本跟上源”的规则。规则越严,数据越新鲜,但往往越慢;规则越松,性能越好,但可能读到旧值。这就是强一致与弱一致的分野。
为什么缓存值得如此大费周章?因为它带来的收益可以量化。设缓存命中率为 H(0 到 1),缓存读延迟为 T_cache,数据库读延迟为 T_DB,则平均读延迟为:
小黑板让九成顾客抬头就能看到答案(命中),只有一成顾客需要去后厨问(未命中回源)——平均点餐时间自然大幅缩短。但正如老王很快会发现的:黑板上的内容如果不更新,光有速度没有准确,迟早要出乱子。下一节,我们看老王如何应对“价格变了”这件事。
- 平均读延迟 T_read每次读请求从发出到拿到数据的平均耗时,是衡量缓存收益的核心指标。
- 命中率 H读请求在缓存中找到有效数据的比例,取值 0 到 1。H 越接近 1,说明缓存越“有用”。
- 命中时的开销 T_cache命中时只需访问内存或 Redis,通常几十微秒到几毫秒,远小于数据库延迟。
- 未命中回源开销 T_DB未命中时需回源数据库,通常几毫秒到几十毫秒,伴随网络与磁盘 IO 开销。
写穿透策略
有一天番茄批发价上涨,老王赶紧拿起粉笔,一边在后厨台账上记下“番茄面 18 元”,一边跑到前台把小黑板改成“番茄面 18 元”。两道工序一气呵成,顾客看到的永远是最新价格。这就是写穿透(Write-Through):每一次写操作都“穿透”缓存直达数据库,两边同步更新,缺一不可。
写穿透的执行流非常直观:应用先更新缓存,再更新数据库(或反过来),两个写都必须成功才算写完成。因为每一步写都是同步的,读方永远只能读到最新值——这是它最大的优点:强一致。但代价也摆在台面上:本该一次完成的写,现在要写两个地方。设缓存写延迟为 T_cache,数据库写延迟为 T_DB,则一次写操作的总延迟为:
老王每次调价都要“前台黑板 + 后厨台账”跑两趟,价格倒是永远准确了,可一天调十次价,他就得跑二十趟。写穿透的强一致,是用写路径的加倍延迟换来的——适合写少读多、对数据新鲜度近乎苛刻的场景,比如价格表、配置中心、权限名单。
- 写操作总延迟 T_write从应用发起写到缓存和数据库都确认收到的总耗时。
- 缓存写延迟 T_cache写入 Redis 或本地缓存的耗时,通常很快(亚毫秒级)。
- 数据库写延迟 T_DB写入数据库并返回确认的耗时,涉及磁盘落盘与索引更新,通常较慢。
- 求和关系两个写是同步完成的,总延迟是两者相加,而不是取最大值或平均值。
写回策略
老王后来学会偷懒了:顾客在柜台问“有没有新上的番茄牛腩面?”他先在黑板上把菜名加上,客人开开心心点单,等到晚上打烊,伙计才把黑板上新增的、改动的菜品,一股脑儿抄回后厨台账。这就是写回(Write-Back):先写缓存、快速响应,回头再异步批量同步到数据库。
写回策略中,每次写操作只更新缓存,并把这个 key 标记为“脏”(dirty)。系统在合适的时机——定时、攒够一批、或系统空闲时——把脏数据批量回写数据库。注意:在回写完成之前,数据库里还是旧值。所以它保证的是一致性中最温和的一档:最终一致。只要给足时间,数据终会一致,但中间存在一个“滞后窗口”。设一批共 n 个写请求,数据库批量写的开销被摊薄,则每个写请求的平均延迟约为:
写回让老王的“写”变得飞快——先写黑板,客人不等待;至于台账,打烊后统一补记。唯一的隐患是:万一打烊前小店失火,黑板上的新菜名就跟着灰飞烟灭了。在系统里,这个“失火”就是缓存节点宕机、进程崩溃。所以写回适合能容忍少量数据丢失、写频率极高的场景,比如访问日志、浏览计数、用户行为特征。
- 平均写延迟 T_write把 n 个写请求的总开销均摊到每个请求上得到的结果。
- 批量大小 n一次回写合并的写请求数量,n 越大,摊薄效果越明显。
- 缓存写延迟 T_cache写请求先落缓存,这一步是同步的,每个请求至少要付这个成本。
- 最终一致收敛回写完成后数据库与缓存收敛到相同值,收敛前存在一个一致性“空白期”。
失效策略
老王的第三招最巧妙:当得知牛腩卖完时,他并不急着在小黑板上写“牛腩面 25 元”——反正也不一定有人问——而是直接把“牛腩面”三个字擦掉。顾客问起,伙计再去后厨问师傅,问到了就顺手把新价格写回黑板。这个“先擦掉、问到了再写”的路数,就是失效策略(Invalidation / Cache-Aside)。
失效策略的核心动作是“删”而不是“写”:当源数据更新时,系统主动删除缓存中对应的旧值(或通过消息通知各副本删除)。下次读请求发现缓存里没有这个 key(缓存未命中),就会回源数据库拉取最新值,并重新填充缓存。这个“读时发现缺失才去源拉取”的机制叫懒加载(Lazy Loading)。失效策略常与 TTL(Time To Live,存活时间)搭配:即使没收到删除通知,key 到期也会自动失效。假设源更新频率为 λ,缓存 TTL 设为 T,那么 T 时间内源被更新的概率——也就是缓存变陈的概率为:
“擦掉”比“改价”轻松多了——老王不用每次调价都跑前台,只有顾客真问起那道菜时才需要回后厨确认。对“读多写少”的面馆——大多数客人只点那几样招牌面——这一招既省力气又不会错得太离谱,成了老王最常用的策略。AI 推理缓存中的模型输出快照,也常靠这种“擦掉 + 回源”来应对模型更新。
- 变陈概率 P_stale缓存中的数据在 TTL 窗口内已经与源不一致的概率。
- 源更新速率 λ单位时间内源数据被修改的次数,λ 越大,同样的 TTL 下数据越容易变陈。
- TTL 时长 T缓存条目允许存活的时间,到期后自动失效。
- 指数项 e^{-λT}在泊松更新假设下,T 时间内源恰好一次也没被更新的概率为 e^{-λT},所以变陈概率是 1 减去它。
多级缓存协同
生意越做越大,老王发现一块小黑板不够用了:熟客进门就问价,街坊路过要看招牌,外地游客得先看到街口的大广告牌才愿意进店。于是他把菜单抄了三份:伙计兜里的速记条(最快最贴身)、前台小黑板(给进店顾客看)、街口大广告牌(给整条街看)。三份副本各有各的速度和更新频率——这就是多级缓存。
在 AI 推理服务中,多级缓存对应三层:最靠近计算的是本地内存缓存(L1,进程内,微秒级);中间是 Redis 分布式缓存(L2,毫秒级,服务间共享,承接热点请求);最外层是 CDN 边缘缓存(L3,把响应缓存到离用户最近的节点)。此外,模型输出本身也是一种特殊缓存:相同或相似 prompt 的推理结果直接复用,避免重复计算。设 L1 命中率为 h₁,延迟为 T₁;L1 未命中时查 L2,命中率为 h₂,延迟为 T₂;两级都未命中才回源数据库,延迟为 T_DB,则平均读延迟为:
熟客进店直接报菜名,伙计兜里的速记条一翻就有(L1 命中);新客看小黑板(L2 命中);只有极少数人站在街口看完广告牌还要进店问东问西(回源数据库)。现实系统中,各级缓存的失效通知和 TTL 必须分层设计:L1 靠进程内消息或极短 TTL,L2 靠 Redis 主动失效,L3 靠 URL 版本号切换——才能既快又不至于错得离谱。
- 第一项 h₁T₁请求在 L1 命中的概率乘以其延迟,对应“本地直接命中”的路径。
- 第二项 (1-h₁)h₂T₂L1 未命中但 L2 命中的概率乘以其延迟,对应“回源到 Redis”的路径。
- 第三项 (1-h₁)(1-h₂)T_DB两级都未命中才回源数据库的概率乘以其延迟,这是最贵但占比最小的路径。
- 概率归一关系三条互斥路径的概率之和为 1,故总延迟是加权和而非简单平均。
延迟一致性权衡
老王渐渐悟出一个道理:面和价,不可兼得。要想黑板上的价格永远最新,就得频繁跑前台改(写穿透,慢);要想改价飞快,台账就必然滞后一阵子(写回,有窗口);想省事只靠擦掉旧价,就得接受偶尔有顾客在柜台扑空(失效,读时回源)。每一种策略,都是老王在“速度”与“新鲜”之间选的平衡点。
工程上的权衡可以用一个代价模型刻画。设缓存条目的 TTL 为 T,源更新速率为 λ。TTL 越长,缓存命中率越高、平均读延迟越低;但缓存变陈的概率也越高,越可能返回过期数据。把“变陈的代价”记为 C_stale(返回过期数据导致的损失),把“缓存未命中回源的代价”记为 C_miss(多花的延迟与算力),则单位时间内的综合代价为:
老王最终定的规矩是:招牌面(卖得最多、改价最少)用长 TTL 加失效策略,几乎天天在小黑板上有货;时令菜(价格天天变)干脆不写黑板,客人问了伙计再去后厨问。把“一致性等级”按菜品(业务)的容忍度分开管理,才是老王的真正智慧——这也是生产系统中“分级缓存一致性”的朴素原型。
- 综合代价 C(T)以 T 为 TTL 时,单位时间“过期损失 + 回源开销”的期望和。
- 过期损失项变陈概率 (1-e^{-λT}) 乘以其代价 C_stale,衡量“返回旧数据”造成的伤害。
- 回源开销项恰好没变陈、到期失效回源的概率 e^{-λT} 乘以回源代价 C_miss。
- 此消彼长的关系两项概率相加恒为 1,TTL 增大时第一项增大、第二项减小,导数为零处对应最优 TTL。
实践选型策略
经营半年,老王把自己的经验写成了一本《面馆经营手册》:什么样的菜适合写黑板、什么样的菜适合打烊再记账、什么样的菜干脆不写。选型不是拍脑袋,而是看“问的人多不多、改价勤不勤”。计算机系统里的缓存策略选型,遵循同样朴素的逻辑。
缓存策略选型的核心输入是读写比例 θ = R/W(读次数 R 与写次数 W 之比)和对数据新鲜度的要求。经验法则是:写多读少(θ 很小)时,写回策略能让写路径足够快;读写均衡时,写穿透保证强一致;读多写少(θ 很大)时,失效策略加 TTL 最划算,因为写次数少,失效通知的成本可以被忽略。选择规则可写成:
《面馆经营手册》最后一条写着:世上没有永远最好的策略,只有最适合当下客流的策略。缓存选型亦然——上线前要压测读写比,上线后要监控命中率与变陈率。当 AI 模型版本更新、业务规则调整时,还要记得像老王换菜牌一样,主动把旧缓存全部擦掉。
- 读写比 θ单位时间内读请求数除以写请求数,刻画请求的整体形态。
- 写回区间 θ < θ₁写占主导时优先保证写路径延迟,接受最终一致。
- 写穿透区间读写均衡且对新鲜度要求高时,用强一致换确定性。
- 失效区间 θ > θ₂读占绝对主导时,用“删 + 回源”把一致性成本降到最低。
AI 应用场景
大模型语义缓存服务
阶段:在线推理加速
解决的问题:相同或相似的用户问题重复走完整推理链路,既消耗 GPU 算力又带来数秒延迟。传统无缓存方案下,每个请求都要重新计算,成本随并发线性增长。引入语义缓存(对用户问题的 embedding 做相似度检索,命中则直接复用历史推理输出),可以将常见问题的响应从秒级降到毫秒级。
具体案例:某智能客服系统将用户问题的 embedding 存入 Redis 向量索引,命中相似度阈值后直接返回标准答案。当业务 FAQ 更新或大模型版本升级时,通过消息队列广播“失效”通知,各节点删除对应的缓存条目,并用 TTL 兜底保证最终一致。这套方案与文中“失效策略 + 懒加载回源”一一对应:模型更新就像老王换菜牌,先擦旧、再等客人问。
AIGC 图片生成的边缘缓存
阶段:内容分发与边缘计算
解决的问题:用户生成的图片被反复请求时,每次都回源图片服务重新读取或处理,带宽与算力开销巨大。CDN 边缘缓存把图片与元数据缓存在离用户最近的节点,用 TTL 控制新鲜度。
具体案例:某 AI 绘画产品上线新模型后,旧风格图片的缓存未及时失效,用户仍看到旧画风。团队采用“版本号作为缓存 key 前缀”方案:模型 v2 上线即切换 key 前缀,旧 key 自然过期——这正是多级缓存协同中“CDN 边缘缓存 + 版本号失效”的实战形态,好比街口广告牌换新海报,旧海报让它自然褪色即可。
推荐系统中间特征缓存
阶段:特征工程与在线推理
解决的问题:用户 embedding、物品 embedding 在推荐链路中被反复读取,全量重算昂贵;但用户行为实时变化,缓存需要写回或失效配合才能兼顾性能与新鲜度。
具体案例:某推荐服务用 Redis 缓存用户 embedding。用户发生点击行为触发特征更新:特征服务先写 Redis(写回模式,标记脏数据),每 5 分钟批量回写离线特征库,保证线上特征与离线训练数据最终一致。选型依据正是文中公式 θ = R/W:点击写入远少于 embedding 读取,故采用“失效 + 定时批量回写”的组合,既保住读取延迟,又控制写入成本。
Python 演示
import time class CacheEntry: def __init__(self, value, expire_at): self.value = value self.expire_at = expire_at class ConsistencyCache: """模拟三种缓存一致性策略的玩具缓存. 缓存对应前台小黑板, 数据库对应后厨台账. """ def __init__(self, mode="invalidate", ttl=5.0, flush_interval=3.0): self.mode = mode self.ttl = ttl self.flush_interval = flush_interval self.store = {} # 缓存区, 存 CacheEntry self.db = {} # 源数据, 权威值 self.dirty = {} # 脏数据登记表, 写回策略专用 self.last_flush = time.time() def _write_db(self, key, value): """模拟数据库写入的耗时.""" time.sleep(0.05) self.db[key] = value def _flush(self): """把脏数据批量回写数据库.""" for key, value in self.dirty.items(): self._write_db(key, value) self.dirty.clear() self.last_flush = time.time() def write(self, key, value): """按当前 mode 执行写操作.""" if self.mode == "write_through": # 同步双写缓存与数据库, 强一致, 延迟为两者之和 self.store[key] = CacheEntry(value, time.time() + self.ttl) self._write_db(key, value) elif self.mode == "write_back": # 只写缓存并登记脏数据, 稍后统一回写 self.store[key] = CacheEntry(value, time.time() + self.ttl) self.dirty[key] = value if time.time() - self.last_flush >= self.flush_interval: self._flush() else: # invalidate # 删缓存旧值, 下次读回源拉新 self.store.pop(key, None) self._write_db(key, value) def read(self, key): """读缓存, 未命中或过期则回源.""" entry = self.store.get(key) now = time.time() if entry is None or entry.expire_at < now: value = self.db.get(key) if value is not None: self.store[key] = CacheEntry(value, now + self.ttl) return value return entry.value def run_demo(mode): cache = ConsistencyCache(mode=mode) cache.write("牛肉面", 15) cache.write("牛肉面", 18) cached = cache.read("牛肉面") db_value = cache.db.get("牛肉面") if db_value is None: db_value = "旧值(尚未回写)" print("[{}] 缓存读到: {}, 数据库: {}".format(mode, cached, db_value)) if __name__ == "__main__": for mode in ("write_through", "write_back", "invalidate"): run_demo(mode)
self.dirty = {}写回策略的脏数据登记表,记录“改了缓存但还没回写源”的条目,对应打烊后待补记的台账。if self.mode == "write_through":写穿透分支:缓存和数据库同步写,保证强一致。self.store[key] = CacheEntry(value, time.time() + self.ttl)写缓存并设置 TTL,TTL 是后续失效策略的兜底。self._write_db(key, value)同步写库,模拟数据库的固定写延迟。elif self.mode == "write_back":写回分支:只更新缓存,不立刻写库。self.dirty[key] = value把 key 标记为脏,等批量回写,这就是“先黑板后台账”的偷懒手法。if time.time() - self.last_flush >= self.flush_interval:每 3 秒触发一次批量回写,将脏数据摊薄到一批。else: # invalidate失效分支的核心:只删不写。self.store.pop(key, None)删除缓存旧值,下次读时自然回源。if entry is None or entry.expire_at < now:懒加载判断:未命中或 TTL 过期则回源数据库并重新填充。
常见误区
误区我以为设计缓存一致性,就是要让缓存里的数据和数据库里的数据在任意时刻都完全一样,分毫不差。达不到这个标准就是不合格的设计。
正解一致性是有强弱等级的。写穿透提供强一致,但要求每次写都双写,延迟高;写回和失效策略提供最终一致,允许在特定时间窗口内缓存短暂滞后。工程上按业务容忍度选择等级,而不是盲目追求“一模一样”。就像老王的面馆,时令菜允许问一下再确认,招牌菜则必须写准——不同数据可以有不同新鲜度要求。
为何常见:教材和面试题总强调“强一致”是黄金标准,初学者容易把“理想”当成“默认”,忽略现实中的性能代价。
误区写回延迟低、吞吐高,看起来完美,那生产系统直接全用写回策略不就行了?比写穿透和失效都高级。
正解写回有一个致命风险窗口:数据只写在缓存里、尚未回写数据库时,如果缓存宕机或进程崩溃,这部分数据就永远丢失了。订单金额、支付状态等不可丢数据绝不能用写回;只有日志、计数、画像特征这类可容忍少量丢失的数据才适合。选型不是选“最好”,而是选“最合适”。
为何常见:性能指标最直观,新手容易只盯着延迟数据,忽视数据安全与业务语义。
误区我以为设置 TTL = 5 分钟,就是每 5 分钟自动去数据库拉一次新数据,所以缓存内容永远是新的。
正解TTL 到期的动作只是“失效”:条目被标记为过期,下次读请求发现过期后才会回源数据库拉取最新值并重新填充(懒加载)。在到期到下一次读之间,数据并不会自动刷新;如果一直没人读,缓存就一直空着或留着旧值。这是“惰性过期”,目的是把刷新代价推迟到真正需要时。
为何常见:直觉上“过期 = 定时刷新”,但缓存设计的核心哲学恰恰是“按需加载”——不为没人读的数据白花算力。
误区缓存里的旧值碍事,那我先把它删掉,再去更新数据库,这样缓存里就不会有旧数据了。
正解先删缓存再写库有一个经典并发漏洞:线程 A 删掉缓存后、尚未写库时,线程 B 读缓存未命中,回源读到数据库旧值并写回缓存——于是旧值又回来了。正确的 Cache-Aside 顺序是“先更新数据库,再删缓存”,即使删除失败造成短暂不一致,也可以用 TTL 兜底收敛。这与老王“先改台账、再擦黑板”的顺序逻辑一致。
为何常见:人脑处理“清除脏数据”时容易只盯着缓存副本,忽略了并发读操作可能把旧值“填回去”。
误区大模型推理那么贵,把输出结果缓存起来,设个超大 TTL 甚至永不过期,反正答案又不会变。
正解模型输出只有在“模型版本、Prompt 模板、业务知识、用户权限”都不变时才有效。模型一升级、知识库一更新,旧缓存答案就是错的。生产实践常用“版本号加入缓存 key”或“版本更新时广播失效”,让旧缓存自然过期。要像老王换季换菜牌一样,主动擦旧写新。
为何常见:只看到推理算力贵,没意识到 AI 系统是持续演进的,缓存键的语义会随版本漂移。
面试题
Q1. 缓存一致性策略要解决的核心问题是?
- A.缓存容量不够用如何扩容
- B.缓存副本与源数据的同步← 正确答案
- C.缓存被并发删除的冲突
- D.缓存过期时间的精确计算
答案:B。缓存一致性聚焦“副本与源”之间的同步问题:源数据变化后,缓存副本如何跟上。选项 A 是容量规划,C 是并发控制,D 是 TTL 调优,都属于缓存工程的其他侧面。正如老王关心的是黑板如何跟上台账,而不是黑板本身的大小。
Q2. 写穿透(Write-Through)策略的核心特征是?
- A.只写缓存不写数据库
- B.先写缓存后异步写库
- C.缓存与数据库同步双写← 正确答案
- D.只删除缓存不写数据
答案:C。写穿透要求每次写操作同步完成“写缓存 + 写数据库”两步,因此读取方永远拿到最新值,属强一致。A 是“只写缓存”的错误做法,B 是写回策略,D 是失效策略。代价正如公式 T_write = T_cache + T_DB 所示,写延迟是两者之和。
Q3. 写回(Write-Back)策略的主要风险是?
- A.每次读请求都要访问数据库
- B.缓存未命中时延迟过高
- C.源数据滞后且崩溃可能丢失← 正确答案
- D.缓存容量占用率过高
答案:C。写回先把数据写进缓存并标记为脏,稍后批量回写数据库。在回写完成前,源数据滞后;若此时缓存节点宕机,脏数据就丢了。A、B 描述的是缓存未命中的通病,D 与容量相关,都不是写回特有的核心风险。好比老王打烊前失火,黑板上的新菜名就没了。
Q4. 失效(Invalidation)策略执行的核心动作是?
- A.更新缓存值为新值
- B.删除旧缓存后等待回源← 正确答案
- C.延长缓存过期时间
- D.同时写缓存与数据库
答案:B。失效策略在源更新时只“删除”缓存中的旧值,不主动写新值;下次读请求未命中后回源拉新,再填充缓存,即懒加载。A 是“更新”语义,C 是 TTL 调整,D 是写穿透。就像老王只把牛腩面擦掉,等客人问了再去后厨确认。
Q5. 多级缓存平均延迟公式 T_avg = h₁T₁ + (1-h₁)h₂T₂ + (1-h₁)(1-h₂)T_DB 中,h₂ 表示?
- A.数据库查询的命中率
- B.L1未命中时L2命中率← 正确答案
- C.写缓存的成功概率
- D.全部请求的缓存命中率
答案:B。h₂ 是条件命中率:在 L1 未命中、进入 L2 的请求中,被 L2 接住的比例。(1-h₁)h₂ 才是在全体请求中“L2 命中”的占比。A 把 h₂ 误当成数据库命中率,C 与写路径无关,D 混淆了整体与条件概率。
Q6. 关于 TTL 与数据新鲜度的关系,下列说法正确的是?
- A.TTL越大数据就越新鲜
- B.到期后缓存自动向源刷新
- C.变陈概率高且命中率更高← 正确答案
- D.只影响容量不影响一致性
答案:C。TTL 越大,数据变陈概率 P_stale = 1-e^{-λT} 越高(不够新鲜),但缓存命中率也越高(延迟更低),这就是权衡。A 说反了;B 是常见误解,TTL 到期只失效、不自动刷新;D 忽视了 TTL 正是控制一致性的关键旋钮。
Q7. AI 推理服务的模型输出缓存,最合理的失效触发方式是?
- A.每次推理前随机删除缓存
- B.模型版本更新时批量失效← 正确答案
- C.缓存永不过期不失效
- D.推理时重新计算覆盖缓存
答案:B。模型输出缓存的有效性绑定模型版本与 Prompt 模板。版本更新时,旧输出的语义已过时,应通过版本号 key 切换或广播失效批量清理。A 随机删除破坏命中率,C 会返回旧模型时代的错误答案,D 等于放弃缓存、失去加速意义。
Q8. 多级缓存架构一般包含哪些层级?
- A.本地内存进程内缓存← 正确答案
- B.Redis分布式热点缓存← 正确答案
- C.CDN边缘节点缓存← 正确答案
- D.浏览器端本地缓存
答案:ABC。典型多级缓存:L1 本地进程内存(最快)、L2 Redis 分布式共享(承接热点)、L3 CDN 边缘(离用户最近)。D 浏览器存储属于客户端侧,一般不纳入服务端多级缓存体系。它们的关系如同老王的速记条、小黑板与街口广告牌。
Q9. 以下哪些属于缓存失效策略的常见实现手段?
- A.主动删除如消息通知← 正确答案
- B.TTL定期过期失效← 正确答案
- C.懒加载回源刷新← 正确答案
- D.缓存预热批量预写
答案:ABC。失效策略的三种手段:源更新时通过消息队列主动通知各节点删除旧值;TTL 到期被动失效兜底;读未命中时懒加载回源并重新填充。D 的批量预写属于缓存预热,是“提前填充”而非“失效”,概念上不属于失效手段。
Q10. 关于缓存一致性的工程权衡,下列说法正确的有?
- A.强一致通常带来更高写延迟← 正确答案
- B.弱一致可降低写路径延迟← 正确答案
- C.读多写少适合失效策略← 正确答案
- D.写多读少适合写穿透策略
答案:ABC。写穿透强一致,但写延迟 = T_cache + T_DB,代价高(A 对);写回类弱一致把库写入异步化、摊薄成本,延迟低(B 对);读多写少时失效通知次数少,收益大(C 对)。D 说反了:写多读少应优先选写回策略,若选写穿透则每次写都双写,主路径被拖慢。