2. OpenTelemetry 协议:Trace、Span、SpanContext 概念,TraceId、SpanId 的生成与传播机制
2.1 核心概念:从请求到追踪的抽象
在分布式系统中,一个用户请求往往跨越多个微服务、中间件和数据库。OpenTelemetry 通过三个核心抽象层将这一复杂路径标准化为可观测的数据结构:Trace(追踪)、Span(跨度)和 SpanContext(跨度上下文)。
| 概念 | 定义 | 类比(快递物流) |
|---|---|---|
| Trace | 一次完整请求的生命周期,由一组具有相同 TraceId 的 Span 组成的有向无环图(DAG)。 | 一个包裹从发货到签收的完整旅程。 |
| Span | Trace 中的一个命名、计时的工作单元。每个 Span 记录开始时间、结束时间、状态、属性(Attributes)和事件(Events)。 | 旅程中的单个环节:揽收、运输、派送。 |
| SpanContext | 跨进程传递的不可变标识集合,包含 TraceId、SpanId、TraceFlags 和 Tracestate。它是分布式传播的“护照”。 | 包裹上的运单号(TraceId)和当前环节编号(SpanId)。 |
平台工程视角: 在自助查询接口设计中,Trace 是查询的“根对象”,Span 是“明细行”,而 SpanContext 是“查询密钥”。用户输入 TraceId 即可检索整棵树,输入 SpanId 可定位到具体服务实例。
2.2 TraceId 与 SpanId:生成规则与唯一性保障
TraceId 和 SpanId 是分布式追踪的“基因序列”,其生成策略直接影响查询性能和跨系统兼容性。
- TraceId:全局唯一,标识整个 Trace。标准长度为 16 字节(128 位),通常编码为 32 位十六进制字符串。生成方式包括:
- 随机生成:使用加密安全的随机数生成器(CSPRNG),保证全局唯一性。
- 组合生成:高 64 位为时间戳(毫秒级),低 64 位为随机数或计数器,便于按时间范围索引。
- SpanId:在 Trace 内唯一,标识单个 Span。标准长度为 8 字节(64 位),编码为 16 位十六进制字符串。生成方式:
- 由创建 Span 的 SDK 在内存中随机生成,无需全局协调。
- 父 Span 在创建子 Span 时,将自身 SpanId 设为子 Span 的 ParentSpanId。
避坑指南: 不要使用 UUID 作为 TraceId。UUID 虽然唯一,但 128 位长度与 OpenTelemetry 标准一致,然而 UUID 的版本位和变体位会破坏二进制排序,导致数据库索引失效。推荐使用纯随机 128 位或时间戳+随机组合。
2.3 传播机制:SpanContext 的跨进程接力
SpanContext 的传播是分布式追踪的“神经系统”。OpenTelemetry 定义了两种传播方式:W3C TraceContext(推荐)和 B3 Propagation(Zipkin 兼容)。
| 传播协议 | HTTP Header | 格式示例 | 适用场景 |
|---|---|---|---|
| W3C TraceContext | traceparent |
00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01 |
新系统、云原生、Kubernetes 环境 |
| B3 Propagation | X-B3-TraceId, X-B3-SpanId, X-B3-Sampled |
X-B3-TraceId: 0af7651916cd43dd8448eb211c80319c |
遗留系统、Zipkin 生态 |
传播流程分为三个步骤:注入(Inject)、提取(Extract)、继续(Continue)。
- 注入:服务 A 在发起 HTTP 请求前,将当前 SpanContext 序列化到请求头中。
- 提取:服务 B 收到请求后,从请求头中反序列化出 SpanContext。
- 继续:服务 B 以提取到的 SpanContext 为父上下文,创建新的子 Span。
2.4 自助查询接口中的关键设计
在平台工程的自助查询场景中,理解上述机制有助于设计高效的查询 API:
- TraceId 索引:将 TraceId 作为数据库主键或分片键,支持 O(1) 查询整条 Trace。
- SpanId 关联:通过 ParentSpanId 字段构建 Span 树,支持“展开/折叠”的 UI 交互。
- 传播透传:在异步消息(Kafka、RabbitMQ)和 gRPC 中同样需要注入/提取 SpanContext,否则追踪链会断裂。
常见断裂场景: 当服务使用线程池或协程时,SpanContext 不会自动继承。必须手动将 SpanContext 从父线程复制到子线程,否则会出现“孤儿 Span”——有 SpanId 但无 TraceId 关联。
2.5 协议对比:OpenTelemetry vs OpenTracing vs OpenCensus
| 维度 | OpenTelemetry | OpenTracing | OpenCensus |
|---|---|---|---|
| SpanContext 结构 | TraceId + SpanId + TraceFlags + Tracestate | TraceId + SpanId + Baggage | TraceId + SpanId + TraceOptions |
| 传播协议 | W3C TraceContext(默认) | B3(默认) | 自定义 gRPC 元数据 |
| ID 长度 | TraceId: 128bit, SpanId: 64bit | TraceId: 128bit, SpanId: 64bit | TraceId: 128bit, SpanId: 64bit |
| 采样决策 | TraceFlags 中的采样位 | 独立采样头 | TraceOptions 中的采样位 |
OpenTelemetry 作为 CNCF 的毕业项目,已统一 OpenTracing 和 OpenCensus 的生态。在平台工程中,建议直接采用 OpenTelemetry SDK,避免多协议适配的维护成本。