视频加载失败

微服务之间如何传递 trace

526 字
3 分钟
微服务之间如何传递 trace

跨微服务时,靠的是“上下文传播”,不是接口 body 显式保留 trace id。

先区分 3 个东西:

  • tracer:代码里 otel.Tracer(...) 的对象,表示哪段代码在打点
  • trace id:一整条链路的 ID
  • span id:链路里某个节点的 ID

微服务之间传递的是 trace id/span 上下文,不是 tracer。

HTTP 场景#

标准做法是放在请求头里,通常是 W3C Trace Context:

  • traceparent
  • tracestate
  • 可选 baggage

流程是:

  1. 服务 A 收到请求,创建或继续一个 trace
  2. 服务 A 调服务 B 前,把当前 trace context 注入到 HTTP header
  3. 服务 B 收到请求后,从 header 提取 trace context
  4. 服务 B 基于这个 context 创建自己的 span

这样 A 和 B 的 span 就会落到同一个 trace 里。

所以一般不需要你在接口 JSON 里专门加:

{
"traceId": "..."
}

除非你有业务上的特殊需求。对 tracing 本身来说,不该这么做,原因是:

  • 污染业务接口契约
  • 容易和真实 OTel 上下文不一致
  • 标准库/中间件已经能自动处理 header

HTTP 里通常像这样:

  • 服务端:Extract(从入站 header 恢复上下文)
  • 客户端:Inject(把上下文写入出站 header)

Go + OpenTelemetry 的服务端提取示例:

// 服务端:从入站请求 header 中提取上下文,再基于它创建 span
ctx := otel.GetTextMapPropagator().Extract(
r.Context(),
propagation.HeaderCarrier(r.Header),
)
ctx, span := tracer.Start(ctx, "handle-request")
defer span.End()

客户端注入示例:

// 客户端:把当前上下文注入到出站请求 header
req := httptest.NewRequest("GET", "http://service-b/api", nil)
otel.GetTextMapPropagator().Inject(
ctx,
propagation.HeaderCarrier(req.Header),
)

gRPC 与消息队列#

gRPC 也是一样,只是不是 HTTP header,而是 gRPC metadata。原理完全相同:

  • client interceptor 注入
  • server interceptor 提取

消息队列也类似,通常放在消息 header / metadata 里,不放消息 body。

实务建议#

  • HTTP:用 traceparent 这些标准 header
  • gRPC:用 metadata
  • MQ/Kafka:用 message headers
  • 不要把 trace id 做成业务接口字段,除非只是为了调试展示
  • 可以额外返回一个 X-Trace-ID 给前端或调用方排查问题,但这只是便于观察,不是主链路传播的标准方式

相关概念见 OpenTelemetry 与链路追踪笔记。

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

微服务之间如何传递 trace
https://blog.81vm3.xyz/posts/trace-propagation/
作者
Blume
发布于
2026-04-22
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
Blume
I build interesting things.
公告
欢迎来到我的博客!
分类
标签
最新动态
站点统计
文章
38
分类
5
标签
98
总字数
23,078
运行时长
0 天
最后活动
0 天前
站点信息
构建平台
Local
博客版本
Firefly v6.16.8
文章许可
CC BY-NC-SA 4.0
文章目录