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)。

  1. 注入:服务 A 在发起 HTTP 请求前,将当前 SpanContext 序列化到请求头中。
  2. 提取:服务 B 收到请求后,从请求头中反序列化出 SpanContext。
  3. 继续:服务 B 以提取到的 SpanContext 为父上下文,创建新的子 Span。
SpanContext 跨进程传播流程 服务 A(前端) Span: checkout 服务 B(订单) Span: createOrder HTTP 请求 ① 注入(Inject) 将 SpanContext 写入 traceparent 头 ② 提取(Extract) 从请求头解析出 TraceId + ParentSpanId ③ 继续(Continue) 创建子 Span,ParentSpanId = 服务 A 的 SpanId

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,避免多协议适配的维护成本。