把指标写成条款:解剖一份单机高可用 ADR 的八个可靠性决定

上一篇把 SLO/RTO/RPO 理成三族刻度,这一篇拿一份刚评审通过的真实 ADR(一个 LLM 网关+在线路由+评测平台的单机生产高可用基线)当标本,看抽象指标怎么被写成具体条款。逐条解剖八个决定,每条给出条款要点→背后概念→源头论文/实践→定价解读:故障域声明(承保范围先于保额,拒绝伪分布式集群=拒绝为范围外风险付保费)、SLO 分级与排除条款(数据面 99.9% 高于控制面 99.5%,与 AWS Builders 库"数据面可用性目标高于控制面"同构)、两档 RTO(自动 2 分钟/人工 15 分钟,43.2 分钟月预算亲手验算出 21 次 vs 2.88 次的自动化边界,理论根基 Crash-Only Software HotOS 2003)、RPO 向量(崩溃 RPO=0 靠持久卷、逻辑误删 RPO≤24h 靠逻辑备份——RPO 是按故障类型索引的向量不是标量)、静态稳定性(热路径进程内不可变快照+last-known-good,控制面故障不得中断数据面,AWS 双模行为反模式)、租约+fencing token+幂等(Leases SOSP 1989 + Kleppmann 2016,exactly-once 投递不存在)、canary 状态机(shadow→canary→active+指标门禁+自动回滚,SRE Workbook 第 16 章,active 是不可逆跃迁点)、恢复演练即发布门禁(Chaos Engineering IEEE Software 2016,未演练的备份等于没有备份)。元规律「指标向量化定律」:外行给系统一个可靠性数字,工程师给每类故障各一个数字——可靠性设计的成熟度=指标从标量分解为按故障类型索引的向量的程度;ADR 的本质是保险合同:故障域是承保范围,指标是保额,架构是保费,排除条款是让承诺可兑现的那部分。

上一篇刚把 SLO、RTO、RPO 理成故障时间轴上的三族刻度,正好手边有一份新鲜标本:一个 LLM 网关 + 在线路由 + 评测平台(我参与的项目,细节已匿名化)刚评审通过的 ADR——《单机生产高可用基线》。两页纸的条款,对着读完我数出了八个可靠性工程决定,每个背后都站着一篇源头论文或一条被生产验证过的工程公理。这篇把它逐条解剖开,看抽象指标是怎么落成具体条款的。先给结论:一份好的可靠性 ADR 本质上是一份保险合同——故障域是承保范围,SLO/RTO/RPO 是保额,架构是保费;而这份 ADR 最可贵的一行不是 99.9%,是”不覆盖宿主机、数据库节点或机房故障”——排除条款才是让承诺可以兑现的那部分。

决定一:先划故障域,再谈指标——承保范围先于保额

条款要点:本轮高可用只覆盖单台宿主机内的进程和容器故障,不覆盖宿主机、数据库节点或机房故障;有状态组件(MySQL/PostgreSQL/消息队列/Redis/对象存储)保持单实例,不在单机内构造伪分布式数据库集群

背后概念:故障域(failure domain / fault isolation boundary)。AWS 有一整份故障隔离边界白皮书讲这件事:先明确”哪个范围内的故障要被容忍”,可靠性设计才有边界。

定价解读:这是上一篇「指标即定价」的第一现场。“不做伪分布式集群”这条否决令,翻译过来是:不为承保范围外的风险付保费。在单机内起三节点数据库”集群”,磁盘一坏三份全坏——花了分布式的钱,买不到分布式的故障域。反过来,条款也诚实标价了后果:状态组件故障允许短时不可用,只保证数据可恢复。范围划歪比目标定低更致命,因为它让所有下游数字失去意义。

决定二:SLO 分级 + 排除条款——数据面的 9 永远比控制面多

条款要点:在线路由月度 SLO 99.9%,控制面与离线评测 99.5%;排除宿主机故障、外部 Provider 故障和批准的维护窗口。

背后概念:两个。其一是 Google SRE 书的基本功——SLO 按服务分级,排除条款写明白(这正是 SLA 手艺在内部目标上的应用)。其二更有意思:数据面可用性目标高于控制面不是这份 ADR 的发明,是 AWS 的公开设计公理——Builders’ Library 原文说 EC2 数据面因为比控制面简单得多,“its design targets a higher availability than that of the control plane”。

定价解读:亲手换算(30 天月):99.9% = 43.2 分钟预算,99.5% = 216 分钟(3.6 小时)。控制面拿到 5 倍宽的预算,不是因为它不重要,是因为它的故障可以被架构挡在用户体验之外(见决定五)——预算分配跟着故障传播路径走,而不是跟着组件的”地位”走。我在 Agent 云预测篇里押过”控制面/数据面分离会成为 Agent 架构默认词汇”,这份 ADR 算一个样本内证据:它连 SLO 都按这条线劈开了。

决定三:两档 RTO——2 分钟和 15 分钟之间隔着一条自动化边界

条款要点:单个进程/容器故障自动恢复 RTO ≤ 2 分钟;整套编排栈人工恢复 RTO ≤ 15 分钟。无状态服务与网关代理跑双副本,入口可摘除故障副本。

背后概念:RTO 不是一个数,是按恢复主体(机器还是人)分的两档。2 分钟这档的理论根基是 Crash-Only Software(Candea & Fox, HotOS IX 2003):让软件只有一种停止方式(crash)和一种启动方式(recover),健康检查 + 自动重启才能成为可靠的恢复路径——论文的反直觉观察是很多系统 crash+recover 比优雅关闭+启动更快。双副本则是另一根杠杆:它把”故障”从”停机”降级成”容量损失”,摘除副本的时间近乎零,根本不进 RTO 账本。

定价解读:拿上一篇的预算表验算:43.2 分钟月预算 ÷ 2 分钟 = 每月最多容忍 21 次”单容器故障且期间服务整体不可用”;÷ 15 分钟 = 全栈人工恢复每月只够 2.88 次。结论清晰得像一条断崖:日常故障必须全自动恢复,人只被允许出场两次。这正是上一篇”四个 9 之后人工出局”的单机版实例——三个 9 时人还能出场,但次数已经被预算钉死了。

决定四:RPO 是向量,不是标量——0 和 24 小时同时成立

条款要点:容器重启时已提交持久化数据 RPO = 0;逻辑误操作或数据损坏的可恢复点 ≤ 24 小时。

背后概念:这是全文我最想圈出来的一条。NIST SP 800-34 定义 RPO 时默认单一灾难场景,但生产条款把它拆成了按故障类型索引的向量:崩溃类故障靠持久卷 + 数据库 WAL 兜底,RPO = 0;逻辑类故障(误删表、脏写)持久卷帮不了你——错误数据也被忠实持久化了,只能靠每日逻辑备份回滚,RPO = 24 小时。

定价解读:两种故障、两种回滚机制、两个价位。同步复制能把崩溃 RPO 压到 0,但对逻辑损坏无效——它只会把错误同步得更快。这解释了一个常见误区:“我们有实时同步,为什么还要每日备份?“因为它们保的不是同一种险。门禁篇说过不可逆性决定门禁位置;RPO 向量说的是同一件事的恢复侧:每类不可逆损失都需要自己专属的后悔药,而且价格各不相同。

决定五:静态稳定性——控制面挂了,数据面继续用旧地图开车

条款要点:在线路由热路径只读进程内不可变策略快照;后台异步刷新控制面快照,并持久化无密钥的磁盘 last-known-good;控制面、消息系统和观测系统故障不得中断已加载策略的在线调用。

flowchart LR
    subgraph CP["控制面(SLO 99.5%)"]
        API["control-api"] --> SNAP["策略快照"]
    end
    subgraph DP["数据面热路径(SLO 99.9%)"]
        REQ["在线请求"] --> RT["routing-runtime<br>进程内不可变快照"]
    end
    SNAP -.->|"后台异步刷新<br>(不在请求路径上)"| RT
    RT --> LKG["磁盘 last-known-good<br>(无密钥)"]
    LKG -.->|"重启时兜底加载"| RT

背后概念:静态稳定性(static stability),出处是 AWS Builders’ Library 的 Static stability using Availability Zones(Weiss & Furr):依赖受损时,系统继续用”最后已知的良好配置”运行——看不到更新,但已经在做的事不中断。反面教材叫双模行为(bimodal behavior,Well-Architected REL11-BP05 明令避免):正常时走一条路、故障时突然切到另一条没演练过的路——故障路径本身成为最大风险。

定价解读:这条是决定二里”控制面预算宽 5 倍”的兑现机制:热路径对控制面零运行时依赖,控制面的 216 分钟预算烧穿了也波及不到在线路由的 43.2 分钟。注意”无密钥”三个字——落盘的 last-known-good 不含凭证,可靠性设计没有为了兜底把安全边界一起兜进去,这种细节是条款经过真评审的痕迹。

决定六:至少一次 + 租约 + fencing token——因为 exactly-once 投递不存在

条款要点:评测任务采用至少一次投递、租约、heartbeat、fencing token、逐模型/逐样本幂等结果和有界重试;不可恢复事件进 DLQ。

背后概念:一整套 1989 年就定型的分布式基本功。租约出自 Leases(Gray & Cheriton, SOSP 1989):用带过期时间的授权代替永久锁,持有者失联,等租约到期即可安全接管——短租约把故障影响压到最小,这是”心跳 + 超时接管”模式的原始出处。但租约有个著名缝隙:拿着过期租约的僵尸 worker 可能在 GC 停顿后醒来继续写。补丁就是 fencing token——Kleppmann 2016 讲得最透:锁服务发放单调递增的令牌,存储侧拒绝旧令牌的写入,僵尸自然被围栏挡住。而”至少一次投递 + 幂等消费”的组合,源于一条无情的公理:网络会丢确认,精确一次投递在分布式系统里不存在,能工程化的只有”至少一次送到 + 重复到达无害”。

定价解读:评测任务是典型的”长时间、可重试、结果可幂等”负载——这套三件套(租约防僵死、fencing 防僵尸、幂等防重复)是它最便宜的正确性方案,比引入分布式事务便宜一个数量级。我在 Inspect AI 实操篇里跑过评测任务,当时没意识到”逐样本结果幂等”在生产里是正确性支柱而不是优化。

决定七:canary 状态机——active 之前设三道门,因为 active 是不可逆跃迁点

条款要点:策略发布走 draft → offline_verified → shadow → canary → active → archived/rolled_back,支持一致性哈希灰度、指标门禁、人工 kill switch 和自动回滚。

stateDiagram-v2
    [*] --> draft
    draft --> offline_verified: 离线评测通过
    offline_verified --> shadow: 影子流量对拍
    shadow --> canary: 一致性哈希灰度
    canary --> active: 指标门禁通过
    canary --> rolled_back: 门禁失败 / 自动回滚
    active --> rolled_back: kill switch
    active --> archived: 被新版本替代
    rolled_back --> draft: 修复后重新提交

背后概念:渐进式交付,源头文本是 SRE Workbook 第 16 章 Canarying Releases:金丝雀的本质是用一小片真实流量做实验,把发布风险从 SLO 预算里的大额支出变成小额分期——章节反复强调指标要”能指示问题、有代表性、可归因”,否则门禁形同虚设。shadow(影子流量,只对拍不生效)比 canary 更早一档,风险为零但也验不出副作用。

定价解读:用门禁篇的语言读这张状态机:draft → shadow 都是可逆区,随便折腾;canary → active不可逆性跃迁点——策略开始影响全量真实流量——所以三道门(离线评测、影子对拍、灰度门禁)全部堆在跃迁点之前,且回滚路径(自动回滚 + kill switch)必须先于前进路径存在。值得一提的是这套状态机管的是路由策略而非代码——配置变更走和代码同规格的发布管线,这是从大量”配置一推全站挂”事故里学来的行业共识。

决定八:演练即门禁——未演练的备份等于没有备份

条款要点:生产部署、迁移、备份和故障演练成为发布门禁,不能以单元测试通过替代运行证据

背后概念:混沌工程的温和版。Chaos Engineering(Basiri 等,IEEE Software 2016,Netflix 团队)把可靠性验证从”论证”改成”实验”:不是推理系统应该能扛住故障,是注入故障看它真的扛住。备份同理——没做过恢复演练的备份只是一份写入成功的文件,它的可恢复性是猜想不是事实。

定价解读:这条把上一篇没展开的一个区别补上了:RTO/RPO 里的 O 是 Objective——目标。目标只有经过演练才变成实测能力,否则 ADR 里的”2 分钟”和许愿没有区别。用证据分级的话说:演练把这些数字从”设计目标”升级成”亲手测出”。这也正是我的论断-数字账本约束在基础设施上的镜像——系统和人一样,不做 rollout 的能力声明都是流畅错觉。

总表:八个决定 × 概念 × 源头 × 价格标签

#条款概念源头买了什么
1单机故障域,拒绝伪集群故障隔离边界AWS 故障隔离白皮书不为范围外风险付保费
299.9% / 99.5% 分级 + 排除条款SLO 分级Google SRE 书 Ch.4;AWS Builders’ Library预算跟故障传播路径走
3RTO 自动 2min / 人工 15min自动化边界 + N+1 冗余Crash-Only Software(HotOS 2003)21 次 vs 2.88 次的出场费
4RPO = 0 与 ≤24h 并存RPO 按故障类型向量化NIST SP 800-34 + WAL/逻辑备份分工每类不可逆损失各配后悔药
5热路径不可变快照 + last-known-good静态稳定性AWS Builders’ Library(Weiss & Furr)控制面故障不穿透数据面
6租约 + fencing + 幂等 + DLQ至少一次的正确性三件套Leases(SOSP 1989);Kleppmann 2016比分布式事务便宜一个量级
7shadow→canary→active 状态机渐进式交付 + 门禁SRE Workbook Ch.16发布风险从大额支出变分期
8演练即发布门禁混沌工程(温和版)Chaos Engineering(IEEE Software 2016)把 Objective 变成实测数字

元规律:指标向量化定律

八条排在一起,能看出一条比”指标即定价”更进一步的规律:

外行给系统一个可靠性数字,工程师给每类故障各一个数字。可靠性设计的成熟度,等于指标从标量分解成”按故障类型索引的向量”的程度。

证据就在条款里:SLO 不是一个,是数据面/控制面两个;RTO 不是一个,是自动/人工两档;RPO 不是一个,是崩溃/逻辑损坏两个价位;连”高可用”本身都先被故障域切成”保这些、不保那些”。每一次分解,都对应一次”这类故障用什么机制恢复、值多少钱”的显式决策。反过来,一份只写着”目标可用性 99.99%“的文档,几乎可以断定没经过这种分解——标量指标是愿望的形状,向量指标才是工程的形状。

而让整份合同成立的,是那行最不起眼的排除条款。承诺的可信度不来自保额多高,来自承保范围划得多诚实。

诚实的提醒

  • 亲手算过:43.2 / 216 分钟月预算、21.6 次与 2.88 次出场额度,Python 一行验算。
  • 核实过的来源:八个源头(SRE 书、SRE Workbook Ch.16、NIST SP 800-34、AWS Builders’ Library 与 Well-Architected、Crash-Only HotOS 2003、Leases SOSP 1989、Kleppmann 2016、Chaos Engineering arXiv 1702.05843)均本次检索核实,正文带链接。
  • 我的观点/框架:“ADR = 保险合同”的类比、「指标向量化定律」是我从这份条款里归纳的,不是文献共识;被解剖的 ADR 本身是设计目标文档,其中的 RTO/RPO 数字尚未经过演练实测——这恰好是决定八要求补的课。
  • 条款为匿名化转述,非原文引用。

最低成本的亲手验证实验(15 分钟,正好验决定三):用 docker compose 起两副本的任意 echo 服务 + 一个反向代理,一个终端跑 while true; do curl -s -o /dev/null -w "%{http_code}\n" localhost:8080; sleep 0.2; done,另一个终端 docker kill 掉一个副本——数一数出现几个非 200,再乘 0.2 秒,你就亲手测出了”摘除故障副本”的真实 RTO,可以和”双副本把故障降级为容量损失”这句话对账。

参考来源

工程实践

论文