type
Post
status
Published
date
Apr 10, 2026
slug
gateway006
summary
tags
gateway
category
icon
password
摘要
对LLM Access Gateway来说,一次模型调用已经可以被还原成一条可关联、可排障、可解释的事件链。
前几篇文章证明了基础链路、治理闭环、故障边界和正常性能基线。这一篇文章关注的是:
- 一次失败请求能不能被讲清楚
- 一次 fallback 能不能被串回具体请求
- provider 当前状态能不能被直接看到
- 不依赖完整 observability platform,是否已经能完成最小排障闭环
当前成立的是一套轻量 observability closure,不是完整 observability platform。
一、问题定义
一个网关的成功路径通常是:请求进来,鉴权通过,路由到 provider,返回结果,事情就结束了。
失败路径却很多,一次调用可能失败在不同层面:
- 请求刚进入就被拒绝
- handler 或 service 处理失败
- provider 首次尝试失败后 fallback
- 流式请求在首 chunk 之前失败
- 流已经开始输出后被中断
对客户端来说,这些都可能只是一次“调用不对劲”;但对网关来说,它们是完全不同的故障故事。
所以重要的是:
能不能把一次请求讲清楚。
如果不能把一次失败从入口一路讲到 provider 尝试和最终响应,排障就会退化成猜。
二、为什么相关性优先于大盘
dashboard 更擅长回答聚合问题:
- 错误率是不是在升、某个 backend 最近失败多不多、TTFT 有没有恶化
但故障通常不是从聚合开始的,而是从一条具体请求开始的:
- 为什么这次超时了、为什么这次没有 fallback、为什么这次只返回了半截 SSE
对当前这个网关来说,observability 的起点应该是:
先让一次请求变成可搜索、可关联、可复查的对象。
先有相关性,后面的大盘、趋势和告警才有意义。
三、最小闭环:request_id、trace_id、日志与状态接口
当前这套最小闭环由五类信号组成:
request_id、trace_id、structured logs、provider events、状态接口与最小指标面
3.1 request_id:把一次 HTTP 请求钉住
request_id 在最外层 middleware 生成,优先复用传入的 X-Request-Id,否则生成新值;随后进入 request context,并在响应头里写回 X-Request-Id。它的职责是:
把一次 HTTP 请求变成一个对客户端和服务端都可见的稳定身份。
响应头原始输出:
这一步证明
request_id 不是只存在于进程内部,而是调用方也能直接拿到。3.2 trace_id:把一次调用的内部路径组织起来
在
request_id 之上,router middleware 会建立 tracing 上下文,并通过 X-Trace-Id 对外暴露。当前实现里,默认情况下 trace_id 与 request_id 对齐。它的职责不是替代
request_id,而是:把这次请求内部的执行路径组织成一棵可追的 span 链。
一次
POST /v1/chat/completions,当前至少会经过这些层次:- middleware、
chat.handler.* 、chat.service.* 、provider.router.*、provider.backend.*、最终 access log
所以对外是一个请求,对内已经是一条可追的执行链。
3.3 structured logs:把“路径”真正落出来
当前 tracing 更接近“日志化 span completion 记录”,而不是 exported tracing backend。
当前最关键的三类日志是:
- http request completed、trace span finished、provider event
其中前两类解决的是“这次请求走到了哪一层”。
同一次请求的整条执行链日志:
同一个 request_id / trace_id 下的
trace span finished + http request completed说明:
这组日志证明同一个
request_id / trace_id 已经能贯穿 provider backend、provider router、service、handler 和最终 access log。3.4 provider event:把“为什么这么路由”讲清楚
provider event 解决的是:provider router 为什么做了这个决定。
当前已经覆盖的关键事件包括:
provider_request_failed
provider_fallback_succeeded
provider_skipped_unhealthy
provider_probe_failed
provider_recovered
provider_stream_interrupted
这里要把边界说清楚:
provider event 本身不带
request_id / trace_id / span_id。更准确的说法应该是:
- 请求主链靠 span / access log 关联
- provider event 补充的是路由和健康状态证据
provider event 日志:
说明:
这组日志证明失败、fallback、cooldown 跳过、probe 恢复这几类状态变化都已经能被看见。
3.5 /debug/providers、/readyz 与 /metrics
排障不能只停留在日志层。当前还有三类外部信号:
- /debug/providers /readyz /metrics
它们分别回答三个问题:
/debug/providers:每个 backend 现在处在什么状态
/readyz:从 provider 可用性视角,网关现在还能不能接流量
/metrics:这是单次故障,还是已经开始变成趋势
/debug/providers + /readyz 验证:
输出说明:
primary 已经 unhealthy,但 secondary 仍可用,所以
/readyz 继续返回 200 ready。/metrics 最小指标面:说明:
这组指标回答了“发生过什么”和“当前状态是什么”,但还不是完整 observability platform。
四、当前最实用的排障路径
到这个阶段,最实用的排障路径并不复杂:
- 先拿
X-Request-Id/X-Trace-Id
- 用 request / trace 去查 structured logs
- 如果问题涉及路由、健康、fallback,再查 provider events
- 再看
/debug/providers
- 最后看
/readyz与/metrics
这条路径里,各个环节回答的问题不同:
request_id / trace_id:这次请求是谁
- structured logs:这次请求走到了哪一层
- provider events:router 为什么做了这个决定
/debug/providers:backend 当前是什么状态
/readyz:网关现在是否 ready
/metrics:这是孤例还是趋势
它的价值在于:
一次失败已经可以被串成一条完整叙述,而不再完全靠猜。
*五、这套设计证明了什么
到这里,当前网关已经证明了四件事:
- 每次请求已经有稳定身份
request_id 和 trace_id 都会对外暴露- 从入口到 provider 调用,主路径已经可以被串起来
middleware、handler、service、provider router、provider backend spans 以及 access log 已经能形成一条执行链
- 路由与健康状态变化已经不是黑箱
provider event、
/debug/providers 和 /readyz 已经让 provider 层状态转移变得可解释- 当前已经能支持最小证据链排障
即使没有 tracing backend、成熟 dashboard 和完整告警体系,也能把一次故障讲成一段有证据的故事
所以这一阶段真正成立的是:
故障故事已经可以被关联、回放、解释。
结论
当前成立的,是轻量 observability closure,不是完整 observability platform。
没有外部 tracing backend,没有成熟指标栈,
/metrics 仍然是进程内、小而有限的。如果说前几篇文章证明的是功能、治理、故障边界和性能基线,那么这一篇证明的是:
一次模型调用,已经可以被组织成一条可关联、可排障、可解释的事件链。
分享
