P16

缓存架构

点击正文段落选定收藏或划线位置;未选定时使用屏幕内第一段。

课内目录

1 缓存架构要回答什么

零售订单 API 在促销时会同时收到查询订单列表、打开订单详情、刷新支付状态等请求。许多请求重复读取相同内容,数据库却要反复执行查询、组织结果、传回数据。缓存把最近使用或预计会重复使用的结果暂存起来,让部分请求走更短的路径。它首先是减少重复工作的手段;如果业务不允许某项结果变旧,就不能仅因为缓存更快而改变业务承诺。

本课沿用零售订单 API 的场景:用户登录后查看自己的订单,订单经历待支付、已支付、已发货等状态,价格、支付与履约记录由数据库保存。假设容量评估使用的峰值是每秒 20000 次读请求,其中订单详情占主要部分。写入包括创建订单和更新状态。我们把数据库及其查询服务统称为源站;后面提到回源,就是缓存没有直接返回结果,需要访问这条权威读取路径。

先把需求写成能检验的句子。本人订单详情在正常条件下的响应时间目标为 P95 不超过 100 ms;P95 表示按响应时间排序后,约 95% 的请求不超过该值。普通列表允许短暂显示旧状态,但用户支付完成后的确认页必须核实最新支付记录。列表允许陈旧的目标窗口暂定为 30 s。这里的数值是本课设计输入,生产方案必须用业务要求和测量结果替换。

缓存架构要回答至少 5 类问题:哪些数据可以存,存在哪里,谁负责更新,数据多旧还能用,缓存出故障后请求去哪里。把它们串起来,才知道性能收益是否以错误结果、权限泄露或更大的故障范围为代价。只写“增加缓存提升性能”,既没有交代适用请求,也无法判断故障时数据库能否接住请求。

缓存通常不是权威记录。订单创建成功,意味着数据库事务已经按约定提交,不能只意味着某个缓存键写入成功。支付动作也不能依据缓存里的“待支付”直接再次扣款,而要由权威业务逻辑检查状态和操作资格。读展示结果与执行业务动作之间存在责任边界:前者可以在约定内稍旧,后者必须重新验证。

容量假设也要尽早摆出来。假设压测得出,在当前查询结构和其他业务负载下,数据库留给这组订单读取的安全预算为每秒 4000 次查询。这里的预算不是数据库宣传页上的极限值,而是满足延迟要求、保留余量后可以使用的份额。缓存失效后,如果每秒 20000 次请求全部回源,即使平时很快,系统仍然缺少可用的故障方案。

订单 API 的缓存决策边界
订单 API 的缓存决策边界

因此,本课会沿着需求、可选层次、读写路径、取舍记录、成本和高可用展开。缓存带来的收益必须与正确性条件同时交付:哪些请求更快,哪些请求仍需查源站,以及发生故障时哪些服务可以继续。后续所有设计都围绕同一组订单数据,避免在不同例子中悄悄更换一致性要求。

2 本地、分布式与 CDN 层次

本地缓存放在订单 API 进程中,读取不需要额外访问远端服务,适合体积小、变化慢、重复访问多的数据。例如订单状态码到展示文案的映射,或者公开的配送方式说明。每个 API 实例通常持有自己的副本,所以扩容后新实例最初没有缓存,旧实例之间也可能短暂看到不同版本。它的生命周期往往跟随进程,重启后内容可能全部消失。

分布式缓存由多个 API 实例共同访问,适合跨实例复用的订单详情摘要。请求需要经过网络,但共享副本减少了重复回源,也方便集中设置有效期和容量边界。共享并不等于永远可用,更不等于强一致。网络超时、节点切换、容量不足、数据淘汰都可能让读取失败,方案要为这些情况分别规定处理方式。

CDN 把可缓存内容放到靠近访问者的位置,适合公开图片、脚本和明确允许共享的公共页面。本人订单列表与订单详情包含个人信息,本课默认禁止进入共享 CDN 缓存。静态资源走 CDN,个性化 API 继续经过认证与授权。使用 HTTP 缓存控制时,可对敏感订单响应设置 Cache-Control: no-store,并核对边缘规则是否确实遵守;不能只看应用代码里有没有缓存调用。

本地缓存、分布式缓存与 CDN 的适用边界
本地缓存、分布式缓存与 CDN 的适用边界

缓存键必须表达数据身份。示意键 order-detail:v1:{tenantId}:{orderId} 包含租户、订单和结构版本,但键名本身不是权限控制。服务仍要核验访问者是否有权查看该订单;如果结果因角色、语言或字段权限不同而变化,还要区分结果版本,或者缓存内部对象,再在授权后生成用户响应。不要把甲用户可见的完整结果直接交给乙用户。

多个层次并非越多越好。每增加某层,就多出容量、失效、观测和故障处理责任。如果订单详情同时进入本地和分布式缓存,更新时只删除分布式副本,本地副本仍可能继续返回旧值。对于本课的起步方案,可以只在本地缓存静态配置,在分布式缓存保存允许短暂陈旧的详情摘要;通过压测证明有必要后,再评估是否增加动态数据的本地副本。

还要区分存储形式。按订单保存对象,用户订单列表变化时,可能只需重建列表与相关订单对象;直接缓存完整列表页面,虽然返回方便,却可能因新增订单、排序变化、分页变化而牵涉更多键。键粒度越粗,读取组装越简单,失效影响范围也往往越大。入门阶段先画出“某次业务变化影响哪些结果”,比研究复杂缓存算法更有价值。

比较可选方案时,可以建立简短表格:只优化数据库实现最简单,但重复查询仍多;加入共享缓存可减少回源,却引入网络依赖和一致性窗口;再加入动态本地缓存可进一步降低读取开销,但增加副本管理难度。选择应由实测瓶颈和业务窗口推动。数据库已有合理索引、查询范围受控,仍然存在大量重复读取,才有充分理由让缓存承担这部分压力。

3 一致性窗口与失效策略

一致性窗口指业务变化发生后,读取结果可能尚未反映变化的时间范围。它不是“偶尔错点也没关系”的含糊说法,而是需要明确起点、终点和适用请求的承诺。例如从订单状态事务提交,到普通订单列表能展示该状态,目标为 30 s 内;支付确认接口则从一开始就走权威读取,不把这个展示窗口当作自己的正确性标准。

最常见的入门读取方式是旁路缓存,也叫 cache-aside:应用先查缓存,命中就返回;未命中时查询数据库,再把结果写回缓存。缓存不自动知道数据库发生了什么,应用必须组织更新策略。为每个条目设置 TTL,也就是存活时间,可以让条目到期后被重新读取,但 TTL 控制的是缓存条目寿命,不自动约束所有业务结果的陈旧时间。

例如订单详情缓存的 TTL 为 30 s,本地副本又在每次读取分布式缓存结果后重新计时 30 s。如果分布式副本已经接近到期,本地仍可能把同一份旧结果保留更久。若确实需要动态多级副本,应传递原始生成时间或绝对到期时间,让下游层继承剩余有效期。源站读取本身若有延迟,或者某条路径允许使用过期数据,也必须计入业务窗口。

数据库更新与缓存失效的时间窗口
数据库更新与缓存失效的时间窗口

对本课的普通订单详情,可以采用“先提交数据库,再删除对应缓存”的基础策略。先提交保证权威状态已改变,再删除使后续读取有机会重新装入新值。如果删除失败,不能把数据库已经成功的提交简单说成没有发生;应记录失效失败并重试,同时由有限 TTL 提供恢复机会。重试时间、告警和窗口是否仍满足要求,都要写进方案。

这个顺序仍可能遇到并发竞争:某个读取先查到旧状态,另个请求随后提交新状态并删除缓存,较慢的旧读取最后才把旧值写回。此时删除已经发生,旧条目却重新出现。短 TTL 能缩短影响,却不能证明该竞争不存在。可以先对必须最新的读取绕过缓存,对一般展示设置可接受窗口;需要严格保证的版本校验等机制留到进阶实现,不用模糊承诺代替验证。

另种思路是在更新数据库后主动更新缓存,优点是热门条目不一定经历冷启动,但它同样面对更新失败、并发覆盖和多副本顺序问题。不要把“删除”与“更新”理解成正确与错误的固定搭配。入门方案优先选择团队能解释清楚、能监测失败、能恢复的流程,并明确数据库与缓存之间没有因为连续调用就变成原子操作。

TTL 应按业务类别制定。公开的状态文案可以保存较久,普通订单详情采用较短有效期,支付确认读取权威记录。缓存不存在的订单时,可短暂保存“未找到”以减少重复查询,但必须先处理身份与访问边界,并考虑订单刚创建时的可见性。对超时、权限失败和不存在不能统一缓存为空,否则临时故障会被放大为持续的错误结果。

最后,要把“窗口目标”与“实现保证”分开写。30 s 是业务要求,能否做到取决于源站新鲜度、回填竞争、失效重试及所有缓存层的期限。评审时逐项说明依据,无法证明覆盖的路径就缩小适用范围,或让该路径走权威读取。这比宣称整个订单系统都“最终一致”更容易检查,也更能保护关键交易。

4 动手:画一条多级缓存读路径

现在拿出画布,把“查看本人订单详情”画成可以逐步执行的路径。入口先完成认证与授权,再决定请求是否允许使用缓存。支付后的确认请求进入权威读取分支;普通查看请求进入共享缓存分支。本地层只提供静态展示配置,避免为了练习多级缓存而把动态订单结果复制到每个进程。图中出现多个层次,不表示每份数据都经过全部层次。

订单详情读取、回源与回填路径
订单详情读取、回源与回填路径

沿普通读取分支标注键的构造、缓存读取超时、命中条件和返回内容。命中条件不仅是键存在,还包括结构可识别、未过期、符合当前访问范围。缓存服务返回错误或超时,与正常未命中应有不同记录。前者可能意味着整体故障,后者可能只是用户首次打开订单;如果指标混在一起,值班人员就难以判断该扩容缓存还是检查网络。

未命中后,不要直接画箭头冲向数据库。先画回源准入,限制同时查询数和单位时间内允许通过的请求数。通过准入的请求查数据库,返回结果后尝试回填,再向用户响应。回填失败通常不应让已经成功读取的请求失败,但需要计数和告警,否则每次请求都可能重新查库。未通过准入时,按业务约定返回忙碌提示或允许的旧摘要,不能把它伪装为最新订单。

动画中按顺序观察正常命中、首次未命中、支付确认和缓存超时。请注意,缓存超时会消耗请求的时间预算;如果总预算已经接近 100 ms,再执行耗时查询就可能超时。缓存访问、数据库访问和返回处理应分配预算,具体数值由测量决定。不要在失败后无限重试,尤其不能让每个层次各自重试多次,最后把流量成倍交给源站。

接着画写入旁线。状态更新先提交数据库,再删除对应详情和相关列表结果,并记录失败后如何重试。创建订单的结果响应可以直接使用本次提交的权威结果,不必先绕到缓存再读回来。客户端随后刷新页面时,是否需要短时间走权威分支,应由读己之写的体验要求决定;这里的读己之写是用户希望立即看见自己刚完成的操作。

为读路径补上热点合并:同个 API 实例内,同个键同时出现许多未命中时,可以让部分请求等待正在进行的回源结果,避免每个请求独立查库。注意实例内合并不能天然覆盖所有实例,也无法替代总回源限制。等待本身要有上限,超时后仍回到既定降级策略。图中明确它的作用范围,就不会把局部优化误当成全局保护。

最后沿着图做桌面演练。分别假设缓存为空、缓存不可达、数据库变慢、删除缓存失败,说明每条箭头会发生什么。再让同事只看图回答:哪类订单结果可能是旧的,哪里检查权限,谁限制回源,哪个失败会影响成功响应。若这些问题必须靠作者口头补充才能回答,图还缺少关键决策。动手产物是可审查的路径,而非只画服务方框的拓扑。

5 命中率与正确性取舍

命中率是命中请求数除以进入该缓存层的读取请求数。假设每秒 20000 次请求都符合共享缓存条件,命中率为 95%,则每秒约有 1000 次未命中。再假设每次未命中只触发 1 次数据库查询,后台读取为每秒 100 次,总量约为 1100 次。这只是容量估算,分页查询、重试、热点合并和权威读取都会改变真实查询数,必须在测量中单独计入。

如果命中率下降到 80%,未命中就变成每秒 4000 次,加上后台读取达到 4100 次,超过前述 4000 次安全预算。命中率只下降了 15 个百分点,前台回源量却增加到原来的 4 倍。性能方案应按未命中量计算负载,而不能只说命中率“仍然很高”。峰值请求量变化时,即使命中率相同,数据库承担的绝对压力也会不同。

交互把命中率、允许回源比例和 TTL 分开调节。命中率来自测量或场景假设,并非由 TTL 自动计算。允许回源比例下降可以保护数据库,但会增加未获服务的请求。TTL 主要提醒你检查陈旧窗口;更长的 TTL 可能有助于复用,却不能在没有访问分布和更新频率的条件下推出命中率。把未知关系画成确定曲线,会让方案产生虚假的精确感。

从请求量、命中率到回源预算的观测链
从请求量、命中率到回源预算的观测链

命中率还需要业务维度。公共配置很容易命中,个人新订单未必重复访问;把它们合成平均值,会遮住高价值接口的压力。至少按接口、缓存层、数据类别统计命中、正常未命中、超时、淘汰和回源量,同时观察 P95 延迟。热点用户或热门订单需要查看分布,但监控标签不能直接放入无限增长的订单号,避免监控成本随订单规模失控。

正确性要用独立证据衡量。可以对受控样本核对缓存与权威结果的状态、版本和生成时间,记录超出业务窗口的比例。抽样过程也会增加数据库读取,必须纳入预算;敏感字段不要原样写进日志。投诉“支付后仍显示待支付”与缓存命中率高并不矛盾,恰恰可能是旧值持续命中。因此性能面板与正确性面板要能对照同个时间范围。

成本也不能只比较内存价格。需要估算缓存对象数量、平均大小、结构开销、复制副本及容量余量,还包括网络传输、可用区间流量和维护时间。比如 1000000 个平均 2 KB 的对象,原始内容约为 2 GB,采用十进制估算;真实占用还要加键、元数据和分配开销,不能把 2 GB 直接当作采购容量。高可用副本进一步增加总资源需求。

选择方案时可以写成简短的架构决策记录,也就是 ADR:背景是峰值重复读取超过预算;选择是共享详情缓存、静态本地缓存和有限回源;理由是满足展示窗口并减少重复查询;代价是新增依赖、失效处理和监控成本。保留数据库优化与动态本地缓存作为比较项,并写出重新评估条件。这样命中率、正确性与成本共同支持选择,而不是谁的指标更漂亮就选谁。

6 常见坑:缓存雪崩后击穿数据库

缓存雪崩通常指大量条目在相近时间失效,或者缓存层整体不可用,导致许多请求同时回源。缓存击穿常用于描述某个热点键失效后,大量并发请求反复查源站。两者范围不同,但都可能把平时被缓存挡住的请求突然交给数据库。本课关注它们如何连成故障链,而不要求先记住复杂分类。

设想促销开始前统一预热了大量订单摘要,又把 TTL 都设为 30 s。约 30 s 后,许多条目集中到期。未命中增多,数据库响应变慢,请求占用连接的时间延长,队列随之增长。客户端以为请求丢失而重试,API 又重试查询,输入负载进一步增加。即使缓存服务本身已经恢复,数据库也可能仍被堆积请求拖住。

给 TTL 增加受控的随机偏移,可以分散独立条目的到期时刻,但要遵守业务上限。例如普通列表要求在既定条件下不超过 30 s,可以在 20–30 s 内分散到期,而不是直接使用 30–60 s。随机到期不能防住缓存整体宕机,也不能解决同个热点键失效。防护措施必须对应具体故障,不能因为加了随机数就认定雪崩已经解决。

缓存异常到数据库过载的排查与保护路径
缓存异常到数据库过载的排查与保护路径

保护的核心是把回源量控制在可承受范围内。设置全局容量目标,并落实到 API 实例的并发限制、速率限制和数据库连接预算;实例扩容时,应重新分配额度,避免实例越多总回源上限越高。遇到缓存超时可暂时减少反复访问故障缓存,但随后绕行数据库仍须经过准入。所谓熔断是暂时停止尝试某条故障路径,不是凭空产生额外的源站容量。

降级要按业务制定。公开说明可以使用已知版本;普通订单列表若允许过期摘要,应标注更新时间并规定最长使用期限;支付确认不满足权威读取条件时,应提示稍后查询,而不是返回猜测状态。不存在允许旧数据的前提,就选择可解释的拒绝或稍后重试。避免用“返回默认成功”维持表面可用,那会把性能故障转变为业务错误。

高可用还涉及故障域。共享缓存可以跨独立故障域部署副本或采用具备故障切换能力的服务,但要核实切换耗时、可丢失的缓存内容和客户端重连行为。缓存副本主要帮助恢复访问,不能替代权威数据备份。切换期间缓存可能接近空库,所以仍要预留冷启动策略:限速回填、分批预热、优先恢复高频且可缓存的数据。

排查时沿时间线看证据:缓存错误是否先升高,未命中是否同步增加,回源速率何时超过预算,数据库延迟和连接等待何时恶化,客户端重试是否放大流量。先实施已有的回源保护与降级,再恢复缓存和逐步放量。演练应覆盖整体不可达、部分键集中到期与热点键失效,记录数据库是否守住预算,以及用户实际收到什么响应。

7 案例:错误的全局失效打满源站

某次发布需要修改订单状态的展示文案。团队把“文案更新”理解为“所有订单缓存都应该更新”,执行了整个订单命名空间的失效操作。缓存进程没有宕机,网络也正常,但有效条目几乎同时消失。每秒 20000 次读取突然大部分回源,数据库的安全预算只有每秒 4000 次,排队迅速增长,订单详情开始超时。

值班人员最初看到缓存资源使用率下降,以为缓存压力已经减轻;看到数据库负载升高,又尝试临时增加 API 实例。新增实例带来更多并发查询,进一步扩大连接等待。这个案例的关键不在于某条删除命令的语法,而在于把业务变更范围错误映射为全局失效范围,并且没有独立于缓存命中率的回源保护。

展示文案更新引发全局失效的故障复盘
展示文案更新引发全局失效的故障复盘

恢复时,先限制普通详情回源,保留必要的权威读取预算,对超出能力的普通查询返回明确的繁忙提示。随后按读取热度逐步回填,而不是对全部历史订单立即发起查询。确认数据库延迟和连接等待回到安全区后,再小步提高准入额度。整个恢复过程要观察实际查询量,因为回填任务本身也会与线上请求竞争同份容量。

“撤销删除”通常不能恢复已经丢掉的缓存内容。回滚发布能停止继续失效,却不意味着所有缓存立即回来;同理,直接重启数据库不会消除错误的流量来源。应先停止触发源,稳住负载,再等待或执行受限回填。值班手册如果只写“回滚并观察”,就没有解释缓存变冷后的这段风险,需要补上明确的准入与恢复条件。

复盘后重新划分数据:订单对象保留状态码,展示文案作为独立的版本化配置。文案变化更新配置即可,不需要清空所有订单详情。如果某次发布确实改变订单对象结构,可以使用新键版本并分批切换,让新旧版本短暂并存。版本切换也会导致新空间变冷,仍需容量计划;版本号只是缩小影响和组织迁移的手段,并不免除回源成本。

变更流程还应提供范围预览:此次操作影响的是配置键、某类订单,还是全部租户的订单详情;预计条目规模是多少;清空后可能产生多大回源。批量失效要支持分批、暂停和监控,不让操作人员只能在“全部执行”与“不执行”之间选择。对于业务必要的立即失效,不能为了平滑流量而继续泄露旧数据,应把强制失效与更严格的准入同时启用。

最后更新 ADR:全局失效只能用于有明确依据的场景,普通文案发布使用配置版本;上线必须检查冷启动回源预算;演练加入“缓存服务正常但条目被清空”。验收看 3 项证据:受影响范围符合预期,数据库查询没有越过安全预算,用户响应符合降级约定。这样,案例沉淀为架构约束和操作能力,而不只是提醒大家以后小心。

8 把缓存层次写进性能方案

性能方案应先列接口分类,再列组件。订单列表与普通详情可以使用有限陈旧的共享缓存,支付确认和改变订单状态的动作按业务规则访问权威数据。CDN 只承接公开静态资源,本地缓存只保存静态配置。每类请求说明峰值、延迟目标、允许陈旧窗口和授权边界,组件选择才有可追溯的依据,也便于评审者发现遗漏的关键路径。

接着用同组假设比较选项。方案甲仅优化数据库查询与索引,部署简单,但峰值重复读取可能超过预算;方案乙增加共享缓存,能够复用详情结果,却需要失效、保护和切换能力;方案丙再加入动态本地副本,可能减少网络往返,但一致性和实例冷启动更复杂。本课选择方案乙,并保留数据库优化作为基础工作,后续根据实测延迟决定是否继续增加层次。

把容量计算写成有条件的估算:普通缓存读取每秒 20000 次,实测目标命中率 95%,每次未命中对应 1 次数据库查询,其他读取每秒 100 次,则正常总量约为 1100 次。权威读取、后台任务、重试若不在这 100 次内,必须另行加入。还要计算低命中率和整体不可用时的负载,证明准入会把回源限制在安全预算内,而不是只展示正常场景。

成本表至少包括对象容量、容量余量、高可用副本、网络流量和运维投入。缓存节省的数据库工作也应量化,但不能先假定数据库资源可立即缩减:数据库仍要承接写入、权威读取和受控故障回源。先测量长期峰值与恢复过程,再决定是否调整资源。为了降低正常账单而拿掉全部故障余量,可能让架构只在缓存完美工作时成立。

缓存故障域、源站保护与恢复顺序
缓存故障域、源站保护与恢复顺序

高可用章节需要写清缓存故障影响哪些接口、切换期间允许什么行为、恢复后如何限速回填。跨故障域副本能降低部分基础设施故障的影响,但错误的全局删除可能传播到所有副本,因此还需要操作范围控制。应用和缓存的部署也应考虑相关故障:如果全部实例与缓存入口依赖同个不可用区域,再多逻辑副本也无法获得预期效果。

风险清单可以围绕责任写成可执行句子。应用负责人维护键结构、授权和失效逻辑,平台负责人维护故障切换与容量告警,业务负责人确认陈旧窗口和降级文案,值班负责人维护操作与演练记录。职责可以由同个团队承担,但不能留下“由缓存自动处理”的空白。尤其要明确谁在失效重试持续失败时做出收紧准入或绕过缓存的决定。

上线按观察、少量流量、逐步扩大进行。先采集实际查询分布与对象大小,再让部分普通详情启用缓存,对照延迟、回源、错误与陈旧样本。增加流量前确认保护规则已生效;回退时也要分批降低缓存依赖,避免所有请求同时改为查库。验收包括正常峰值、低命中率、缓存不可达和全局清空,保留曲线与结论,作为 ADR 的证据附件。

9 从缓存架构走向消息与异步架构(P17)

缓存解决了重复读取,但没有自动解决变化如何可靠传播。订单支付完成后,可能需要失效详情缓存、更新列表、通知履约、生成用户通知。如果把这些工作全部串在支付请求里,某个下游变慢就会拖住响应;如果只提交数据库然后随手启动后台任务,进程退出又可能丢掉这次处理。读路径变短以后,变化处理的责任反而更加清晰。

消息与异步架构把部分后续工作从当前请求中移开,让生产变化的服务与处理变化的服务可以独立安排节奏。订单状态提交后产生变化事件,消费者据此处理缓存失效等工作,是常见方向。但“发送事件”不能被当作“所有缓存都已更新”。队列积压、消费失败和重试都会延长展示窗口,原来在接口内的等待,可能变成用户看到旧状态的时间。

在本课范围内,先记录需要传播的事实及责任:订单标识、所属租户、状态版本、变化发生时间、处理结果。避免为了方便把整份包含敏感信息的订单对象发送给所有下游。事件字段和权限仍需业务设计,不能因为经过消息通道就默认安全。哪些消费者确实需要哪些数据,是后续方案评审的重要问题。

异步失效也会遇到重复与先后顺序。消费者可能再次收到已经处理过的变化,因此“再删同个缓存键”通常比“再扣款”更容易安全处理;但失效之后的旧读取回填竞争依然存在。先知道这些边界即可,消息投递语义、幂等和顺序控制将在 P17 继续展开。本课不把引入消息系统当作缓存一致性问题的自动答案。

需要立即反馈的支付确认仍然遵守原来的权威读取约定。普通列表可以等待异步更新,却必须有可观测的延迟与恢复路径。假设列表允许 30 s 陈旧,消费积压已经达到 25 s,就不能只看缓存 TTL 仍然正常;处理耗时、回填与多级副本都在消耗同份时间预算。下一章的容量规划要把积压与处理速率纳入视野。

还要防止恢复过程出现新的冲击。消息消费者暂停后恢复,可能短时间处理大量失效事件,让刚回暖的缓存再次变冷。后续方案应协调消费速率、失效范围和回源预算,而不是把消息队列当作无限缓冲。缓冲可以推迟工作,无法取消工作总量,也无法替数据库增加查询能力。缓存与异步系统必须共享对源站容量的认识。

到这里,交给 P17 的输入已经具体:哪些订单变化需要传播,哪些读取可以等待,最长等待多久,消费失败如何被发现,恢复是否会重新制造冷启动。缓存架构完成的是读路径与副本边界的设计,消息与异步架构继续安排变化的可靠处理。沿用同个订单案例,才能看清新增组件承担的责任,以及仍留在数据库和业务接口上的正确性约束。

放进方案里

本方案面向零售订单 API,将普通订单列表和详情的重复读取交给共享缓存,将静态展示配置放入本地缓存;CDN 仅用于公开静态资源,个人订单响应禁止共享缓存。数据库保留权威记录,支付确认与状态变更执行权威校验。普通列表的陈旧目标窗口为 30 s,实际保证需要覆盖读取源、失效失败、回填竞争及全部副本期限。

读取采用旁路缓存,先认证授权,再查共享缓存;未命中和缓存异常分别计数,回源统一经过准入。状态更新先提交数据库,再失效相关详情和列表结果,失败进入可观测的重试流程。所有条目设置有限 TTL,在业务上限内分散到期;必须最新的请求绕过展示缓存。批量变更提供影响范围预览,避免文案更新触发全局清空。

按峰值每秒 20000 次可缓存读取、95% 命中率、每次未命中触发 1 次查询及每秒 100 次其他读取估算,正常回源约为每秒 1100 次。数据库安全预算暂定每秒 4000 次,需以压测确认;缓存整体不可用时依靠限流、并发限制和明确降级守住预算。成本覆盖对象开销、容量余量、高可用副本、网络与维护投入,不能只按原始内容大小采购。

ADR 记录选择共享缓存的依据、未采用动态本地副本的原因及重新评估条件。上线验证正常峰值、集中到期、全局清空和故障切换,观察延迟、命中、回源、错误及陈旧样本。应用、平台与业务分别确认失效责任、恢复流程和允许展示的旧数据;恢复采用限速回填和逐步放量。后续由 P17 补充订单变化事件与异步失效的可靠处理,并把消费延迟纳入同份一致性预算。

10 小结:下一步消息与异步架构(P17)

缓存架构把可重复利用的读取结果放到合适层次,同时明确权威数据、访问权限、陈旧窗口和故障路径。命中率需要换算为绝对回源量,性能收益需要与正确性、成本和高可用共同评估。全局清空与缓存宕机都可能制造冷启动,回源保护必须在平时就已经生效。

自测时,尝试说明:为什么支付确认不能直接相信旧缓存?95% 的命中率下降到 80% 时,回源量如何变化?删除全部缓存之后,数据库靠什么避免过载?如果普通列表允许陈旧 30 s,异步失效积压会占用哪部分预算?能沿读写路径回答这些问题,就可以带着明确的传播需求进入 P17,继续设计消息与异步架构。

同阶段相关课

AI 答疑

默认关闭。启用后,只发送本课标题、最近阅读的屏幕内正文摘录(最多 6000 字)与本次问题。

配置自己的服务