← 回首页

工程日志

熔断容易,难的是熔断之后往哪儿去

7 月 19 日那次事故里,熔断本身工作得完全正常。出问题的是它后面的储备容量。

这篇讲的那次「7 月 19 日事故」是占位样例,没有真的发生过——它和事故复盘页上标着「样例」角标的是同一条。写成这个样子是为了说明我们打算怎么写工程日志:时间线到分钟、根因写清、改动署名。真实事故发生后会替换掉它,并去掉这段说明。

最后更新
2026-07-24
作者
供给与运维

熔断阈值不该是「超时」

最初我们的熔断条件是「请求超时」。听起来合理,实际上太晚了:等到首字节超时触发,用户已经等满了整个超时窗口,而且这样的请求已经堆积了一批。

现在真正在跑的条件是「连续 2 次失败」,退避 30 秒起、逐级到 15 分钟。失败只算 5xx、连接失败和超时——429 不算:限流说明这条路线还活着,熔断它只会把流量推给别人,那正是要防的连环崩。 我们想做的是「首 token 延迟超过滚动中位数 3 倍、持续 60 秒」:不同模型的正常延迟差着数倍,写死一个阈值要么太敏感要么形同虚设。但它还没有做——延迟的 p50/p95 目前只用来给路线打分,没有接进熔断判定。这一段原来写成「现在的条件是……」,那是错的:把想做的事写成已经做了的事,比不写更糟。 也不在这里补一个「能提前多少」的估计:写一个没量过的数字,会让整篇文章的可信度跟着一起塌——这条原则本来就写在这篇文章里,而英文版当时没守住。

熔断之后,容量够不够

熔断把流量从 A 全部切到 B 的那一刻,问题就从「A 还好吗」变成「B 扛不扛得住」。如果 B 的储备并发只按日常峰值配一点点余量,结果是没有一个请求失败,但所有人都在排队——比直接报错更难查,因为监控上什么都不红。

「有备用」和「备用扛得住」是两件事。 该有的是一条容量口径:任一能力组的储备并发低于当前峰值的某个倍数就告警,不等它真的被用到。这条还没做——具体倍数也不打算现在编一个,它得从真实峰值分布里量出来。上一节刚说过,写一个没量过的数字会让整篇文章的可信度跟着塌,这里同理。

为什么默认不跨能力组降级

跨组降级很诱人:同组没得用了,往下降一档总比失败强。我们选择了默认不这么做。

因为用户没法验证这次降级是必要的。一旦允许静默降级,「是不是偷偷给我用便宜货」这个怀疑就永远洗不清了——而这正是整个 Prism 卖点最脆弱的地方。所以同组无可用模型时默认返回 503,附完整的尝试记录。想要跨组降级的用户可以显式打开。

代价是我们会多一些 503。这个代价我们认,因为可验证比可用率更贵。

灾备的账怎么算

  • 失败的尝试不计费。 只对最终成功返回的那一次计量,中间试了几次都不进用户账单。但流已经开始之后上游断了,已经产出的那部分照常计费——它和「用户自己按了中断」走的是同一条收尾路径(`settle("error")` 与 `settle("aborted")` 都提交真实用量)。
  • 切到更便宜的模型,按实际用的算。 用户占便宜,我们不追。
  • 切到更贵的模型,也按实际用的算。 每条线有自己的售价,路由到哪条收哪条,账单上写实际服务的模型。

最后一条是实打实的成本,而且正好发生在我们已经在处理故障的时候。但让用户为我们的故障多付钱,一次就足以毁掉信任——这笔账我们算过,付得起。