Lazy loaded image
007-故障路径还能返回200,但成本已经不正常了:failure path benchmark 与结果解读
字数 3458阅读时长 9 分钟
2026-4-10
type
Post
status
Published
date
Apr 10, 2026
slug
gateway007
summary
tags
gateway
推荐
category
icon
password

说明

文章 005 建立的是正常路径基线。
而这篇 007 要解决的是一个更实际的问题:
当网关进入 fallback、stream 中断或者 backend unhealthy 这些故障路径时,我们该用什么基线来判断:系统虽然还活着,但时间消耗已经不正常了。
压测failure path benchmark目的:
  • 给后续故障场景压测提供对照基线
  • 给监控和告警提供判断标准,避免把“还能返回 200”误判成“系统成本仍然健康”
  • 给后续优化排序提供依据,知道下一步该优先优化 timeout、retry、fallback 还是 healthy path 本身
所以这篇文章真正关心是:
故障发生以后,即使客户端还能拿到 200,代价是不是已经偏离正常基线。
对网关来说,很多故障场景不会立刻表现成“请求失败”:
  • primary timeout 之后 secondary 接住了请求
  • upstream 500 之后 router 走了 fallback
  • stream 在首包之前失败,但还能切到 secondary
  • /readyz 仍然是 200,因为系统还有一个 healthy backend
如果只盯着“还能不能返回 200”,很容易得出一个错误结论:
系统还活着,所以性能代价应该不大。
这篇文章要做的,就是把这个误判拆开。

一、问题定义

故障场景压测最容易犯的错误,是把“还能返回 200”误判成“性能代价不大”。
在这个项目里,至少要区分下面四类路径:
  • healthy path
  • fallback path
  • interrupted stream path
  • not-ready edge
它们都和故障有关,但时间成本完全不同。
  • 有的代价体现在 latency 膨胀
  • 有的代价体现在 stream 完整性变化
  • 有的代价并不会马上把 /readyz 打成 503,但请求成本已经明显偏离健康状态
所以这篇文章只抓住一句话:
故障路径还能返回 200,但可能时间消耗已经远超正常基线。

二、故障路径分层图

先把这次讨论的对象说清楚。
下面这张图要表达的是:
  • healthy path 对应文章 005 建立的正常基线
  • fallback path 对应“可用性保住了,但请求代价变了”
  • interrupted stream path 对应“不是慢一点的成功,而是协议已经开始后的终止”
  • /readyz 反映的是 aggregate readiness,不是单请求成本

三、先把对照基线拿出来

这篇文章只拿最关键的 local HTTP adapter baseline 作为参照。

non-stream baseline

  • P95 = 34 ms

stream baseline

  • TTFT P95 = 28 ms
  • Latency P95 = 98 ms
这组数字代表的是:
  • 网关已经进入真实 adapter 代码路径
  • 已经包含 socket I/O、request construction、response parsing 等本地 HTTP adapter 成本
  • 但还没有进入故障路径
所以后面所有 failure path,都用它做参照。

四、这组对照基线到底有什么用

如果没有文章 005 的 healthy baseline,后面所有 failure-path 数字都很难判断轻重。
比如:
  • 213.8 ms 单独看,不能确定是否不正常
  • 1.017s 单独看,也只能感觉“有点慢”
但一旦把它们和 healthy adapter baseline 放在一起看:
  • healthy non-stream 是 P95 34 ms
  • 500 fallback 约是 213.8 ms
  • timeout fallback 约是 1.017s
你就能立刻判断出三件事:
  • 这些故障路径虽然还能返回 200,但时间成本已经不是正常路径的量级
  • /readyz = 200 不能说明请求成本仍然健康
  • 后续该优先优化的,不再是 healthy path 的几毫秒,而是 timeout、retry、fallback 和 unhealthy recovery 这些 failure-path 机制
也就是说,这组数据不是“为了证明故障会变慢”,而是为了给后续监控、压测和优化建立一条明确的 failure-cost 对照线。

五、timeout fallback:可用性保住了,但代价几乎吃满 timeout budget

第一个场景是最直观的。

场景配置

  • primary synthetic upstream sleep 2.5s
  • adapter timeout 1s
  • secondary backend 保持 healthy

观察结果:

  • 客户端仍然拿到 200
  • 返回内容来自 secondary backend
  • 端到端耗时大约 1.017s
  • /readyz 仍然是 200

和基线对比

healthy non-stream adapter baseline 是:
  • P95 = 34 ms
而 timeout fallback 这一类请求的单次代价大约是:
  • 1017 ms
这不是“小幅变慢”,而是代价等级已经完全换了一层。

timeout fallback 结果图

这组结果说明的是:
timeout fallback 的可用性,是用等待成本换回来的。
它没有把服务立刻打挂,但它几乎把 timeout budget 整段吃掉了。

六、upstream 500 fallback:代价比 timeout 小,但仍然明显偏离正常路径

第二个场景是 upstream 持续返回 500

场景配置

  • synthetic upstream 持续返回 500
  • adapter 允许一次 retry
  • retry 失败后 router fallback 到 secondary

观察结果

结果是:
  • 客户端仍然拿到 200
  • traced request 总耗时约 213.8 ms
  • upstream 记录到 两次 500
  • /readyz 仍然是 200
这个细节很重要。
两次 500 说明这里不是直接切 secondary,而是 adapter 先做了一次 retry,随后 router 才接管 fallback。

和基线对比

healthy adapter baseline:
  • P95 = 34 ms
当前这条故障路径:
  • 213.8 ms

500 fallback 结果图

这组结果要表达的是:
500 fallback 比 timeout fallback 代价小,但它仍然不是正常路径。
它的主要代价来自:
  • adapter retry
  • router reroute
  • secondary backend 补位
不是单纯“又慢了一点”,而是请求已经进入了一条新的成本路径。

七、stream 首包前失败:完整输出还能保住,但时间成本已经不再接近正常流式基线

stream 场景不能只看一个 latency 数字,因为这里首先要守住的是协议边界。
先看首包前失败。

场景配置

  • upstream 在 first chunk 之前失败
  • router 仍然有时间 fallback 到 secondary

观察结果

结果是:
  • 客户端仍然收到完整 SSE 响应
  • 最终仍然有 data: [DONE]
  • request duration 约 209.7 ms
  • metrics 中有 provider_request_failed
  • metrics 中也有 provider_fallback_succeeded
这说明 stream 虽然出故障了,但它仍然属于可恢复场景

和基线对比

healthy stream baseline 是:
  • TTFT P95 = 28 ms
  • Latency P95 = 98 ms
而首包前失败这条路径,已经明显高于正常流式基线。
虽然当前仓库没有把这类故障场景单独做成完整 TTFT 分布表,但仅从 request duration 和 fallback signals 来看,成本已经不是 healthy stream 的量级。

pre-first-chunk 结果图

这组结果说明的是:
首包前失败仍然可以 fallback,完整输出也还能保住,但它已经不是接近正常基线的 stream。

八、stream 首包后中断:这已经不是“慢一点的成功”,而是协议生效后的诚实终止

另一个 stream 故障场景更关键。

场景配置

  • stream 已经开始输出
  • 上游随后中断

观察结果

结果是:
  • 客户端先收到 partial chunk
  • 之后连接结束
  • 没有 data: [DONE]
  • metrics 中有 provider_stream_interrupted
  • 没有 provider_fallback_succeeded
这几个信号放在一起,意义非常明确。
一旦首包已经发给客户端,网关就不能再假装前面什么都没发生,再去拼一条新的 fallback stream。
如果那样做,得到的不是恢复,而是协议污染。

post-first-chunk 结果图

这组结果说明的是:
首包后中断不再属于 fallback 成功,而属于协议已经生效后的诚实终止。
所以 stream failure 不能只按“快慢”去理解。
必须先区分:
  • 首包前失败
  • 首包后中断
这两个场景的语义根本不是一回事。

九、为什么 /readyz = 200 时间成本仍然可能已经高出基线

这批结果里,一个很容易被误判的点是:
很多故障场景下,/readyz 仍然保持 200
这并不表示当前请求成本还接近正常基线。
在当前网关设计里:
  • 只要还有一个 healthy backend,/readyz 就可能继续返回 200
  • primary 进入 cooldown,不等于 gateway 立刻 not ready
  • 只有所有 backend 都 unhealthy 时,/readyz 才会变成 503
所以 /readyz 的语义更接近:
  • aggregate readiness
  • traffic admission signal
而不是:
  • single-request cost signal
也就是说,下面这些事情都可能发生在 /readyz 仍然是绿的时候:
  • timeout fallback 已经把请求拉到 1s 级别
  • 500 fallback 已经把请求拉到 200ms 级别
  • stream 首包前恢复已经明显高于健康流式基线
这就是为什么 /readyz = 200 不能被误读成:
系统现在的请求代价仍然接近正常。

十、结果总览

把当前仓库里已经成立的 failure drill 和 healthy baseline 放在一起,可以得到下面这张表。
场景
客户端结果
关键代价
readiness
说明
healthy non-stream adapter path
200 JSON
P95 34 ms
200
正常 non-stream 基线
timeout fallback
200 JSON
1.017s
200
可用性保住,但代价接近 timeout budget
upstream 500 fallback
200 JSON
213.8 ms
200
明显偏离 healthy baseline
stream 首包前失败
完整 SSE + [DONE]
209.7 ms
200
仍可 fallback,完整输出被保住
stream 首包后中断
partial SSE
[DONE]、无 fallback success
200 或恢复中
协议边界上的终止,不是恢复成功
all backends unhealthy
/readyz = 503
readiness lost
503
aggregate readiness 真正翻红
这张表最关键的地方,不是哪一项“更慢”。
而是它把几类 failure path 的代价结构拆开了:
  • 有的是 等待成本
  • 有的是 retry + reroute 成本
  • 有的是 完整 stream 被保住,但代价升高
  • 有的是 协议已经开始,只能诚实终止
  • 有的是 aggregate readiness 终于失效
这几类不能混成一句“故障时会变慢”。

十一、边界说明

本文里的对比,不是 hosted provider benchmark,也不是完整的 chaos benchmark。
当前用的是本地 synthetic upstream,所以这里测到的是:
  • gateway path
  • adapter path
  • fallback path
  • interruption path
  • 本地 upstream 交互开销
它没有覆盖:
  • 高并发、长时间故障窗口下的尾延迟分布
  • probe recovery 抖动期间的波动统计
  • 真实 hosted provider 的 WAN jitter
  • 公网环境下更复杂的 latency 分布
所以这组结果的正确定位是:
它已经说明 failure path 的代价可以被量化,但它还不能替代完整的故障压测报告。

十二、如何复现实验

这组结果的价值之一,在于当前仓库里可以稳定重放。
保持与 article 005 相同的 local HTTP adapter 配置,只切换 synthetic upstream mode 即可。

十三、结论

文章 005 建立的是健康状态下的性能基线。
而这篇 007 真正建立的,不只是“failure path 也能被量化”,而是:
网关的可用性,不能只看还能不能返回 200,还要看 failure path 的时间成本已经偏离正常基线多少。
当前仓库里的结果已经足够说明这一点:
  • healthy non-stream adapter baseline 大约是 P95 34 ms
  • upstream 500 fallback 的单请求代价约上升到 213.8 ms
  • timeout fallback 的单请求代价约上升到 1.017s
  • 而这几个场景下,/readyz 仍然可能是 200
这组结果真正的用处,不只是说明“故障时会变慢”,而是给后续工程判断立了一条标准线:
  • 不能只用 /readyz 判断系统是不是“基本正常”
  • healthy benchmark 和 failure-path benchmark 必须分开看
  • 后续该优先优化的,不再是 healthy path 的几毫秒,而是 timeout、retry、fallback、cooldown recovery 这些真正决定故障代价的机制
对 LLM Gateway 来说,“还能返回 200”只说明系统还活着,不说明这次请求的成本仍然正常。真正有意义的,是把 healthy cost 和 failure cost 分开测、分开看、分开优化。
回到首页