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
500fallback 约是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 结果图
这组结果要表达的是:
500fallback 比 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级别
500fallback 已经把请求拉到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
500fallback 的单请求代价约上升到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 分开测、分开看、分开优化。
分享
