第 56 章 LLMOps II:缓存、故障应对、服务目标与反馈
米拉 连问两次相同的住宿问题。直接复用第一条经过核验的答案,看起来很合理:为什么还要再检索、再检查同样三行原文?随后,制度适用日期变了,另一个租户也提出了看似相同的问题,下游又停止响应。原本为了提速的改动,变成了正确性和可用性问题。
第 55 章已经标识了每次结果背后的版本。现在可以把问题问得更准确:什么条件下可以复用旧计算,新计算做不完时又该怎么办?答案既要说明用户会收到什么,也要说明维护者怎样把它计入统计。
56.1 条件仍然成立,才能复用结果
Section titled “56.1 条件仍然成立,才能复用结果”最简单的回答缓存(response cache) 把问题映射到以前算出的最终结果。如果键只有 Beijing lodging,2025 年的 600 就可能被拿来回答 2026 年的问题,即使检索程序与资料库都没有错。缓存键遗漏日期,绕过了系统本来要落实的检查。
缓存键(cache key) 标识准备复用的计算。本实现把经过认证的租户、用户和权限范围,全部四个请求字段,以及版本清单摘要组合起来。摘要覆盖数据、词法处理、上下文、回答/结构/Skill 代码和相关配置。问题字节必须完全相同;大小写或空格变化,可能只是少一次命中。这种保守选择节省的计算少于宽泛的规范化规则,却不会悄悄把不同问题当成同一个。
例 56.1:文字相同,适用答案不同。 同一资料库保留两份制度。请求 A 查询 2025-12-31 的 Beijing lodging,应当得到 v1 的 600;请求 B 使用相同文字,日期改为 2026-01-01,应当得到 v2 的 750。资料库身份无法区分两者,因为数据字节没有变化,还必须把日期放进键中。语言变化会改变返回的引文,访问主体变化也可能改变允许读取的记录,因此这些条件都要参与。
每次读取都要重新鉴权,返回已存任务结果前还要检查归属。查询回答缓存之前,服务会核对授权,而且本例根本不缓存订单读取结果。缓存命中可以跳过部分计算,不能跳过权限。它也不会更新用户的长期记忆,更不会教会模型什么。
表 56.1:四种名称相近、保存内容不同的缓存
| 缓存 | 保存什么 | 命中需要什么 | 不能提供什么 |
|---|---|---|---|
| 单请求内的 KV 缓存 | 前面位置的注意力数值状态 | 兼容的续写与模型计算 | 可直接复用的完整制度答案 |
| 前缀缓存 | 匹配输入前缀的计算状态 | ID、位置和计算条件匹配 | 访问权限,或仅仅意思相近 |
| 回答缓存 | 最终经过检查的结果 | 请求、访问和处理范围匹配 | 自动更新知识 |
| 语义缓存 | 与足够相似的查询表示关联的答案 | 相似规则,以及必需的权限/日期/版本检查 | 两个问题答案相同的证明 |
前两种在第 31 章已经介绍,用来节省模型计算;后两种位于应用层。本例默认只用精确匹配的回答缓存,不涉及神经模型状态。
56.2 到期是一条时间规则,不是真伪检测器
Section titled “56.2 到期是一条时间规则,不是真伪检测器”例 56.2:算清最后一个有效时刻。 一条回答在单调时钟读数 10 秒时写入,存活时间(time to live,TTL) 为 30 秒,到期时刻就是 秒。39.999 秒时,匹配的条目仍可使用;恰好到 40 秒时,就不能使用。这是由实现复核的教学时钟算例,不是服务实际运行寿命的测量。
用 表示写入时刻, 表示 TTL, 表示查找时刻,单位都为秒。时间上有效,要求:
严格小于号让边界没有歧义。代入后, 成立, 不成立。命中还要求键正确、当前授权有效;这条公式只描述时间条件。
假设经授权的新制度在时刻 20 生效。只设 30 秒 TTL,旧条目仍可能用到 40 秒,除非另有条件变化。应当更新资料库/版本身份及其兼容索引,让新请求无法命中旧键,或者明确使旧条目失效。TTL 只限制身份不变时最多复用多久,不会自己发现事实已经改变。
本地缓存最多保存 64 项,满了之后淘汰最久未使用的条目。保存和返回时都复制对象,避免一个调用方改动另一个调用方将要取到的内容。查找时拒绝过期项,后台也定期清理。切换版本后,即使问题不变,也会进入不同的缓存范围。有意恢复旧版本,意味着恢复旧处理条件,不能拿它掩盖必须接受的制度更新。维护者要分清:有问题的是应用配置,还是确实生效了一份新的权威资料。
56.3 问题很相似,答案仍可能不兼容
Section titled “56.3 问题很相似,答案仍可能不兼容”语义缓存(semantic cache) 可以在用户换一种说法时增加命中。它先把查询转成表示,找到接近的旧查询,再决定是否复用旧答案。精确匹配漏掉同义表达时,这很有吸引力,却也增加了一项新的判断:判断错了,损失不只是少省一次计算,而是返回了错误答案。
例 56.3:向量非常接近,金额却错了。 给历史日期查询指定向量 ,给现行日期查询指定 。这些坐标由作者手工设置,不是学到的嵌入。点积为 0.99,两个长度分别为 1 和 ,所以余弦相似度约为 0.999949。若阈值为 0.99,就会接受这次匹配,虽然两个问题需要的金额分别是 600 和 750。
把阈值提高到 0.999,仍然会接受。相似度描述的是这组坐标,不能证明日期等价。即使两种说法确实同义,也要满足租户、时间、版本和任务条件。对这个小服务而言,目前没有足够证据支持增加一条语义接受规则,所以默认关闭语义缓存。将来的实验需要固定的误匹配案例、独立答案标签,并测量省下的工作与错误复用之间的取舍。
56.4 在工作压垮服务之前,控制接纳
Section titled “56.4 在工作压垮服务之前,控制接纳”缓存能帮助重复请求,连续不断的新问题却仍然要计算。第 54 章把活动回答进程限制为两个,我们还需要限制一个访问主体发起新工作的速度。否则,一个调用者就可能反复占满全部位置。
令牌桶(token bucket) 保存一份数值额度。这里的“令牌”是请求许可,不是语言模型 Token。桶有容量上限,按规定速度补充,每次允许发起请求就花一个许可。因令牌不足而被桶拒绝的尝试不消耗许可,但拒绝本身仍要可观察。本服务默认容量为四个许可,每秒补充两个。下面改用更小的数,便于逐项核对。
例 56.4:跟踪一个容量为二的桶。 桶一开始装满,容量为 2,每秒补充 1 个许可。三个请求同时在时刻 0 到达:第一个用完后剩 1,第二个之后剩 0,第三个被拒绝。到 0.5 秒,积累了半个许可,仍不够,因此又一次请求被拒绝。到 1 秒,已有一个完整许可,可以使用。继续计算如下;时间都是教学给定的秒数,不是流量实测。
表 56.2:先补充,再判断是否够用
| 到达时刻 | 使用之前的许可数 | 接纳? | 使用之后剩余 |
|---|---|---|---|
| 0,第一个请求 | 2 | 是 | 1 |
| 0,第二个 | 1 | 是 | 0 |
| 0,第三个 | 0 | 否 | 0 |
| 0.5 | 0.5 | 否 | 0.5 |
| 1 | 1 | 是 | 0 |
| 1.5 | 0.5 | 否 | 0.5 |
| 2 | 1 | 是 | 0 |
若上次观察后剩余 个许可,补充速度为 ,经过了 秒,容量为 ,那么使用之前的许可数为:
的单位是许可数。从 0.5 秒到 1 秒,、、,得 ,接纳后再减去一。取最小值,是为了防止闲置十秒就攒出超过容量 2 的额度。令牌桶允许短时突发,也约束持续速率,却不保证工作进程一定空闲:有许可的请求,仍可能因为全局并发位置已满而收到 503。
实际 HTTP 测试分别制造了限流的 429 和容量占满的 503。立即重试,只会给引发失败的条件再添负担。响应会给出等待提示;用户稍后发起的请求仍受当时容量和自身截止时间约束,不能把等待提示理解成一定成功的承诺。
56.5 下游持续失败时,暂时不要再调用
Section titled “56.5 下游持续失败时,暂时不要再调用”暂时性故障可能值得有限重试;持续中断却会让反复尝试白费工作。本地故障程序在处理真实 HTTP 任务时,人为抛出“下游不可用”异常。每个任务最多尝试两次只读计算,中间等 50 ms。这是在给定故障条件下实际执行的软件,不是在测量某个外部提供方的中断。
熔断器(circuit breaker) 根据观察到的失败,决定是否还要发起调用。处于闭合(closed) 状态时允许调用;达到失败阈值后进入断开(open) 状态,立即拒绝;冷却结束后,半开(half-open) 状态只允许少量探测。探测成功就闭合,失败则再次断开。熔断器在依赖修复期间保护调用方,并不负责修复依赖本身。
例 56.5:数失败请求,不是数尝试。 手算数据规定,连续两个请求失败后断开,冷却五秒。时刻 0,一个请求用完允许的尝试后失败,计数为 1。时刻 1,另一个请求失败,计数达到 2,熔断器断开。时刻 2,新请求直接得到降级结果,不接触下游。时刻 6,距离断开恰好五秒,允许一次探测。它成功后清零计数并闭合;时刻 7,普通调用可以继续通过。
- 应用保存的记录0 秒:失败计数 1
- 应用保存的记录1 秒:计数 2 → 断开
- 应用保存的记录2 秒:降级;零次尝试
- 应用保存的记录6 秒:一次探测成功 → 闭合
距断开已过 6 − 1 = 5 秒。半开时,第一次探测尚未结束,不能再放入第二次。
- 应用保存的记录重试成功:好结果
- 应用保存的记录两个失败任务与一次断开拒绝:3 个坏结果
- 应用保存的记录探测恢复:好结果
五个符合条件的任务:2 个好结果 / 5 = 40%。恢复后仍保留先前错误;这是注入故障的演练流量,不是生产可用性。
图 56.1 区分两组时间:讲解用的固定时间线冷却五秒,实际本地冒烟演练把冷却缩短为 0.25 秒。实测记录中,两个任务各自用完尝试次数,下一个任务包含 circuit_open,没有 attempt 事件;后来的探测返回 750。另有一条记录在第二次尝试成功,没有使熔断器断开;还有一条人为延迟的任务到达截止时间。这些路线分别检验不同控制。
本例的降级(fallback) 是明确返回“暂时不可用”,并让用户选择准备一份说明交给人工。它不会把过期制度当作现行答案。较便宜的模型、词法检索或上次已知结果,也可能用作降级,但都要单独评测,并让用户看见限制。本服务本来就在做词法检索,把同一路径改叫“新的降级模型”,不会增加能力。
取消不是下游响应失败。第 54 章发现的缺陷在这里就有了影响:如果把用户停止请求算成依赖失败,健康的依赖也可能被熔断。现在的回归检查要求取消绕过依赖失败计数。监控首先需要事件含义正确,然后才谈得上把图表做得更漂亮。
56.6 先定义承诺,再计算百分比
Section titled “56.6 先定义承诺,再计算百分比”“服务有多大比例正常工作?”如果没有定义请求和好结果,连分母都不确定。本章的教学服务指标(service-level indicator,SLI) 衡量服务端观察到的完成行为。输入有效、身份已认证的请求有资格进入统计;在三秒内到达回答、暂不作答或拒绝访问终点,算好结果。已获授权却因容量或速率限制被拒的请求,算坏结果。未认证或格式错误的请求单独报告,不计入;明确取消也单列,不计入这个完成指标。
这并不是说每次暂不作答都是好答案。指标衡量的是服务是否按响应契约完成处理。第 54 章漏掉的同义表达,可以在这个 SLI 中算好结果,在回答质量评测中却算失败。服务端记录终点,也不证明浏览器已经收到结果;交付情况需要客户端指标或外部探测。这些限定应当放在数字旁边。
服务目标(service-level objective,SLO) 为一个指标规定统计窗口内的目标值。本练习给定 95%,窗口从 0 秒开始,到 60 秒之前结束,包含左端、不包含右端。这是教学目标,不是测得的客户要求、具有合同含义的服务等级协议(service-level agreement,SLA),也不是生产建议。
例 56.6:把窗口内的记录数清楚。 固定数据共有 22 条观察:18 次按时完成、两次有资格计入的错误、一次明确取消、一次未认证请求。最后两种分别统计。因此,分母计入 20 条,分子计入 18 条:
分母不能取 18,因为符合统计条件的失败不能消失;也不能取 22,因为这项指标明确排除了另行报告的两个类别。目标为 95%,允许的坏结果比例就是 。20 次符合条件的请求,对应的错误预算(error budget) 为 次。实际观察到两次,超过目标允许值一次。预算不是允许故意伤害某一位用户,而是在整体目标受到威胁时,帮助决定可靠性工作的优先级。
预算消耗倍率(burn ratio) 比较实际坏结果比例与允许比例:
分子是实际坏请求占比 0.10,分母是目标允许的比例 0.05。两者都没有量纲,所以 表示,在这个窗口中,坏结果发生比例是允许值的两倍。它没有说明是哪项修改造成失败,也不能说明这个比例还会持续多久。
如果目标改为 99%,相同记录对应的预算为 ,消耗倍率为 10。小数预算可以表示总体允许值,不表示可以执行“五分之一次请求”。样本这么少时,一次失败就已经超出它。若没有符合条件的请求,SLI 和消耗倍率都未定义;返回 100% 等于编造了证据。
本地监控报告会话窗口,最多保留 1,000 条观察。还在处理的任务没有提交终态观察,因此实时快照只能暂作参考;要解释一个已经完成的窗口,应先等其中的任务结束。如果丢弃了更早的记录,就会标记窗口不完整,维护者不能把留下的部分说成整个会话的可用性。诊断文件另外轮换,大系统还可能对轨迹采样。这些存储规则都不能改变原本希望统计的分母定义。
56.7 告警要求调查,不要求相信某个原因
Section titled “56.7 告警要求调查,不要求相信某个原因”演示告警规定:至少有五条符合条件的记录,而且消耗倍率严格大于 2。固定的 18/20 算例恰好为 2,因此虽然没有达到服务目标,却不会触发这条告警。这是有意保留的区别:达到告警阈值,与达到 SLO,是两个判断。实际故障序列包含两次成功完成和三次错误,五请求指标为 ,消耗倍率为 ,所以会触发。
可执行的告警策略还要规定观察窗口、接收者、处置动作和解除条件。本例由本地操作负责人查看失败轨迹,停止注入故障,等待冷却后的探测,再确认一次成功请求。会话比例仍包含先前错误,一次探测成功不会擦掉整个窗口。长期运行的系统常把短窗口和长窗口结合起来,在及时发现与孤立噪声之间取舍。这次小演练没有替读者选定生产参数。
如果发布后延迟降低,可能是缓存命中更多、请求分布变了、用户减少了,或者依赖恢复了。监控观察的是被收集到的分布里发生了什么。要把改善归因于发布,应当比较受到适当控制的条件,保留分母,并检查其他变化。带时间戳的相关关系是调查线索,不是因果证明。
56.8 把报告变成审过的测试,不自动当成新知识
Section titled “56.8 把报告变成审过的测试,不自动当成新知识”界面提供三类反馈:来源似乎有误、没有找到答案、使用不方便。系统把类别和请求引用关联到已认证的访问主体,不收集任意自由文本、凭据或用户提议的新制度。报告先进入隔离待审区(quarantine):队列里的内容不能改变资料库、回答规则或模型参数。
例 56.7:报告不是答案依据。 提交一条虚构的“没有找到答案”报告后,回归列表仍为空。维护者核对制度自 2026-01-01 起生效的边界,另写一条虚构案例,要求从 v2 得到 750,并记录理由。它随后进入已审列表,实际检查返回 750。删除原访问主体的报告,会移除与用户关联的反馈;留下的已审固定案例不含用户身份。这是明确的作者审核与新增回归提案,不是自动训练,更不是自动部署。
服务限制待审报告数量,七天到期后由定期清理流程删除,也支持按访问主体删除。本地进程把反馈放在内存中,重启就结束这条队列。持久反馈系统若要承诺跨重启保留同样行为,还需要有访问控制的存储、删除传播和操作流程。有用的回归应当另外保存为审查过、适当去标识化、带来源与版本的材料。
自愿反馈存在选择偏差(selection bias):报告问题的人,未必代表全部用户。假设发生了 100 次请求,十位用户反馈,其中八份报告涉及来源错误。 是报告内部的比例,不是所有请求的来源错误率。即使算 ,也只是已观察到的报告比例;没有报告的错误与可能误报的问题,使它不能直接成为准确率估计。系统抽样标注与有针对性的用户研究,可以补充反馈,不能被反馈取代。
KA-10 的运行增量已经可以通过共享服务规则、故障程序与 Notebook 执行。固定逻辑时钟使边界计算可复核,实际 HTTP 记录则展示另外的运行路径。风险与运行手册继续保留词汇缺口、单实例运行、缺少生产可用性证据等限制。毕业章会据此判断,究竟交付到了什么程度。
56.9 本章小结
Section titled “56.9 本章小结”缓存只能在明确的身份、权限、日期和到期条件内复用计算,语义相似又增加了一项可能出错的接受判断。接纳限制、截止时间、有限重试与熔断器保护不同边界;降级必须如实说明什么当前不可用。SLO 从明确的统计对象、结果和窗口开始,告警只是指出需要调查的条件。反馈要经过审核并进入有版本的回归,才能成为维护证据,不能因为已经存下来,就自动拿去训练或改答案。
56.10 习题
Section titled “56.10 习题”- 一个条目在 12 秒写入,TTL 为 20 秒。判断 31.999 秒和 32 秒的查找是否有效。如果资料在 15 秒改变,只靠 TTL 能否防止 20 秒时复用旧答案?
- 桶容量为 3,每秒补充 2 个许可,时刻 4 时为空。请求依次在 4.25、4.5、5 到达。每次使用前有多少许可,是否接纳?
- 一个窗口中有 48 次按时完成、两次符合统计条件的过载拒绝、三次取消和两次未认证尝试。按本章指标及 98% 目标,计算 SLI、预算和消耗倍率。如果产品决定把取消也计入,会怎样?
- 熔断器在两个请求失败后断开,每个请求允许两次尝试。为什么数尝试可能使它过早断开?为什么取消不该算依赖失败?
- 新版发布后,投诉变少,中位延迟也降低。分别给出一种其他可能解释,并提出更有说服力的证据。
- 一份反馈说“750 错了,改成 900”。从报告到可能的数据或回归更新,允许经过什么流程?审核之前,哪些东西必须保持不变?
56.11 参考解答
Section titled “56.11 参考解答”- 到期时刻为 秒,第一次查找在时间上有效,第二次无效。只靠 TTL,20 秒仍允许复用。应更新有关数据/版本身份及其兼容索引,或明确使旧项失效,同时检查访问与请求范围。
- 4.25 时有 个许可,拒绝并保留 0.5;4.5 时又补充 0.5,够一次,接纳后剩 0;5 时再补充 1,接纳后剩 0。被拒绝不会花掉半个许可。
- 分母为 50,SLI 为 ,允许坏结果为 ,实际为 2,消耗倍率为 。若把取消算坏结果,符合条件的记录变为 53,SLI 为 ,预算为 1.06,消耗倍率为 。这是另行声明的一项指标,不是在修正原定义下的计算。
- 第一个请求的两次失败尝试,就可能在第二个请求出现之前达到阈值二,所以必须明确计数单位。取消说明调用者停止工作,不证明依赖失败;应单独处理,并释放已占用的探测资格,不虚构失败。
- 投诉减少,可能因为用户变少,或反馈入口更难找到;延迟降低,可能因为缓存命中比例提高或工作负载变简单。应检查使用与报告比例,抽样标注结果,比较相近请求分布和版本,并设置适当对照后再判断因果。
- 先隔离待审。不能因为用户提议一个数字,就改变权威资料、缓存身份、回答规则或模型。由有权负责制度的人确定 900 是否有效、适用于哪个区间;维护者审核来源,建立有版本的数据/回归修改,保留历史案例,评测候选,再经过发布门。被拒绝或没有依据的报告,始终不能混成已接受标签。
56.12 参考文献
Section titled “56.12 参考文献”- Google SRE Workbook:Implementing SLOs,SLI 定义/实现与错误预算策略。支持明确测量边界并记录目标依据;本章数字由作者给定。
- Google SRE Workbook:Alerting on SLOs,预算消耗与多窗口部分。解释告警为何不能只靠一个成功百分比。
- Google SRE:Handling Overload,客户端节流与利用率。支持保护容量;令牌桶数据是本书原创的简化规则。
- Microsoft:Circuit Breaker 模式,方案与重试区别。支持闭合/断开/半开行为;本项目另外明确采用连续失败请求计数。
- OWASP LLM10:2025 Unbounded Consumption,资源、超时和待执行操作限制。支持限制工作量,不能据此认证能抵御恶意流量。
- NIST AI 600-1,Generative AI Profile,MANAGE 4.1–4.3,印刷页码第 44—45 页。支持审核反馈、处理问题和明确恢复责任。