Lazy loaded image
006-从 Request ID 到 Trace ID:把一次模型调用变成可关联的事件链
字数 1990阅读时长 5 分钟
2026-4-10
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_idrequest_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 验证:
notion image
输出说明:
primary 已经 unhealthy,但 secondary 仍可用,所以 /readyz 继续返回 200 ready
/metrics 最小指标面:
说明:
这组指标回答了“发生过什么”和“当前状态是什么”,但还不是完整 observability platform。

四、当前最实用的排障路径

到这个阶段,最实用的排障路径并不复杂:
  1. 先拿 X-Request-Id / X-Trace-Id
  1. 用 request / trace 去查 structured logs
  1. 如果问题涉及路由、健康、fallback,再查 provider events
  1. 再看 /debug/providers
  1. 最后看 /readyz/metrics
这条路径里,各个环节回答的问题不同:
  • request_id / trace_id:这次请求是谁
  • structured logs:这次请求走到了哪一层
  • provider events:router 为什么做了这个决定
  • /debug/providers:backend 当前是什么状态
  • /readyz:网关现在是否 ready
  • /metrics:这是孤例还是趋势
它的价值在于:
一次失败已经可以被串成一条完整叙述,而不再完全靠猜。

*五、这套设计证明了什么

到这里,当前网关已经证明了四件事:
  1. 每次请求已经有稳定身份
    1. request_idtrace_id 都会对外暴露
  1. 从入口到 provider 调用,主路径已经可以被串起来
    1. middleware、handler、service、provider router、provider backend spans 以及 access log 已经能形成一条执行链
  1. 路由与健康状态变化已经不是黑箱
    1. provider event、/debug/providers/readyz 已经让 provider 层状态转移变得可解释
  1. 当前已经能支持最小证据链排障
    1. 即使没有 tracing backend、成熟 dashboard 和完整告警体系,也能把一次故障讲成一段有证据的故事
所以这一阶段真正成立的是:
故障故事已经可以被关联、回放、解释。

结论

当前成立的,是轻量 observability closure,不是完整 observability platform。
没有外部 tracing backend,没有成熟指标栈,/metrics 仍然是进程内、小而有限的。
如果说前几篇文章证明的是功能、治理、故障边界和性能基线,那么这一篇证明的是:
一次模型调用,已经可以被组织成一条可关联、可排障、可解释的事件链。
 
回到首页