← 返回题目列表

多级缓存的 TTL 应该如何分层设计?

中等 第 17 / 26 题 更新于 2026/09/30
多级缓存TTL本地缓存一致性
学习 AI 实战项目

简化版

多级缓存 TTL 不应所有层相同。越靠近调用方的 L1 通常容量小、失效传播弱,TTL 应更短;共享 L2 可更长并作为回源缓冲;各层 TTL 还要受业务最大陈旧时间约束,并配随机抖动、版本校验和主动失效,不能靠 TTL 单独保证一致性。

详细版

设计先确定业务允许旧多久 S,再分配 L1、本地进程缓存,L2/Redis,边缘/CDN 和负缓存的生命周期。常见关系是 L1 数秒到数十秒、L2 数分钟到小时,但没有固定比例;更新频率、加载成本、访问热度和失效通道可靠性决定实际值。

L1 命中时绕过 L2,所以 L1 的最坏陈旧不能超过 S。主动失效事件可缩短平均陈旧,但事件会丢,TTL 是最终自愈上限;value 带数据版本可阻止旧回填。各层 TTL 加独立抖动,避免同一批 Key 同时失效;负缓存 TTL 通常更短,防止合法新建记录长期不可见。

评估时不仅看命中率,还看陈旧值年龄、各层 miss/回源、失效延迟和数据库压力。更新热点可使用逻辑过期+后台刷新,权限/库存等高风险数据则缩短或绕过某层,不能为了命中率牺牲正确性。

完整版教学

一、多级 TTL 要解决什么矛盾

L1 本地缓存延迟最低,但每个实例各有副本,失效最难保证;L2 分布式缓存集中可控,却多一次网络;数据库最权威也最昂贵。TTL 在命中收益、数据新鲜度和回源压力之间做时间预算。

如果 L1 与 L2 都设 30 分钟,数据库更新并删除 L2 后,L1 仍可能旧 30 分钟;如果所有层都设 1 秒,系统几乎退化成频繁回源。应先由业务风险给出最大陈旧上限,再分层。

记忆钩子:TTL 是“最迟多久自愈”的上限,不是精确失效时刻,也不是一致性协议的替代品。

二、先确定业务陈旧预算

不同字段可接受时长不同。商品描述可旧 5 分钟,价格可能只允许 10 秒,库存下单时必须权威校验,权限撤销可能要求近实时。不要因它们属于同一个对象就共用一个大 JSON 和统一 TTL。

数据最大陈旧预算示例设计倾向
商品文案5 分钟L1+L2,较长 TTL
展示价格10 秒短 L1、主动失效
库存展示3 秒短缓存,下单回源
权限撤销接近实时版本/事件,高风险操作回源
不存在结果10~60 秒短负缓存

数字只是例子,实际应由业务损失和更新频率确定并写入 SLO。

三、L1 与 L2 的时间关系

通常 L1 TTL 小于 L2,因为 L1 数量多且无法统一原子失效。假设业务允许旧 60 秒,可设 L1 10 秒、L2 5 分钟,但数据库变更通过事件删除 L2;注意 L2 的 5 分钟并不直接等于用户旧 5 分钟,因为主动失效负责正常路径,TTL 负责漏事件自愈。

若没有可靠主动失效,L2 TTL 必须也不超过业务陈旧预算。不能写出 L1=30s, L2=1h 后声称最坏只旧 30s:L1 过期会从仍旧的 L2 重新加载,再延长一轮。

无主动失效:最坏旧值可能被下层旧缓存反复续命
有版本/主动失效:TTL 作为通知失败后的上限

因此读路径回填 L1 时应保留下层值的生成时间,不要把“剩余寿命”重置成完整 TTL。

四、剩余 TTL 和生成时间为何重要

L2 中值已存在 290 秒、总 TTL 300 秒,L1 读取后若再缓存 60 秒,可能把生命周期延长到 350 秒。更稳妥是 value 带 generatedAt/dataVersion,L1 TTL 取本层上限与下层剩余新鲜预算的较小值。

business_stale_limit = 300s
value_age = now - generatedAt = 290s
L1 configured_ttl = 60s
effective_L1_ttl = min(60s, 300s - 290s) = 10s

若剩余预算已小于加载和使用安全余量,应触发刷新而不是继续层层复制旧值。这个原则能防止多级缓存“续命”。

五、TTL 抖动与逻辑过期

批量预热的 Key 若都设 1800 秒,会在半小时后一起失效。可以使用 baseTTL ± jitter,例如 1800±300 秒,将回源摊开。各层抖动应独立,避免 L1/L2 同时到期。

超热点不适合让第一个请求同步回源,可使用逻辑过期:物理 Key 保留更久,value 中记录逻辑过期时间;过期后一个后台刷新者更新,其他请求在允许范围内读旧值。它牺牲短暂新鲜度换稳定性,必须限制 stale 上限。

负缓存的 TTL 更短,因为不存在状态可能很快因新建而改变。删除/创建事件应主动清除 tombstone,避免用户刚注册却长期显示不存在。

六、主动失效、版本与 TTL 如何配合

数据库更新后通过消息使 L2 和各实例 L1 失效,可显著缩短平均一致性窗口。但消息会重复、延迟或丢失,因此失效操作幂等,客户端按版本拒绝旧事件,TTL 保证最终清理。

value 带版本还可防旧查询晚回填。A 读到 v10 后卡住,B 已缓存 v11,A 只能在当前版本不高于 v10 时写入,否则丢弃。TTL 解决时间边界,版本解决并发顺序,两者职责不同。

正常:DB v11 -> invalidation(v11) -> L2/L1 删除 -> 回填 v11
漏通知:旧值最多存活到 TTL/陈旧上限
乱序回填:版本检查拒绝 v10 覆盖 v11

七、怎样用指标校准

分层记录 L1/L2 命中、miss、回源 QPS、value age、主动失效延迟、刷新失败、stale 返回和数据库成本。总体命中率高可能掩盖 L1 长期返回旧值,因此新鲜度指标必不可少。

灰度调整 TTL:先缩短/延长一个租户或实例组,观察命中收益与陈旧率。演练失效消息中断,确认 TTL 能在目标时间内自愈;演练批量预热,确认抖动能摊平过期。

l1_hit_rate: 92%
l2_hit_rate_after_l1_miss: 95%
value_age_p99_seconds: 18
stale_response_rate: 0.03%
invalidation_lag_p99_ms: 420
origin_qps: 760

八、常见误区与追问

  • 误区:L1 TTL 小于 L2 就一定不会返回过旧数据。 L1 可能反复从旧 L2 回填并重置寿命,需要生成时间/剩余预算。
  • 误区:有主动失效后可以永久缓存。 通知可能丢失,仍需 TTL 或版本审计作为自愈兜底。
  • 误区:所有层使用同一 TTL 最简单。 会同步过期并制造回源峰值,也忽略各层失效能力差异。
  • 误区:随机 TTL 会破坏业务一致性。 抖动只能在业务允许的新鲜度范围内使用,目的是分散刷新。
  • 追问:TTL 越短越一致吗? 平均陈旧可能降低,但回源和抖动增大;高风险场景还需版本/权威读,而非无限缩短。
  • 追问:逻辑过期何时适合? 超热点、允许短暂旧值且刷新昂贵时适合,不适合权限和强实时状态。
  • 追问:CDN TTL 怎么纳入? CDN 也是一层,必须共享业务 stale 预算并支持 purge/versioned URL,不能单独设置。

九、加强记忆

多级 TTL 按“先定陈旧预算、近端更短、禁止层层续命、通知加版本、过期要打散”设计。L1/L2 的具体数字来自业务风险与回源容量;value 保留生成时间和版本,L1 只使用剩余新鲜预算;主动失效缩短平均窗口,TTL 负责漏通知自愈,抖动负责避免同刻回源。