第二章 核心概念:Trace、Span、SpanContext
做链路追踪,你首先得搞懂三个最基础的概念:Trace、Span 和 SpanContext。这三个词我几乎每天都要跟它们打交道。说白了,它们就是链路追踪世界的“主语、谓语、宾语”——Trace 是一次请求的完整旅程,Span 是旅程中的每一个环节,而 SpanContext 就是各个环节之间传递的“接力棒”。
我在刚接触分布式系统时,就被这些概念绕晕过。后来亲手搭过一次全链路追踪,才真正理解了它们是怎么配合的。咱们一个一个说。
1. Trace —— 一次请求的完整轨迹
Trace 代表一个从用户发起请求到最终返回结果的完整过程。无论这个请求穿过多少服务、调用多少数据库,只要属于同一笔业务,就共享同一个 TraceID。
我记得有一次排查一个订单超时问题,就是通过 TraceID 把 6 个微服务的日志串起来的。如果没有 Trace,你只能在一个一个服务的日志里翻,特别痛苦。
| Trace 关键属性 | 说明 |
|---|---|
| TraceID | 全局唯一标识,通常 16 字节或 128 位随机数生成 |
| 起始时间 | Trace 的开始时间戳,对应第一个 Span 的开始 |
| 结束时间 | Trace 的结束时间戳,对应最后一个 Span 的结束 |
| Span 列表 | 该 Trace 下所有 Span 的集合,按父子关系组织 |
2. Span —— 一个服务或组件的工作单元
Span 是 Trace 的基本组成单位。一次 RPC 调用、一个数据库查询、一个消息队列生产动作,都可以用一个 Span 来表示。每个 Span 有明确的开始和结束时间,还带操作名称、标签、日志等辅助信息。
你想想看,一个 Trace 就像一棵树,根 Span 是入口服务,子 Span 是它调用的下游服务,再往下是更细粒度的操作。我见过一张 Span 树展开后多达几十层,没有父子关系根本理不清。
每个 Span 包含以下核心字段:
- SpanID —— 当前 Span 的唯一标识
- ParentSpanID —— 父 Span 的 ID,根 Span 没有父 ID
- OperationName —— 操作名称,比如 “HTTP GET /api/order”
- StartTime / EndTime —— 耗时区间
- Tags —— key-value 形式的描述信息,例如 “http.status_code=200”
- Logs —— 事件记录,比如异常堆栈
3. SpanContext —— 跨进程传递的上下文
SpanContext 是保证链路不中断的关键。当服务 A 调用服务 B 时,必须把 TraceID、当前 SpanID 等信息通过 RPC 请求头传给下游,下游才能把自己产生的 Span 挂到同一个 Trace 树下。
SpanContext 一般包含:
- TraceID —— 当前请求的全局 ID
- SpanID —— 当前 Span 的 ID(下游用它作为 ParentSpanID)
- Baggage —— 自定义上下文数据,会随着调用链逐级传递
- 其他元数据 —— 比如采样标记、跟踪版本等
目前业界主流使用 W3C Trace Context 标准,通过 traceparent 和 tracestate 两个 HTTP Header 传递。我的习惯是在所有微服务网关层统一注入这个 Header,避免每个服务自己拼写导致格式错误。
核心原则: SpanContext 是“只读”的,下游可以从中读取 TraceID 和父 SpanID,但不能修改。如果一定要传递额外的业务数据,请用 Baggage,但要注意 Baggage 会随着请求一直传播,滥用会导致 Header 膨胀。
4. 三者关系与流转
把这三个概念放在一起,就形成了一个完整的链路追踪模型。下面这张图展示了一个典型的请求流程:
从上图可以看到:一个 Trace 包含多个 Span,Span 之间形成父子树形结构。根 Span 没有父 ID,其他 Span 通过 ParentSpanID 找到自己的父亲。SpanContext 就夹在每两个服务请求的 Header 里,保证下游 Span 能正确地认祖归宗。
5. 实战中容易踩的坑
理论讲完了,我来盘点一下现实中我见过最多的问题:
- TraceID 不统一 —— 有的服务用 UUID,有的用雪花算法,两个链路追踪系统之间根本无法关联。我建议要么全部用 W3C 标准格式,要么统一一个分布式 ID 生成服务。
- SpanContext 丢失 —— 异步线程、消息队列、定时任务是最容易漏传 SpanContext 的地方。我之前遇到过一条链路在消息消费端突然断掉,查了半天发现是开发没把 Header 传递到新线程里。
- Baggage 滥用 —— 有人把所有业务参数都塞进 Baggage,结果每个请求的 HTTP Header 膨胀到几十 KB,影响性能。记住 Baggage 只放“穿透上下游的关键标识”,比如用户 ID、会话 ID,别的别往里塞。
- Span 结束时间不准确 —— 有些框架在 Span 结束时只记录了当前时间,但真正的操作可能还在后台线程跑。一定要确保 Span 的范围和实际业务逻辑严格对齐。
好了,现在你对 Trace、Span、SpanContext 应该有了比较清晰的认识。这三个概念是所有链路追踪工具的基石,后面的章节我们所有的排查技巧都建立在这上面。嗯,理解了它们,你就算入门了。