3混沌工程:系统韧性实践

稳态假设

定义系统的正常行为

稳态业务指标SLO稳态偏离

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第3章 稳态假设

定义系统的正常行为

章节导读

稳态假设是混沌工程的第一原则,也是整个方法论的逻辑起点。在科学实验中,你要测量任何变化,首先必须定义「不变」是什么;你要判断系统是否被故障破坏,首先必须精确描述「系统正常」长什么样。这个看似简单的定义工作,实际上是混沌工程中最考验功力、也最容易被轻视的环节。一个草率的稳态假设会让后续所有实验失去意义:假设定义得太松,系统已经半瘫痪了指标依然「正常」,实验永远「通过」,团队获得虚假的信心;假设定义得太紧,正常的业务波动就会触发「失败」判定,实验噪音淹没真实信号,团队疲于奔命地调查根本不存在的问题。稳态假设的质量,直接决定了混沌实验的信噪比。

什么是稳态(steady state)?借用控制论的概念:系统在正常条件下表现出的、可度量的、相对稳定的行为模式。注意三个限定词。「正常条件」指没有故障注入、没有异常事件的基线状态;「可度量」意味着稳态必须落实为具体的指标与阈值,「感觉系统挺正常的」不是稳态;「相对稳定」承认稳态不是恒定值而是动态范围——电商系统在晚高峰的每秒订单数是凌晨的十倍,但两个时段各自都处于稳态,稳态的定义必须容纳这种周期性波动。Netflix 对稳态的经典选择是「每秒播放启动数」(stream starts per second, SPS):用户点击播放按钮并成功进入观看状态的事件速率。这个指标的精妙之处在于它是端到端的——一次成功的播放启动,意味着 DNS 解析、CDN 调度、API 网关、用户鉴权、推荐系统、视频存储、客户端播放器等整条链路全部正常工作。任何一个环节的严重故障,都会让 SPS 下跌。用一个业务结果指标覆盖整条技术链路,这是稳态设计的最高境界。

为什么必须是业务指标而非系统指标?因为混沌工程的终极问题是「用户是否受影响」,而系统内部状态与用户感受之间的映射既不稳定也不完整。CPU 使用率 80% 可能是系统在高负载下健康运转,也可能是死循环 bug 在空转;内存占用平稳可能一切正常,也可能缓存已经全部失效、数据库正在被穿透。更根本的问题是:系统指标的「正常范围」本身难以定义——多少算高?多少算异常?而业务指标有天然的锚点:成功率的上限是 100%,「每秒成功订单数」下跌 10% 在任何语境下都意味着真实损失。业务指标把工程问题翻译成了商业语言,这也让混沌实验的结果能够被管理层理解——「实验期间订单成功率没有下跌」比「实验期间 CPU 曲线没有异常」有说服力得多。

构建稳态假设的方法论有两条互补的路径。第一条是自顶向下:从 SLO(服务等级目标)出发。如果你的组织已经建立了 SLO 体系(例如「订单 API 可用性 99.95%,P99 延迟 500ms」),SLO 天然就是稳态假设的候选——它们本来就是组织对「什么是可接受的服务质量」的正式承诺。混沌实验验证的正是「故障条件下 SLO 是否依然成立」,这让混沌工程与 SRE 实践无缝衔接。第二条是自底向上:从用户旅程出发。梳理核心用户旅程(浏览→加购→下单→支付→履约),为每个旅程的关键步骤找到对应的业务事件指标,再组合成稳态。两条路径的产出应当交叉验证:SLO 覆盖不到的用户旅程环节是监控盲区,用户旅程中无 SLO 支撑的指标是治理缺口。

稳态假设在实验中的使用方式同样讲究。实验前,先采集基线数据确认系统当前处于稳态(不在稳态的系统上做实验,结论无效);实验中,持续监测稳态指标,配合中止条件实时保护;实验后,对比实验组与对照组(或实验前基线)的指标差异,做出「稳态保持」或「稳态偏离」的判定。判定必须处理统计噪音问题:业务指标天然波动,如何区分「实验导致的下跌」与「正常波动」?简单方案是用历史同期数据构建波动区间(例如过去四周同时段的成功率均值 ± 3 倍标准差);严谨方案是 Netflix ChAP 式的对照组设计——把流量随机分为实验组与对照组,两组唯一的差异是故障注入,组间差异的统计显著性检验直接给出结论。

本章还将深入讨论稳态设计的常见误区:稳态定义过窄(只看一个指标,故障从别的维度漏过去)、忽略长尾(平均值掩盖 P99 劣化)、混淆相关与因果(指标同时变化不代表实验是原因)、忽略时变性(用全天固定阈值处理有明显日周期的指标)、零流量假稳态(流量被切断时比率指标失真)。每一个误区都对应真实发生过的误判案例。稳态假设与可观测性(第 7 章)的关系也将在本章厘清:可观测性是稳态度量的基础设施,没有指标采集、聚合、查询的能力,稳态假设只是纸上谈兵;反过来,混沌实验常常首先暴露可观测性的盲区——当你想度量某个稳态却发现没有对应指标时,你就找到了监控体系最该补的课。学完本章,你将能够为任何系统设计出高质量的稳态假设,这是从「会跑工具」到「会做实验」的第一步。

核心概念

  • 稳态
  • 业务指标
  • SLO
  • 稳态偏离

要点精析

稳态的本质是「动态平衡」而非「恒定值」,理解这一点是正确定义稳态的前提。物理学的稳态指系统在持续的能量/物质交换中保持宏观性质稳定;软件系统的稳态类似——请求持续流入流出、数据持续读写、实例持续调度重建,但业务指标的统计特征保持稳定。这意味着稳态假设必须表达为「带容差的动态范围」而非「固定阈值」。实践中的三种表达方式:(1)绝对阈值——「成功率 ≥ 99.5%」,适合有明确业务红线的指标,简单但对时变指标不适用;(2)相对基线——「与过去四周同时段均值相比下跌不超过 3%」,适合有周期性的业务指标,能自动适应高峰低谷;(3)组间对照——「实验组与对照组指标差异无统计显著性」,最严谨,是 Netflix ChAP 的方案。成熟团队通常组合使用:绝对阈值做安全底线(触发中止条件),相对基线或对照组做实验判定。一个反面案例:某团队用「每秒订单数 > 500」做稳态,结果所有深夜实验都「失败」(深夜正常订单数只有 100),所有大促实验都「通过」(即使故障导致订单数从 5000 跌到 600 依然大于 500)——固定阈值在时变指标上完全失效。

业务指标优于系统指标的深层原因:业务指标度量的是系统的「目的」,系统指标度量的是系统的「手段」。组织的目的是让用户完成交易/观看/沟通,手段(CPU、内存、网络、队列)只是达成目的的资源。手段异常不一定目的受损(单台机器 CPU 高但负载均衡把流量分走了),手段正常也不代表目的达成(所有指标绿灯但 DNS 解析失败,用户根本进不来)。业务指标的选择标准有四条:端到端性(指标覆盖完整用户旅程而非单个组件)、用户视角(反映用户实际感受而非内部处理量)、单调性(指标变好一定意味着用户受益,不存在「成功率太高反而是问题」的歧义)、灵敏度(严重故障必然引起指标可观测的变化)。Netflix 的 SPS 四条全占:播放启动是端到端事件、直接对应用户核心体验、越高越好、任何链路故障都会让它下跌。为你的系统寻找这样的「北极星指标」是稳态设计的第一步:电商是成功订单数,社交产品是消息送达数,SaaS 是核心操作完成数,API 平台是成功调用数。找不到单一指标时,用 2-3 个指标的组合,但要警惕组合过多导致的判定复杂化。

从 SLO 出发构建稳态假设是与 SRE 体系衔接的最佳路径。SLO(Service Level Objective)是组织对服务质量的正式承诺,例如「可用性 99.95%」「P99 延迟 < 500ms」「数据持久性 99.999%」。SLO 与稳态假设的关系是:SLO 定义了「可接受的最低质量」,稳态假设定义了「实验判定的标准」,前者是后者的天然下界。具体转化方法:(1)可用性 SLO → 稳态的成功率条件,注意 SLO 通常按 30 天滚动窗口计算(99.95% 月度可用性允许约 22 分钟停机),而实验判定用短窗口(5 分钟成功率),需要把月度预算换算为实验窗口的合理阈值;(2)延迟 SLO → 稳态的分位数条件;(3)错误预算(error budget)概念为实验提供了绝妙的制度接口——错误预算是「SLO 允许的失败额度」,混沌实验消耗的失败额度只要控制在预算内,就是「用可接受的代价购买韧性知识」,这个论证框架能让管理层直观理解实验的安全性。没有 SLO 体系的组织怎么办?先补 SLO——这本身就是混沌工程带来的第一个改进。没有 SLO 就做混沌实验,等于没有尺子就量身高:你会被迫在实验过程中临时争论「多少算异常」,判定失去客观性。

从用户旅程出发构建稳态的具体方法:旅程映射 → 关键事件识别 → 指标化 → 组合。第一步,画出核心用户旅程的完整路径,例如电商的「搜索 → 浏览商品 → 加入购物车 → 提交订单 → 支付 → 查看订单状态」。第二步,为每个环节识别可观测的业务事件:搜索返回结果数、商品页加载成功、加购成功、订单创建成功、支付成功、订单查询成功。第三步,把事件转化为指标:速率(每秒成功次数)、成功率(成功/尝试)、延迟(P50/P99)。第四步,组合成稳态假设——不是所有环节等权,按业务价值加权:支付环节的权重最高(直接损失收入),搜索环节次之(影响转化),页面装饰性元素最低。旅程方法的最大价值是暴露「组件视角监控」的盲区:某支付公司曾发现所有系统指标正常但支付成功率下跌,排查后发现是一个新上线的风控规则误杀了 3% 的正常交易——风控系统自身「工作正常」(规则确实执行了),故障发生在系统间的语义层面。用户旅程指标天然覆盖这类跨组件的语义故障,因为它度量的是端到端结果而非组件状态。

稳态验证的统计方法:如何区分「实验效应」与「自然波动」。业务指标永远在波动——用户行为的随机性、营销活动的脉冲、爬虫流量的起伏、甚至天气与时段效应。朴素的做法(实验前后各取一个时间点比较)极易误判。三种递进的严谨方案:(1)历史基线法:采集过去 N 周同时段的指标分布,计算均值 μ 与标准差 σ,实验期间指标落在 μ ± 3σ 之外才判定为偏离。优点是简单,缺点是无法处理基线本身的漂移(如业务自然增长)。(2)对照组法:把流量随机分为实验组与对照组,只有实验组承受故障注入,比较两组指标的同期差异。随机分流保证两组在统计上同质,组间差异只能来自故障注入——这是因果推断的金标准,Netflix ChAP 的核心设计。(3)统计过程控制(SPC):对指标时序建立控制图(control chart),用滑动窗口计算过程均值与控制限,检测点超出控制限、连续多点单侧偏移、趋势性变化等模式。SPC 的优势是对「缓慢劣化」敏感——故障注入后指标不是断崖下跌而是缓慢爬升的错误率,固定阈值可能一直不触发,但 SPC 的连续偏移规则能捕获。实践中对照组法 + SPC 的组合最为可靠:对照组消除外部因素干扰,SPC 提供灵敏的偏离检测。

误区一:稳态定义过窄——单一指标或单一维度无法覆盖系统的失败模式空间。真实案例:某团队的稳态只有「API 成功率 ≥ 99.9%」,一次缓存故障实验中成功率确实没跌(请求都返回了 200),但响应体里的推荐列表全部退化为兜底的热门内容——功能「成功」但体验严重降级,实验被误判为通过,直到一周后运营发现推荐点击率异常才暴露。教训:稳态应当覆盖「可用性 + 质量 + 性能」三个维度——请求成功(可用性)、返回内容正确且完整(质量)、响应时间可接受(性能)。质量维度最难度量但最重要,可行的代理指标包括:响应体大小分布(内容缺失时响应会异常小)、业务转化漏斗(推荐降级会反映在点击率上)、数据一致性校验(写入后读回比对)。误区一的另一个形态是「只覆盖主流程」:稳态只度量下单成功率,忽略了退款、查询、消息通知等次要流程——而故障往往先击穿最薄弱的次要流程。设计稳态时做一次「失败模式盘点」:列出系统所有可能的「坏法」,检查每种坏法是否至少被一个稳态指标捕获。

误区二:忽略长尾——平均值与中位数会系统性地掩盖少数用户的灾难性体验。数学事实:如果 99% 的请求延迟 50ms、1% 的请求延迟 10 秒,平均延迟是 149ms,看起来相当健康;但那 1% 的用户(日活百万的产品即一万人)正在经历完全不可用的服务。更隐蔽的是长尾的用户构成非随机性:延迟长尾往往集中在特定用户群——移动端弱网用户、跨地域用户、大客户(数据量大的账户查询天然更慢)、使用旧版本客户端的用户。这些群体恰恰可能是商业上最重要的。混沌实验对长尾特别敏感:故障注入常常首先劣化长尾(例如连接池紧张时,最先超时的是那些本来就需要更多时间的复杂请求),如果稳态只看平均值,实验会漏报这些早期信号。正确做法:稳态假设必须包含高分位数指标——P99 是底线,对延迟敏感的业务应当看 P99.9。工程挑战在于高分位数的度量成本:P99.9 需要足够的样本量才有统计意义(每分钟 100 个请求的系统,P99.9 每小时才 36 个样本点),低流量系统可以用直方图聚合(Prometheus 的 histogram_quantile)在可接受的精度下计算,或者放宽到 P99 并延长观测窗口。

误区三:混淆相关与因果——实验期间指标变化了,不代表变化是实验引起的。生产环境永远有多个事情同时发生:你的实验注入故障的同时,营销团队上线了活动、另一个团队发布了代码、某个区域的运营商网络出现了抖动。如果实验判定只做「实验期间指标是否异常」的时间相关性检查,误判率会很高——把别人的活动当成自己的实验效应(假阳性),或者更危险地,实验的真实效应恰好被营销活动的正向脉冲抵消(假阴性,系统其实已经被故障打伤了,但指标看起来正常)。化解方法按严谨度递增:(1)实验窗口管理——建立组织级的实验日历,避免与已知的大活动、大发布重叠,实验前检查变更队列;(2)对照组设计——随机分流保证外部因素同等作用于两组,组间差异归因于实验(这是为什么 ChAP 坚持对照组);(3)多指标交叉验证——实验效应应当在因果链路上留下连贯的痕迹:注入数据库延迟,应当观察到数据库调用延迟上升 → 依赖它的 API 延迟上升 → 转化率下跌,如果只有末端指标变化而中间链路无异常,就要怀疑归因;(4)重复实验——单次实验的结论存疑时,在不同时段重复运行,真实效应可复现,巧合不可复现。

误区四:零流量假稳态与比率指标的数学陷阱。比率型指标(成功率 = 成功数/总请求数)在分母趋近零时行为诡异:如果实验意外阻断了所有入口流量,系统「没有失败的请求」,成功率显示 100%——完美的假稳态。真实案例:某团队的网络故障实验把服务的健康检查也打挂了,负载均衡将实例全部摘除,流量归零,成功率面板一片祥和,实验判定「通过」;实际上服务对用户已完全不可用,直到有用户投诉才发现。防御手段:(1)稳态组合中必须包含吞吐量下限条件——「每秒请求数 > 基线的 80%」,流量异常消失立即触发失败判定;(2)对小分母做统计保护——请求数低于某阈值时比率指标不参与判定(PromQL 中用 and 条件过滤);(3)从外部视角探测——用合成监控(synthetic monitoring,如黑盒拨测)从用户侧主动发起请求,不依赖真实流量的存在。这个误区的普遍教训是:每个稳态指标都要做「失效模式分析」——这个指标在什么情况下会撒谎?把指标的谎言堵死,稳态假设才真正可信。

稳态假设与可观测性的共生关系:稳态假设是可观测性需求的「牵引器」,可观测性是稳态假设的「地基」。实践中最常见的情况是:团队开始设计混沌实验时,才发现想度量的稳态根本没有对应指标——「我们想知道故障期间消息是否延迟投递,但消息系统只有吞吐量指标没有端到端延迟指标」。这个发现本身就是巨大价值:混沌工程以最直接的方式暴露了可观测性缺口,而且给出了缺口的优先级——你无法为混沌实验度量的行为,真实故障时同样无法诊断。从稳态需求反推可观测性建设的路径:(1)列出所有想要的稳态指标;(2)盘点现有指标覆盖情况;(3)对缺口按「实验需要 + 日常运维需要」双重价值排序;(4)补齐埋点、聚合规则、仪表盘。另一个方向的共生:混沌实验为可观测性提供「测试」——注入已知故障后,检查告警是否触发、触发是否及时、仪表盘是否清晰展示了故障影响、追踪数据能否定位根因。很多组织的告警体系从未被系统性验证过(谁敢主动触发告警条件呢?),混沌实验恰好提供了安全的验证机会。第 7 章将完整展开这个主题,本章先建立认知:稳态假设的每一个指标,都是对可观测性体系的一次调用与检验。

稳态假设的生命周期管理:稳态不是一次性定义、永久有效的,它需要随系统演进而持续修订。触发修订的事件包括:业务模式变化(新增了重要用户旅程,稳态要覆盖)、架构重构(微服务拆分改变了指标语义)、指标基线漂移(业务增长让旧的绝对阈值失效)、实验误判(假阳性/假阴性暴露了稳态定义的缺陷)、SLO 调整(组织对质量的承诺变化)。管理实践:(1)稳态假设代码化——用 PromQL/查询语言写成文件纳入版本控制,修订走 code review 流程,每次变更有记录有理由;(2)定期校准——每季度用最近的历史数据重新计算基线与波动区间,更新相对阈值;(3)误判复盘——每次实验判定与人工复核不一致时(自动判定通过但人眼看出问题,或反之),把稳态定义的缺陷记入 backlog;(4)版本关联——稳态定义与系统版本关联,大版本发布后主动验证稳态指标是否依然有效(新代码可能改变了指标的埋点语义)。一个有用的心智模型:把稳态假设当作「系统的体检标准」——体检标准本身也需要随医学认知与人群特征的变化而更新,用二十年前的标准体检今天的系统,结论必然失真。

多服务系统的稳态分层设计:全局稳态、服务稳态、组件稳态的三层结构。全局稳态是组织级的北极星指标组合(Netflix 的 SPS、电商的 GMV 相关指标),用于回答「用户整体是否受影响」,是大爆炸半径实验与游戏日的判定标准。服务稳态是单个服务的 SLO 派生指标(order-service 的成功率与延迟),用于服务级实验的判定,也是实验中止条件的依据。组件稳态是基础设施层的健康指标(数据库连接数、队列深度、缓存命中率),一般不直接做实验判定,但用于根因分析与机制解释——当服务稳态偏离时,组件稳态的数据告诉你「为什么」。三层的关系是自顶向下牵引、自底向上支撑:实验判定优先看服务稳态与全局稳态(用户视角),组件稳态提供解释力(工程视角)。分层设计的实践价值:(1)不同爆炸半径的实验用不同层的稳态——1% 流量的实验用服务稳态判定(全局指标对 1% 流量不敏感,看不出变化),全区域演练用全局稳态;(2)避免「组件指标噪音干扰判定」——缓存命中率日常波动很大,若纳入实验判定会产生大量假阳性,把它放在解释层而非判定层;(3)三层指标的联动分析本身就是根因定位的自动化雏形。

代码示例

/* 示例 1:完整的稳态假设定义——三维度组合(PromQL) */
/* steady-state-order-service.promql */

/* 维度一:可用性——订单创建成功率(相对基线 + 绝对底线双重判定) */
(
  sum(rate(order_created_total{status="success"}[5m]))
  / sum(rate(order_created_total[5m]))
) >= 0.995
or
(
  (sum(rate(order_created_total{status="success"}[5m])) / sum(rate(order_created_total[5m])))
  >=
  0.97 * (sum(rate(order_created_total{status="success"}[5m] offset 7d)) / sum(rate(order_created_total[5m] offset 7d)))
)

/* 维度二:性能——P99 延迟(长尾保护) */
histogram_quantile(0.99,
  sum(rate(http_request_duration_seconds_bucket{job="order-service"}[5m])) by (le)
) < 0.8

/* 维度三:吞吐量——防零流量假稳态 */
sum(rate(order_created_total[5m])) > 50

/* 维度四(质量代理):响应体大小分布未异常收缩 */
histogram_quantile(0.5,
  sum(rate(http_response_size_bytes_bucket{job="order-service",route="/api/checkout"}[5m])) by (le)
) > 2000
这个 PromQL 文件展示了一个生产级稳态假设的完整形态,四个维度各司其职。可用性维度用了「绝对底线 or 相对基线」的双重判定:绝对条件(≥99.5%)是业务红线,任何情况下跌破即失败;相对条件(不低于七天前同时段的 97%)处理时变性——深夜基线成功率本来就有波动,与上周同期比较更公平。offset 7d 选择一周前而非一天前,是因为业务有周内周期(周一与周日的模式不同)。性能维度的 P99 < 800ms 保护长尾用户体验。吞吐量维度(>50/s)是防「零流量假稳态」的关键——没有它,流量被完全切断时成功率条件会诡异地满足。质量维度最有意思:用响应体大小的中位数做内容完整性的代理指标——当缓存故障导致推荐列表退化为空或兜底内容时,成功率不变但响应体显著变小,这个代理指标能捕获「成功但降级」的隐蔽失败。实践中响应体大小阈值需要根据历史分布设定(这里 2000 字节假设正常 checkout 响应中位数约 3-5KB)。把四个维度用 and 组合成最终判定,任何一个维度失败即稳态偏离。
/* 示例 2:用 Prometheus Recording Rules 预计算稳态指标——性能与复用的最佳实践 */
groups:
  - name: steady-state-rules
    interval: 30s
    rules:
      /* 预计算:订单成功率(5 分钟窗口),实验判定与日常监控共用 */
      - record: orders:success_rate:5m
        expr: |
          sum(rate(order_created_total{status="success"}[5m]))
          / sum(rate(order_created_total[5m]))

      /* 预计算:成功率的 7 天同时段基线 */
      - record: orders:success_rate:baseline_7d
        expr: |
          avg_over_time(
            (sum(rate(order_created_total{status="success"}[5m]))
             / sum(rate(order_created_total[5m])))[7d:1h]
          )

      /* 预计算:成功率的波动标准差(用于 3-sigma 判定) */
      - record: orders:success_rate:stddev_7d
        expr: |
          stddev_over_time(
            (sum(rate(order_created_total{status="success"}[5m]))
             / sum(rate(order_created_total[5m])))[7d:1h]
          )

      /* 稳态偏离判定:低于基线 3 个标准差 */
      - record: orders:steady_state:violated
        expr: |
          (orders:success_rate:5m
           < orders:success_rate:baseline_7d - 3 * orders:success_rate:stddev_7d)
这个 Recording Rules 配置把稳态判定从「临时查询」升级为「预计算指标」,体现了稳态假设工程化的三个最佳实践。第一,复用:orders:success_rate:5m 既服务于混沌实验判定,也服务于日常监控面板与告警——同一份计算逻辑避免「实验用的指标和监控用的指标对不上」的经典扯皮。第二,动态基线的正确实现:baseline_7d 用 avg_over_time 对七天的成功率做滑动平均,stddev_7d 计算同期波动幅度,两者组合出 3-sigma 判定——这是「历史基线法」的标准实现,自动适应业务增长与周期波动,无需人工维护阈值。注意子查询语法 [7d:1h]:以 1 小时分辨率采样过去 7 天,在精度与计算成本之间平衡。第三,判定结果本身指标化:orders:steady_state:violated 是一个布尔型指标,实验护栏、告警系统、Chaos Mesh 的 Probe 都可以直接消费它,稳态判定逻辑集中在一处、处处引用。30 秒的计算间隔意味着偏离最多延迟 30 秒被捕获,对中止条件来说足够及时。把这类规则文件纳入版本控制,稳态假设的每次修订都有 diff 可查、有 review 把关——这就是「稳态即代码」。
/* 示例 3:Chaos Mesh Probe——把稳态假设内嵌到实验定义中 */
apiVersion: chaos-mesh.org/v1alpha1
kind: PodChaos
metadata:
  name: redis-node-kill
  namespace: production
spec:
  action: pod-kill
  mode: one
  duration: "90s"
  selector:
    labelSelectors:
      app.kubernetes.io/name: redis
      role: replica
  statusCheck:
    /* 实验期间持续验证稳态:成功率不低于 3-sigma 下界 */
    http:
      url: "http://prometheus.monitoring:9090/api/v1/query?query=orders:success_rate:5m%20%3E%20(orders:success_rate:baseline_7d%20-%203%20*%20orders:success_rate:stddev_7d)"
      criteria:
        statusCode: "200"
        response: |
          "result":[{
    failureThreshold: 3      /* 连续 3 次检查失败判定实验失败并中止 */
    periodSeconds: 10
这个 PodChaos 定义展示了稳态假设与实验执行的深度集成:statusCheck 字段让 Chaos Mesh 在故障注入期间每 10 秒主动查询 Prometheus,验证「成功率高于 3-sigma 下界」的稳态条件是否依然成立。查询 URL 中的 PromQL 经过 URL 编码,其逻辑是:如果 orders:success_rate:5m 大于动态下界,查询返回非空 result,响应体匹配 criteria,检查通过;一旦稳态偏离,result 为空,连续 3 次(30 秒)后 failureThreshold 触发,Chaos Mesh 自动终止实验并清理故障。这种「稳态内嵌」模式相比外置护栏脚本的优势:实验与判定原子化——不存在「实验跑起来了但护栏忘了启动」的窗口;状态统一——实验的失败原因(稳态偏离)直接记录在 Chaos Mesh 的实验状态里,报告自动生成。选择的目标也有讲究:杀的是 redis 的 replica 而非 master——replica 失效是更常见的真实场景(主从切换实验留给更高级的演练),且爆炸半径天然更小。duration: 90s 给足了「故障 → 检测 → 判定 → 中止」的完整时间窗口。
/* 示例 4:对照组分流的稳态验证——Nginx 按用户哈希分流 + 分组指标 */
/* nginx.conf 片段:1% 流量打实验标记 */
map $http_user_id $exp_group {
    default        "control";
    "~^[0-9a-f]$"  "canary";    /* user_id 十六进制末位为特定值的约 1/16 用户 */
}
server {
    location /api/ {
        proxy_set_header X-Exp-Group $exp_group;
        proxy_pass http://order-service;
    }
}

/* 应用侧:按实验组维度上报指标 */
/* order-service 伪代码 */
metrics.counter("order_created",
    tags={"status": result, "exp_group": request.headers["X-Exp-Group"]}).inc()

/* 稳态判定:组间成功率差异的 PromQL */
abs(
  (sum(rate(order_created_total{status="success",exp_group="canary"}[5m]))
   / sum(rate(order_created_total{exp_group="canary"}[5m])))
  -
  (sum(rate(order_created_total{status="success",exp_group="control"}[5m]))
   / sum(rate(order_created_total{status="control"}[5m])))
) < 0.005   /* 两组成功率差异小于 0.5% 判定稳态保持 */
这个示例实现了 ChAP 式对照组实验的简化版,是稳态验证最严谨的形态。分流层用 Nginx 的 map 指令按用户 ID 哈希把约 1/16 的用户划入 canary 组——按用户而非按请求分流很重要:同一用户的所有请求进入同一组,避免用户体验在两组间跳变,也保证组间样本独立。标记通过 X-Exp-Group 请求头贯穿整条调用链(需要配合分布式追踪的 header 透传),应用按组维度上报指标。判定层比较两组成功率的绝对差异:因为分流是随机的,两组在统计上同质,任何显著差异都只能来自 canary 组承受的故障注入——这就是对照组设计消除「相关 vs 因果」误区的机制。0.5% 的差异阈值需要结合样本量校准:canary 组流量越小,成功率的统计噪音越大,阈值应相应放宽(或用正式的假设检验计算 p 值)。这个方案的工程成本不低(分流、透传、分组指标三处改造),建议只对高价值实验(大爆炸半径、核心链路)启用;日常小实验用示例 2 的历史基线法即可。
/* 示例 5:稳态假设的失效模式自查脚本——你的指标会撒谎吗? */
#!/bin/bash
/* steady-state-audit.sh - 对稳态指标做「撒谎测试」 */
PROM="http://prometheus.monitoring:9090"

echo "=== 测试 1:零流量检测 ==="
/* 查询当前吞吐量,确认稳态组合包含流量下限条件 */
RATE=$(curl -s "${PROM}/api/v1/query?query=sum(rate(order_created_total[5m]))" | jq '.data.result[0].value[1]')
echo "当前订单速率: ${RATE}/s —— 若此值归零,成功率指标将显示什么?"

echo "=== 测试 2:长尾检测 ==="
/* 对比 P50 与 P99,差距过大说明平均值类指标不可靠 */
P50=$(curl -s "${PROM}/api/v1/query?query=histogram_quantile(0.5,sum(rate(http_request_duration_seconds_bucket[5m]))by(le))" | jq '.data.result[0].value[1]')
P99=$(curl -s "${PROM}/api/v1/query?query=histogram_quantile(0.99,sum(rate(http_request_duration_seconds_bucket[5m]))by(le))" | jq '.data.result[0].value[1]')
echo "P50=${P50}s P99=${P99}s —— P99/P50 比值超过 10 时,任何基于均值的稳态都是盲区"

echo "=== 测试 3:指标新鲜度检测 ==="
/* 确认指标采集没有中断(陈旧数据会让稳态假性成立) */
FRESH=$(curl -s "${PROM}/api/v1/query?query=time()-max(timestamp(order_created_total))" | jq '.data.result[0].value[1]')
echo "指标延迟: ${FRESH}s —— 超过 60s 说明采集链路可能中断,稳态判定基于僵尸数据"

echo "=== 测试 4:维度完整性检测 ==="
curl -s "${PROM}/api/v1/query?query=count({__name__=~'order_.*'}) by (__name__)" | jq -r '.data.result[].metric.__name__'
echo "^^^ 以上是订单域全部指标 —— 检查是否覆盖 可用性/性能/吞吐量/质量 四维度"
这个审计脚本针对稳态指标的四种「撒谎方式」做主动自查,是稳态假设质量保障的实用工具。测试 1 直指零流量假稳态:脚本提示你思考「流量归零时成功率显示什么」——如果答案是「100%」或「NaN」,你的稳态组合缺少吞吐量下限条件。测试 2 量化长尾风险:P99/P50 比值是延迟分布偏态的直观度量,健康的在线服务通常在 3-10 倍之间;比值超过 10 说明存在显著的慢请求群体,此时任何基于平均值的稳态判定都在掩盖问题。测试 3 是最容易被忽视的「僵尸数据」陷阱:如果指标采集管道中断(exporter 挂了、服务发现失效),Prometheus 里的旧数据会让稳态查询基于陈旧值「假性通过」——time() - max(timestamp(...)) 计算数据新鲜度,超过阈值说明你看到的「稳态」其实是历史快照。测试 4 做维度盘点:列出订单域的全部指标名,人工检查四个维度的覆盖情况。建议把这个脚本加入实验前的检查清单,每月定时运行一次——稳态假设的可信度依赖指标管道的健康度,而指标管道本身也是会坏的分布式系统。
/* 示例 6:Grafana 稳态仪表盘的核心面板定义(JSON 片段) */
{
  "panels": [
    {
      "title": "稳态总览:订单成功率 vs 动态基线",
      "type": "timeseries",
      "targets": [
        { "expr": "orders:success_rate:5m", "legendFormat": "实时成功率" },
        { "expr": "orders:success_rate:baseline_7d", "legendFormat": "7天基线" },
        { "expr": "orders:success_rate:baseline_7d - 3*orders:success_rate:stddev_7d", "legendFormat": "3σ下界(中止红线)" }
      ],
      "fieldConfig": { "defaults": { "thresholds": { "steps": [
        { "color": "red", "value": null }, { "color": "green", "value": 0.995 } ] } } }
    },
    {
      "title": "实验标记叠加:Chaos 实验时间窗",
      "type": "annotations",
      "targets": [
        { "expr": "chaos_experiment_active{namespace='production'} == 1", "legendFormat": "{{experiment_name}}" }
      ]
    },
    {
      "title": "长尾监控:P50 / P99 / P99.9 延迟",
      "type": "timeseries",
      "targets": [
        { "expr": "histogram_quantile(0.5, sum(rate(http_request_duration_seconds_bucket[5m])) by (le))", "legendFormat": "P50" },
        { "expr": "histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket[5m])) by (le))", "legendFormat": "P99" },
        { "expr": "histogram_quantile(0.999, sum(rate(http_request_duration_seconds_bucket[5m])) by (le))", "legendFormat": "P99.9" }
      ]
    }
  ]
}
这个 Grafana 面板配置是稳态假设的可视化落地,三个面板对应稳态监控的三个关键需求。第一个面板把实时成功率、7 天动态基线、3σ 下界画在同一张图上——实验期间盯这张图,你能直观看到指标相对基线的偏离程度与距离中止红线的余量,这比看裸数字更能培养团队对「正常波动范围」的直觉。阈值线(0.995)用红绿色带标示,跌破瞬间视觉可辨。第二个面板是混沌实验的专属设计:把 chaos_experiment_active 指标作为 annotation 叠加到所有时序图上,实验的起止时间在指标曲线上画出竖线——复盘时一眼看出「指标变化与实验窗口的时间关系」,这是归因分析的第一手视觉证据。建议所有混沌工具都暴露类似的「实验进行中」指标(Chaos Mesh 有 chaos_daemon 相关指标,自研护栏脚本也应上报心跳指标)。第三个面板监控延迟分位数族,P50/P99/P99.9 三条曲线的「张开程度」就是长尾健康度:三条线贴合说明延迟分布紧凑,P99.9 独自飙升说明少数请求正在遭遇异常。把这个仪表盘设为实验期间的作战室大屏,稳态假设就从抽象定义变成了团队共同注视的具象画面。

实验笔记

【实验目标】为你的系统(或演练用的 Online Boutique 演示系统)构建一套完整的稳态假设:识别北极星业务指标、定义四维度判定条件、实现动态基线、搭建稳态仪表盘,并通过「撒谎测试」验证稳态假设的健壮性。本演练不注入故障,专注稳态定义本身——记住,稳态假设的质量决定后续所有实验的价值。

【环境准备】Kubernetes 集群 + Online Boutique 演示系统(参考第 1 章 labNotes)+ kube-prometheus-stack 监控栈 + Grafana。确认 Prometheus 已采集到应用 HTTP 指标(若演示系统未暴露业务指标,可用其自带的 opentelemetry 集成或为 checkoutservice 添加简单的 Prometheus counter 埋点——这个埋点过程本身就是极好的练习)。

【演练步骤】第一步(40 分钟),用户旅程映射:画出 Online Boutique 的核心旅程(浏览商品 → 查看商品详情 → 加购物车 → 下单),为每个环节找出对应的可观测事件与现有指标,填入旅程-指标映射表。标记出「想要但没有」的指标(例如「下单成功」若无直接埋点,思考用什么代理)。第二步(40 分钟),北极星指标选择:从映射表中选出 1 个端到端业务指标作为北极星(建议:每秒成功下单数),论证它满足端到端性、用户视角、单调性、灵敏度四标准。第三步(60 分钟),稳态假设实现:参照代码示例 1、2,编写 Recording Rules 实现四维度稳态(可用性绝对阈值 + 3σ 动态基线、P99 延迟、吞吐量下限、质量代理),部署到 Prometheus 并用 promtool 验证规则语法。第四步(40 分钟),仪表盘搭建:按示例 6 创建 Grafana 稳态仪表盘,包含基线对照面板与分位数面板。第五步(40 分钟),撒谎测试:运行示例 5 的审计脚本,逐项检查零流量、长尾、僵尸数据、维度完整性四个失效模式,记录发现的问题并修复。第六步(30 分钟),压力验证:用 k6 对系统施加波动负载(模拟高峰与低谷),观察稳态仪表盘是否正确反映系统状态——负载变化时动态基线是否适应、阈值是否误报。

【预期结果】一套部署完成、经过验证的稳态假设体系:Recording Rules 文件(纳入 Git)、Grafana 仪表盘(可分享链接)、旅程-指标映射表、撒谎测试报告。验收标准:任意时刻看一眼仪表盘,能在 10 秒内回答「系统现在是否处于稳态」。

【进阶挑战】(1)实现对照组分流(示例 4):为演示系统添加 Nginx 分流与分组指标,验证组间基线差异小于 0.5%;(2)为稳态偏离配置 Alertmanager 告警,模拟偏离(手动把某服务副本缩到 0)验证告警在 2 分钟内触达;(3)设计「质量维度」的更强代理:为下单响应添加内容校验(订单号格式、金额非零),把校验失败率纳入稳态;(4)写一份稳态假设评审文档,邀请搭档按本章误区清单(过窄、长尾、因果、零流量、时变性)逐项挑战你的定义;(5)计算你的 3σ 下界在过去 30 天会被自然波动触发几次——如果超过 1 次,说明 σ 估计或阈值需要调整,这就是统计方法与现实磨合的真实体验。

延伸阅读

  • 《Chaos Engineering: System Resiliency in Practice》第 5 章(稳态假设专章)——本书对稳态的原始论述,包含 Netflix SPS 指标设计的第一手细节:为什么选播放启动而不是播放时长或错误率,指标演进过程中踩过的坑。
  • 《Site Reliability Engineering》第 4 章 "Service Level Objectives" 与第 6 章 "Monitoring Distributed Systems"——SLO 的定义方法与 Google 的四大黄金信号(延迟、流量、错误、饱和度),是稳态假设与 SRE 体系衔接的理论基础,免费在线阅读。
  • Netflix Tech Blog: "It's All a Bout Testing: The Evolution of Chaos Automation Platform"——ChAP 的对照组实验与统计判定设计的详细披露,包括如何用 SPC 区分实验效应与自然波动,是「稳态验证统计方法」的最佳工程文献。
  • Prometheus 官方文档 "Querying basics" 与 "Recording rules" 章节——本章大量 PromQL 的语法基础,特别是 offset、子查询 [7d:1h]、histogram_quantile 的精确语义,建议动手验证每个函数的行为。
  • Google SRE Workbook 第 2 章 "Implementing SLOs"——把 SLO 从概念落地为可运行系统的完整指南,包含多窗口多燃烧率告警等高级技巧,可直接复用于稳态假设的告警设计。
  • 文章 "The How and Why of Fitting Things Together"(Netflix 关于 SPS 指标的分享)——讲述 Netflix 如何围绕单一业务指标组织整个可靠性体系,对「北极星指标」的说服力有最生动的诠释。
  • 《Thinking in Systems》(Donella Meadows)——系统思维经典,虽然不直接讲软件,但关于存量、流量、反馈回路、系统韧性的论述是理解「稳态」概念的思想源头,能显著提升你对复杂系统行为的直觉。
  • Grafana 官方文档 "Annotations" 与 "Thresholds"——把实验窗口叠加到指标曲线、配置视觉阈值的具体操作指南,是搭建稳态作战室仪表盘的实操参考。
3/12稳态假设
0%