摘要:讲发布策略的文章,通常在比较"几套环境、切多少流量、资源贵不贵"。这些指标描述手段的形态,不描述失败时的行为。这篇把发布还原成一次受约束的状态转移:四种状态,以什么顺序、什么粒度改变;失败时能否沿一条代价有上界的路径退回。看清这几点就够了------五种机制的差异不在拓扑,在原子动作与回滚路径;A/B 属于另一个坐标系;策略几乎不改变暴露的总量,它改变的是暴露的分布。
读法:全文是一条问题链,每章回答一个问题,并引出下一个。

适用范围:讨论需要连续服务、且多个版本在发布窗口内共享状态(尤其是共享数据库)的在线系统。离线批处理、纯静态分发、客户端强版本锁定不在讨论范围。文中数字用于比较量级、推导取舍,不是精确估算。
一、一次发布,改的是四种状态
讲发布策略的文章,开头往往是一张表:是否零停机、回滚快不快、资源成本高不高。这张表本身没错,但它把注意力引向了一个好画图的东西------拓扑。几套环境、权重多少、用什么负载均衡。拓扑容易讨论,也容易实现,于是慢慢被当成了问题本身。
拓扑是手段,而手段会随基础设施换代被替换。真正决定一次发布是否可控的,是这套手段背后改了哪些状态、改到了什么程度。
部署、放量、实验,这三个词常被混着用,先把它们分开。

这篇用"发布"作总称,指从部署、放量到功能生效的全过程。在"单体应用加一台机器"的年代,这三件事其实是同一件。代码传上去,功能就发布了,用户就看见了。缺陷是结构性的,代码一旦上线就在影响全部用户,中间没有缓冲区。
现代发布机制都在做同一件事:把这三件事拆成可独立控制的步骤。怎么拆,就是策略的分野;能拆多细,要看分流决策点落在什么位置、服务是否持有状态、指标能不能按版本切片。
拆开之后会看到,一次发布同时改变四种状态,它们的可逆性差得很远。

前三种可以撤,它们描述"系统如何运行"。第四种部分不可逆,它描述"系统已经产生了什么"。这是所有发布事故的共同结构:流量和代码退得回来,数据退不回来。 我判断一套发布方案好不好,先看它对第四种状态做了什么,再去看它把流量切得多细。
退得回去吗?退回来要多快?这是两个必须分开回答的问题,前者是能力,后者是时间。再往下还有一个更前置的:你怎么知道它已经坏了?
二、撤退的上限,被最粗的载体锁死
2.1 退回的阶梯
把"退回载体"竖着读,是一条阶梯:改一个配置键 < 改一条路由规则 < 换一组实例 < 重建一份制品 < 重构一次数据。载体越轻,回退越确定。
由此得到第一个判断:**一个系统的撤退成本,被它最粗的那一层锁死。**只支持整版本回滚的团队,只能讨论回滚快不快,讨论不了回滚到什么程度------他们的终点只有一格。
这条阶梯衡量的是载体有多轻,不是恢复有多快。换回旧制品在载体意义上是轻的,可若要经过编排层逐批重放,恢复时间仍与一次发布同阶(§6.1)。我在评审别人的发布方案时见过不少这种错配。方案里写着"回滚只需切回路由,秒级",实际演练一次要十几分钟,因为切回去的是引用,重放的是实例。
2.2 数据的三档可逆性
数据的可逆性再分三档,它决定了所有策略的可用边界。
- 加法型(新增列、表、索引):保留即可,接近零成本,天然可逆。
- 变换型(回填、格式转换):可重算,成本随数据量增长,重算期间有竞态,属于"技术上可逆、工程上昂贵"。
- 删除与覆盖型(删列、就地更新、外部副作用):不可逆,一旦执行,回滚窗口永久关闭。
所以发布设计真正要做的事,是把变更拆成可逆段与不可逆段,把不可逆段推到确认之后。选一个切换方式,只是其中一步。这件事没有任何流量策略能代劳。三档里最容易被当成"已经处理过"的,是变换型;我自己的排期习惯是,只要动过存量数据,就先按不可逆那一档来算。
2.3 兼容:数据退不回去的真正原因
有没有载体只说清了一半,另一半是,退回去的那个版本,还认不认得现在的数据?
讨论任何机制之前,先检验它们共同依赖的假设:
任意时刻,线上的所有版本都能正确解释当前的数据形态,并且彼此的写入都能被对方正确解释。
只要前提一破,后面全部失效,蓝绿的原子切换会切出一批读不懂新数据的实例;金丝雀的小流量会因为第一批不兼容写入而失去止损意义;滚动的混合期会把错误均匀撒到全部流量上。
写成可检验的形式,是发布窗口内并存版本 A 与 B,则 A 读 A 写、A 读 B 写、B 读 A 写、B 读 B 写四种组合都必须成立。中间两项是跨版本、且方向不可互换的义务------A 能读懂 B 的写入,不代表 B 能读懂 A 的写入。破坏性变更至少打破其中一格。
扩展-收缩(Expand-Contract)常被理解成"分两步改表",它真正做的事,是把兼容期提升为一个显式的、有时长的系统状态。五个动作的顺序不能交换。
- 扩展:只做加法,新增可空列、新表、新索引,对旧版本无害。
- 双写:同时写新旧两种结构,读仍走旧结构,保证旧版本能读懂。
- 回填:后台迁移存量数据,控制批次与速率。
- 切读:确认回填完成后改为读新结构。
- 收缩:等旧实例全部下线、回滚意图消失后,再删除与收紧。
回填读到的是旧结构快照,而期间在线流量仍在写入;若以整行覆盖的方式落库,就会把用户刚写的新值覆盖回旧值,产生一次安静且难以复现的数据丢失。解法是条件写入:仅当目标为空或水位更旧时更新,或者用单调版本号做冲突检测。
两次写入彼此独立时,双写就可能只成功一半。先写新后写旧、旧写失败就分裂;反过来则新结构滞后。可选的路不多------同事务原子写(代价是耦合与锁竞争)、接受最终一致并配套对账补偿、把一侧定义为派生数据允许重放------选哪条都行,但必须先定下"以谁为准"。这一条我不给标准答案,它取决于补偿能力。
真正不可逆的一步是收缩,它同时关闭旧结构、旧代码路径与回滚窗口,所以只在一种状态下成立,即旧实例全部下线、且已经没有回滚意图。"暂时没出事"不是条件,是运气。
2.4 三类常被漏掉的状态
并存的版本本身就是成本,两个版本并存要维护两组跨版本读写关系,四个版本并存就是十二组。观察时长本身不增加组合数,它延长的是"多个版本同时存在"的持续时间,并让下一次发布更容易叠进同一个窗口,从而把并存的版本数推高。共存版本数该像容量一样做预算,不是像保险一样越久越好。
缓存、会话、本地索引这类影子状态不参与结构变更评审,却同样携带格式假设。新旧版本序列化方式不同,切换瞬间就会读到对方写入、自己解析不了的值------表现为零星出现、重启即消失的反序列化异常。缓存让它可失效、可重建、格式可识别即可;会话不能套用同一标准,因为它不可重算,清空等于全量强制登出。判断标准是双层的:清空后系统能否在可接受时间内恢复?清空是否造成不可接受的用户损害?
还有一类状态根本不在数据库里:消息已投递、支付已扣款、通知已发出、第三方已落单。它们无法回滚,只能补偿------幂等键防重复生效,对账发现偏差,补偿事务抵消效果。发布期的重试是常态,接口不幂等就可能造成重复下单、重复扣款。所以发布设计必须能回答:这次变更,在不可撤销的外部世界上留下了什么?答不上来,这次发布的可逆性上限就等于那个外部系统的上限。
那能不能靠灰度把这些风险兜住?能力有硬边界。企业里往往没有独立灰度库,建库意味着双向同步的一致性、TB 级成本、数据合规、跨团队资源博弈,任何一项都足以让方案在预算阶段被否决。于是灰度大多共享生产库,能力被逐层锁死:入口层可按用户分流;单系统内可做全链路闭环;到数据层卡住,因为结构变更无法在共享库上隔离验证;到跨系统也卡住,因为灰度标识要在调用链、消息、定时任务里全程透传。
由此得到一条判据,能灰度的范围,是"灰度标识能透传的范围"与"数据能被隔离的范围"两者的交集,取更小的那个。难点从来不在流量,在上下文传播。
退得回去,要的是载体还在、数据可撤、旧版本还读得懂新数据,三件同时成立。最后一件最常被忽略,也最难补。
三、止血的四段,贵的不是执行
"回滚快不快"是句含糊话。拆开看,止血耗时是四段相加。

收敛最容易被忽略,却常常是真正的瓶颈。路由变更是一个过程,不是瞬时事件,时间常数由最慢的一层决定:域名解析的缓存有效期,分钟级;已经建立的长连接不会重新解析,可能存活数小时;配置分发与客户端缓存;应用内的本地缓存、连接池、会话。
四段里,只有执行与收敛属于回滚动作本身。检测与决策发生在发布系统之外,一个取决于观测能力,一个取决于由人还是由函数决定。把"回滚快不快"等同于"执行快不快",是多数误判的来源。
所以"秒级回滚"是个分层命题,它成立的前提是最慢的那一层也是秒级。入口依赖域名解析、客户端持有长连接,再快的编排也缩短不了收敛时间。同理,"切换完成"也是错觉------指令生效的瞬间,仍有在途请求与存量连接在打旧版本,双向都是如此。
这条推论的实用价值在于排序:**优化止血,应先优化检测、决策与收敛,而不是执行。**把执行从 30 秒压到 3 秒,收益远小于把检测与决策从 10 分钟压到 1 分钟。多数团队的时间花在前者,因为前者更容易买到。如果真按四段去量一遍,我猜多数人会发现自己在最便宜的那一段上花得最多。
四、暴露量:四个因数,两种局面
止血耗时只是暴露时长。一次故障的业务损失,取决于四个数相乘。

错误、延迟、资损、数据污染,都可以折算成单次损害这一个抽象量。量纲不统一,只用来比较量级。四个因数的性质并不相同。请求速率由业务给定,单次损害由缺陷决定,看起来能压的只有暴露比例和暴露时长。
4.1 一千个请求
把灰度和时长一起往小里调,直觉上损失也在等比例变小。这个直觉成不成立,要看暴露时长究竟由什么支配。
设灰度占比 1%、缺陷相对基线的超额失败率 0.5%、判定需要 5 个失败样本。系统每秒 2000 个请求,给新版本的约 20 个;这 20 个请求里每 10 秒出现一个失败样本,凑够 5 个约需 50 秒。
失败率降到万分之二,平均 250 秒才出现一个。凑够 5 个,21 分钟。
灰度再压到 0.1%,三个半小时。
这里有一条简单的换算关系:需要的请求数 = 需要的失败样本数 ÷ 失败率。 5 ÷ 0.5% = 1000 个请求。灰度占比调小,采样时间等比例变长,最终经过新版本的请求总数还是这 1000 个。这几个数我反复算过很多遍,最反直觉的一条是,灰度从 1% 降到 0.1%,损害一分没少,只是晚了十倍。
该问的不是灰度多小才安全,是凑够样本要多久。
方案里常写"1% 灰度跑十分钟",看上去是个稳妥的数字。它够不够,完全取决于系统吞吐和缺陷率;低流量或极低缺陷率下,这个窗口只能证明"在这个样本量下没观察到失败",不能读作"没有失败"。
上面算的都是期望值,第 5 个样本什么时候到,本身就是个随机量。95% 的情况下可能比平均值慢近一倍,把 50 秒当窗口,就有相当概率把"没看到"误判成"没问题"。另一处更要紧的是,失败率必须是相对基线的差值------基线本身有 1% 错误率时,要检出的是 1.5% 与 1% 之间的 0.5 个百分点,所需样本还要计入基线波动;按用户级分流时样本不再独立,这个换算直接失效(§6.3)。
把同一套算术搬到延迟上,数字立刻变得不可用。每秒 20 个请求,一分钟只有 1200 个样本,落在 P99 尾部的约 12 个,估计值本身就会漂移十几个百分点,与"延迟涨 20%"处在同一量级。
4.2 两种局面:压流量有时有效,有时只是摊平
把上面的换算关系代回四个因数,会发现灰度占比和请求速率都被约掉了。灰度越小,采样越久,两边抵消,步子迈得再小也不会少走路。
一线生机在于,暴露时长未必真的由采样决定。现实中它通常还有一个下限:采样间隔、人工观察窗口、变更窗口到点必须决策。这个下限如果比采样时间短,局面就完全不同。

用同一组参数(失败率 0.5%、每秒 2000 个请求、需要 5 个样本),三种灰度算出来的东西一模一样。

在"采得完就等着看"这个局面里,**五种机制对期望损害的影响相同。**这不是否定金丝雀,而是把它买到的东西说准。金丝雀给的是一个暴露上界,最坏情况只影响这个比例的人,不是更小的期望值。它之所以普遍有效,是因为多数团队的暴露时长被"观察 30 分钟"这类人为常数支配,落在第二种局面,此时灰度占比才线性地缩小损害。
落到操作上,第一件事是判断自己在哪个局面,算一算凑够样本要多久,和观察窗口比一下,前者远大于窗口,就在第一种局面,缩小灰度只是摊平。第一种局面能动的杠杆只剩这些:降低对样本数的要求(判据更灵敏)、降低单次损害(隔离、限流、降级)、提高对低失败率的检出能力(影子流量、注入测试),压灰度不在其中。第二种区别过度延长窗口,窗口长度是线性进入损害的,多观察一小时,损害就多一小时。
窗口该定多长,这篇文章给不出通用数字。它取决于能承受多少暴露、判据能压到多灵敏,两个答案都由业务给,不由方法给。
影子流量是这条式子的特例,把请求复制一份给新版本、丢掉响应,用户侧暴露比例为零。它没有消除对共享状态的写入风险,也没有消除外部副作用,必须配合写入隔离或只读设计,否则"用户看不见"只意味着"用户没看到响应"。
这套估算的边界也很明确,它假设请求同质、损害可以线性叠加,而资损与延迟其实不可相加;它假设缺陷独立均匀到达、被完整采样、且可归因到版本;它假设四段严格串联,而现实中执行与收敛会重叠。这些数字用来判断量级与取舍,不用来计算精确损失。
策略压住的是"暴露比例 × 暴露时长",而这两个量在第一种局面里互相抵消。要变,先换局面,再谈比例。
五、看不见的故障,任何策略都救不了
前一章的所有推论,都建立在一个前提上,就是能及时发现失败。这一步决定四段里的第一段,也决定整套防御的上限。

这张表是全文最实用的一个判据,也是"配了自动回滚却仍然出事"的根因:**某类故障在信号表里没有对应行,任何策略对它都无效。**蓝绿的快速回切对内存泄漏没有意义,泄漏被确认时已是数十分钟后,越过了软信号的窗口,跟回滚快不快没关系。
最难处理的是"无信号"那一行,它不是某个指标忘了配,是一类后果没人想到;想到它的办法通常来自复盘上一次事故,不是再加两个监控项。
用业务信号做发布闸门,是用低灵敏度传感器控制高风险动作;只用硬信号,则会漏掉"不报错但变慢三倍"的退化。两层同时存在,才算一次完整验证。
把判据做成函数,参数是这几个:指标、基线、窗口、阈值、连续失败次数。它本质是一次持续的假设检验,原假设是"新版本与基线同分布",每次采样都在积累反驳它的证据。
还有第 0 个参数,也是最容易被跳过的一个。指标必须按版本切片。只看聚合值时,新版本的恶化会被正常流量平均掉。5% 的流量里错误率从 1% 涨到 3%,全局只从 1.00% 升到 1.10%。这个变化在统计上并不算噪声,每秒 2000 个请求、十分钟就是两万个多余错误,难的是它在按绝对值配置的告警里通常不触发。问题出在判据口径,不在信息丢失。
拿绝对值做阈值,在多数业务里没有意义------日内曲线、缓存预热、下游批处理都会改变正常区间,所以基线必须同期取。更严格的做法是让基线组与金丝雀组同时运行、同源流量,代价是额外容量。
两类错误的代价不对称,但都不能忽略。阈值太松会漏检,事故直达用户;太紧会误杀,发布回退。后者常被当作"重发一次就好",其实有长期成本。频繁误杀之后,工程师会绕过它、放宽它、最后关掉它。一个不被信任的自动化,比没有自动化更危险,因为它制造了"已受控"的错觉。我宁可把阈值调松一点,也不愿看到有人开始绕开它。
连续失败次数是抗抖动,不是保险。它把毛刺与趋势分开,代价是"次数乘以采样间隔"的额外延迟,必须计入检测时间。这里的次数是判定次数,与 §4.1 的样本数含义相通但不必相等,两个数要一起定。
自动化的边界得先说清,系统能自动回滚流量与代码,不能自动回滚数据。所以"开启自动回滚"的安全前提是本次变更的数据部分可逆。若包含删除或不可逆副作用,自动回滚只能止血、不能复原,此时需要的是人工确认点,不是更快的自动化。
两件题外话,都指向同一个机制。GitOps 真正做的事,是把发布变成"声明的差分",而不只是用 Git 管配置,于是可逆性的载体从人的操作变成版本化配置,回滚等于反向 diff。平台化(服务网格、无服务器)降低了执行与收敛的实现成本,但平台买的是执行便宜,不是风险更小------暴露面的上界仍由分流粒度决定,状态纪律一条也没被豁免。
先有能看见的信号,才谈得上止血。判据的分层设计,决定了系统能在多长时间内、看见哪一类的坏。
六、五种机制,是同一组答案的五个取值
前三章其实已经给出了评价机制的三把尺子:撤退有多深(载体与兼容)、撤退有多快(四段)、伤面有多大(一千个请求与两种局面)。下面五种机制,差别就在这三把尺子上取不同的值。
下面所有结论,都以 §2.3 的状态兼容为前提。若新旧版本无法安全共享数据,任何机制的回滚语义都不成立。
6.1 滚动:原子是"副本"
滚动维护单一不变量,期望副本数,通过创建新版本副本组、逐步扩缩两组副本完成替换。原子动作是"替换一个副本",不是"切换一次流量"。
版本混合的比例其实不由发布者决定。实际比例取决于副本数、就绪速度与调度延迟,能调的只有两个数:允许临时超出期望副本的比例、允许不可用的比例(最常见的容器编排平台默认都是 25%)。它们管速度和容量,不管"哪些请求走新版本"。
分流发生在副本维度,采样发生在请求维度,两者不等价。长连接会把早期请求长期钉在旧副本上,连接复用让负载分布不均,慢请求堆在少数副本上。滚动的混合是隐式的,金丝雀才是显式的------你以为在看 5% 的样本,其实在看一个比例未知、构成未知的样本。
探针返回成功,说明的只是进程还在。缓存热不热、连接池建没建、依赖通不通,都不在它的判定范围内。新版本是否正确的判断,被完全留给了外部,也就是接口契约的兼容纪律,和发布后的观测。
反向滚动与正向滚动同阶耗时,期间故障持续积分,而且撤不掉已经写入的数据------滚动的"回滚"是一次重放,不是一次返回。这才是"回滚慢"的真实含义。不是执行慢,是回滚期间仍在制造新的暴露。
实例从"被判定下线"到"进程真正退出",必须走完三段:从流量入口摘除、等待在途请求完成、再终止进程。缺一段,每次发布都会稳定产生一批 5xx。终止信号与摘除之间有竞态,这个窗口没法用参数消除,只能靠"先摘后停"的顺序;等待时长要覆盖长尾,取平均延迟等于放过最慢的那批请求。滚动的成败,最终落在这种协议细节上。
它把资源用到最省,但不提供风险控制语义。安全性由"新旧可互操作"这个外部假设承担。
6.2 蓝绿:把原子性从"副本"提到"指针"
蓝绿维护两份完整环境,任一时刻只有一份承载流量。相对滚动,它把原子动作从"替换一个副本"提到了"改写一个路由引用"。
同一个"切换"动作,落在解析层、连接层、请求层,结果完全不同。解析层受缓存有效期影响,连接层不会为存量连接重新解析,只有请求层真正做到逐请求判定。所谓"秒级原子切换",说的是请求级路由的能力,不是"两套环境"的固有属性。
它压的是执行与撤退的不确定性,不是整条止血时间。旧环境原封不动地活着,让执行时间与"回滚路径是否可用"同时变得确定;但检测与决策不受影响,而它们通常才是止血时间的主要成分。准确的说法是,蓝绿缩短的是"从决定回滚到回滚完成"的时间,不是"从故障发生到止血完成"的时间。它实际上承担的是容灾切换的功能,优化撤退的确定性与执行的可预测性,不优化暴露概率。
蓝绿没有可调的暴露比例,它恒为 1。按 §4.2,在"窗口还没用完"的局面里,1% 的灰度只影响 1% 的流量,蓝绿影响 100%,同样的缺陷,损害差一百倍;在"采得完就等着看"的局面里,则与金丝雀打平。它不缩小暴露面,而是把"一部分人受影响"换成"所有人同时受影响",用暴露形态的确定性换撤退的确定性。
两套环境要花两份资源,但只有一部分资源真的翻得动。计算资源可以翻倍,缓存容量、连接池上限、下游配额、数据库连接数、消息吞吐,不会因为多一套环境就自动扩容。切换瞬间,未预热的缓存会让流量直接穿透到数据库,未建立的连接池会触发连接风暴。
回滚窗口并不独立存在,它就等于旧环境的存活期。旧环境一销毁,蓝绿的回滚语义立即退化为"重新部署"。所以"切换后保留观察期"不是谨慎,是蓝绿定义的一部分------慢 SQL、内存泄漏这类问题可能数小时后才显形。
6.3 金丝雀:把暴露面从"未知"变成"可控"
相对蓝绿的全有全无,金丝雀把请求集合切出一个子集给新版本,以该子集的指标作为接受或拒绝的判据。原子动作是"抽样",它第一次让暴露比例成为一个由设计者规定的数。
分流决策点的位置决定语义精度。按副本数近似得到的是"约几成比例的副本",实际请求占比会漂移;按请求特征分流才是可控的抽样。抽样单位从"请求"变成"用户"之后,样本之间产生相关性,统计功效下降,但体验一致性上升。
一致性与伤害集中,是产品决策不是技术决策。用户级分流让同一批人持续承受坏版本:伤害集中、可归因、可补偿,但这批人的体验被彻底牺牲;请求级分流让所有人偶发异常:伤害分散、归因困难。工程上通常分工,取样本用请求级,做体验评价用用户级。
它买到的是暴露上界,不是更小的期望值(§4.2)。真实承诺是"最坏情况下只影响这一小份流量"。这个承诺的价值随单次损害的尾部变厚而上升。数据污染、资损这类后果不能靠缩短时长抵消,只能靠限定面积。金丝雀本质上是用统计换一个更小的暴露面,能压到多小取决于判据设计,不取决于把比例调到多低。
采样、基线、判定、动作,这四步少一步,闭环就断。缺基线,阈值失去意义,因为日内波动常大于版本差异;缺动作,系统退化成一块看板。"人工盯盘"本质是用注意力替代判定函数,可靠性随发布频率下降。
流量能撤回,数据撤不回。第一批不兼容写入会留在库里,等旧版本读到报错。共享数据层上,它的止损能力有上限。止损的对象是流量,受伤的是数据。
6.4 红黑:把"每次都不一样"变成"每次都一样"
红黑用不可变制品构建整套新环境,验证后整体替换,旧环境销毁。
回滚语义并不强于蓝绿,两者都是引用切换。区别在别处。红黑等于蓝绿的路由切换加每次重建环境;如果蓝绿本来就每次重建,红黑没有额外的机制收益。
长期存活的环境会漂移,有人改过配置、打过补丁、手工装过依赖。这些差异不可见、不可审计,却会让"同样的制品,行为不同"。每次全新建环境,消除的是这类方差。红黑真正买到的是运维一致性,不是风险控制。"这次发布"和"上次发布"因此成了可比较的同一件事。
红黑的回滚有一个前提,就是旧制品还在。旧制品被覆盖、基础镜像被回收,回滚就变成"用当前工具链重建旧版本",确定性与滚动相同。新建环境还会引入新的时间常数:镜像分发、实例启动、依赖预热构成"新环境未就绪、旧环境仍在承载"的叠加窗口,这是上线抖动最常见的来源。
6.5 A/B:换了一个坐标系
A/B 用稳定的分桶函数把样本映射到互斥组,比较各组业务指标的差异是否显著。原子动作是"划分一个桶"。
前置条件是各组都已达到上线标准。组间差异是产品假设,文案、排序、定价、交互,不是代码质量,所以它不承担风险控制职能,也不该用作发布闸门。一个转化率更高但内存泄漏的版本,在这里会被判为赢家------它的因变量里没有"健康"这一项。
A/B 的失效几乎全是统计意义上的:样本不足把噪声当结论;分流不随机导致组间不可比;人群构成不同会让分层结论与整体结论相反;指标被污染(上游缓存混流、埋点版本不一致)导致结论指向错误原因;多重比较导致"总能找到一个显著结果"。工程上的硬要求是用户级稳定分流,通常用一致性哈希把同一用户固定到同一组;按请求随机分流会让同一用户在组间跳变,行为数据被污染。
它可以与金丝雀叠加,但顺序不能颠倒。先证明"能跑",再追问"值不值"。反过来等于用业务指标代理技术健康度:转化率没变,不代表没在泄漏内存;止血是秒级的,业务判据是天级的,两者差几个数量级。
"发布"这个前缀是历史误称,它是效果实验。
6.6 一张表收束

**五种机制没有高下,只有取值不同。**要深的撤退就准备旧环境,要窄的暴露就控制抽样,要可复现就重建环境。选错的常见原因,是先选了机制,再倒推它该解决的问题。
七、要设计五个层,不是挑五种策略
把五种机制排名是脱离上下文的做法。更有用的是承认,"发布"其实是五个可以独立演进的决策层。

这张表最大的价值,是暴露一个常见困境的真实成因。**多数团队不是选错了策略,而是只有前两层、缺后三层。**于是所有风险控制压力都压在编排层,指望用一个可用性参数,解决本该由开关、兼容窗口和判定函数分担的问题。
五层里最容易被低估的是功能层。它把"代码在不在机器上"与"功能对用户是否生效"解耦,代码可以以关闭状态铺满全量,生效范围由运行时配置决定。由此得到一个其他机制都没有的性质。回滚不动代码、不动流量,只改一个判定条件。对移动端这种"版本以周计、无法回退"的场景,这几乎是唯一可用的撤销手段。代价是债务,每个开关都是一条永久分支,需要生命周期------创建人、过期时间、清理动作。缺一件,开关系统在一两年内就会变成一份没人敢删、也没人说得清的行为说明书。
具体怎么选,看损失与可逆性两件事。

上表第三行最容易被误读,蓝绿在期望值上通常劣于金丝雀,它把暴露比例置为 1,窗口受限局面下损害是 1% 灰度的 100 倍。选它的理由不能是"期望损失更小",只能是存在不可逆后果------数据污染、对外副作用、资损这类尾部极厚、且无法用时间抵消的损害。此时看的是单次损害的最大值,不是均值;蓝绿买的是后果的上限。
反过来也成立,一次完全可逆的变更,仅因为"版本重大"就选蓝绿,等于把可逆性换成了更大的暴露面。这看起来保守,实际是在两个都要付的代价里多付了一个。那条被反复验证的原则------普通服务滚动、核心服务金丝雀、重大版本蓝绿兜底------成立的理由不是"重要性"不同,是回滚成本曲线与后果形态不同。我自己的排序是,先看数据层有没有纪律,再看流量层,最后才看编排层。倒过来排,钱多半花在看得见的那一层上。
不要问"用哪种策略",要问"哪一层还缺"。缺数据层,任何流量策略都只是把问题推后。
八、落地前,过三张清单
前面七章给了判断,这一章给可执行的检查。
常见错法

回滚路径从不演练,等于把它的第一次执行留给事故现场。这条路径要经过解析缓存、长连接、在途请求,耗时不掌握在发版的人手里。
发布前该答的问题

把前面七章压到最短,剩下这几条。
第一,发布能力的上限由可逆性设计的深度决定,不由策略的先进程度决定。能把多少变更拆成加法型、可重放型、可补偿型,就有多大的发布自由。策略只是把这份自由兑换成时间与流量的工具。窗口受限的局面里,缩小流量能线性缩小损害;检测受限的局面里,任何策略都必须让那一千个请求过去。
第二,分流粒度决定了能看见什么,也决定了能撤回多少。只能按副本分流,就只能观察一个比例未知的样本,也只能做一次全量回退;能按身份分流,才能定向验证与定向撤回。观测能力与撤退能力,是同一个能力的两个方向。
第三,状态兼容是所有流量策略的前置条件。平台化降低了执行的代价,没有降低兼容的义务;删除一列不会因为上了服务网格就变安全。
我一直觉得,好的发布系统不是不出错,是错得起:任何一次错误都被限制在一个已知边界内,并且存在一条演练过、代价有上界的退回路径。这句话可以检验:损害有上界(暴露比例被限住,或单次损害的尾部被隔离),退回路径存在(载体还在),退回时间可承诺(收敛层已知且被验证过)。三者缺一,剩下的就只是运气。
所以下次发布前,先花十分钟算一件事:凑够 5 个失败样本要多久,和你的观察窗口比一比。这一算比换任何一种策略都值。