微服务核心05:链路追踪

理解 traceId、span 和上下文传播如何把跨服务调用串起来,并用它定位慢接口、错误扩散和异步链路问题。

字数 1978 阅读时长 ≈ 6 分钟 2026-6-3 2026-7-12
微服务核心05:链路追踪

链路追踪解决什么问题

链路追踪解决的是微服务排查里最痛的一件事:一次用户请求经过多个服务后,问题到底发生在哪一跳。

单体应用里,一个异常堆栈通常就能把路径暴露出来。微服务里,请求可能从网关到订单服务,再到库存、支付、优惠券、消息队列和第三方接口。没有链路追踪时,排查经常变成拿着时间点去不同服务里搜日志,像拼碎片。

链路追踪的价值,是把碎片变成一条有证据的调用链。

更深入一点,链路追踪不是单独的日志系统,而是一套上下文传播机制。它要在请求进入系统时生成或接收 trace 上下文,并在 HTTP、RPC、消息队列、线程池、定时任务之间继续传递。只要某一段传播断了,后面的日志和 span 就会变成”孤儿”,排查时还是要靠猜。

场景:一个慢接口怎么被看清楚

用户反馈”提交订单很慢”。订单服务日志显示接口耗时 3.8s,但看业务代码没有明显慢点。继续翻库存服务、支付服务、优惠券服务日志,每个服务时间格式不同,机器也不一样,很难确定顺序。

如果链路追踪完整,一次请求会有一个 traceId,每一段调用会有一个 span。慢请求里最有价值的不是”总耗时很长”,而是能看出哪一段在关键路径上拖慢了整体,并且能确认异步和 MQ 后半段有没有把上下文继续传下去:

Trace 上下文传播

这时就很清楚:订单慢不是库存,也不是优惠券,而是支付服务调用第三方通道慢。排查从”猜哪个服务有问题”变成”验证支付链路为什么慢”。

核心概念:traceId、span 和上下文

可以用三句话理解链路追踪的核心模型:

概念含义类比
traceId一次完整请求的唯一标识订单号,贯穿整个流程
span请求中的一个步骤或一段操作订单里的每一个商品项
上下文传播把 trace 信息从一个服务传到下一个快递单在各个转运站之间传递

更完整的模型通常包括:

  • Trace:一次端到端请求
  • Span:一次具体操作,例如网关转发、SQL 查询、RPC 调用
  • Parent / Child:span 之间的父子关系,用来还原调用树
  • Context:跨线程、跨进程传播的上下文
  • Baggage:少量跨服务附带字段,要谨慎使用,避免把大对象塞进链路

例如 W3C Trace Context 的思路里,请求头会携带类似 traceparent 的上下文。你不一定要手写这些头,但要知道追踪系统靠它们把不同服务的 span 串起来。

最容易断链的地方

Java 项目里尤其容易在这些地方断链:

场景断链原因解决方式
线程池异步新线程没有继承父线程上下文包装线程池或使用 TransmittableThreadLocal
MQ 生产消费消息属性没有携带 trace 信息生产端写入 trace 属性,消费端恢复上下文
HTTP 客户端Feign / RestTemplate 没有统一拦截器添加拦截器自动注入 trace Header
网关入口网关生成了 traceId 但业务日志没打印日志 MDC 统一输出 traceId
采样率过低关键错误请求没有被采到错误请求、慢请求提高采样优先级

采样策略也很关键。全量采样成本高,低采样率又可能漏掉关键错误。更实用的做法是普通请求按比例采样,慢请求、错误请求、核心链路请求提高采样优先级。否则事故发生时,你可能只看到”系统很慢”,却找不到那条最有价值的慢 trace。

常见误区:链路追踪不能代替日志和指标

一个常见误区是:有了链路追踪,就不用认真打日志了。

链路追踪告诉你请求经过哪里、每段耗时多少、错误出现在哪个 span。日志告诉你当时的业务上下文,比如订单号、用户、状态流转、第三方错误码。指标告诉你这个问题是单次请求异常,还是整体趋势变坏。

三者应该配合:

  • Tracing:告诉你这次请求卡在哪一跳
  • Logging:解释这一跳具体发生了什么
  • Metrics:判断这种情况是不是正在变多

如果只有 trace,没有结构化日志,你知道支付服务慢,但不知道是哪个通道、哪个错误码、哪个商户。只有日志,没有 trace,你又很难把多个服务里的日志串成一条请求。

项目实践:别让异步和消息链路断掉

落地链路追踪时,优先保证三件事。

第一,入口统一生成或透传 traceId。 外部请求进入网关时,如果调用方没有传,就生成一个;如果已有可信 traceId,就继续透传。

第二,所有服务日志都打印 traceId。 只上报链路平台还不够,排查时经常还要搜日志。日志里没有 traceId,链路和日志就接不上。

日志侧要配合 MDC 或类似机制,把 traceId 放进每一行结构化日志里。异步线程池要包装任务提交,把父线程上下文复制到子线程;消息队列要在生产端写入 trace 属性,在消费端恢复上下文。

第三,异步和消息链路不能断。 很多线上问题不是同步 HTTP 链路,而是订单创建后发消息、消费端扣库存、异步通知第三方。这里如果没有 trace 传播,后半段链路就像凭空出现。

真正项目里不一定手写上下文传播逻辑,通常会用框架拦截器、过滤器或 agent 自动完成。但你要知道它在做什么:接收请求时获取或生成 traceId,放进日志上下文,调用下游时写入 Header,发布消息时写入消息属性。否则断链时不知道去哪补。

排查路径:先找一条失败 trace

遇到慢接口或错误率升高,通常按这个顺序看:

  1. 先找一条失败或慢请求的 trace
  2. 看总耗时最长的 span,而不是先看报错最多的服务
  3. 判断是单个下游慢,还是多个下游一起慢
  4. 看错误是在入口被拒绝、调用超时,还是下游返回业务失败
  5. 再用 traceId 去日志里查业务上下文
  6. 最后看指标,确认是个例还是整体趋势

链路追踪最有用的地方,不是画一张漂亮的拓扑图,而是在事故现场缩小范围。

看 trace 时还要注意两类假象:

  • 父 span 很长,不代表父服务本身慢,可能只是它等待子调用返回
  • 某个 span 报错,不代表根因就在这里,可能是上游传入了非法状态

正确做法是先找关键路径上最长的等待,再结合日志看业务错误码。

可观测性的三件套

最后收个尾。微服务核心模块到这里就串起来了:网关收口入口,配置中心管理变化,熔断限流负责止损,链路追踪负责定位,服务治理把这些能力放到同一张稳定性地图里。

而链路追踪本身,也只是可观测性的三分之一。完整的可观测性包括 tracing、logging、metrics 三件套,它们互相补充,缺一不可。