3. Trace 与 Span 核心概念

分布式追踪的核心数据模型由 Trace 和 Span 构成。理解这两个概念及其关联的 ID 体系、父子关系、生命周期和上下文传播,是掌握 OTel 采样策略的基础。本章将逐一拆解这些核心要素。

3.1 Trace:一次请求的完整路径

一个 Trace 代表一个端到端的请求或事务在分布式系统中的完整执行路径。它由多个 Span 组成,这些 Span 通过唯一的 Trace ID 关联在一起。

  • Trace ID:全局唯一的 16 字节(128 位)标识符,通常以 32 位十六进制字符串表示。所有属于同一请求的 Span 共享同一个 Trace ID。
  • 作用:将分散在不同服务、不同进程中的 Span 串联起来,形成完整的调用链路。
Trace ID 生成规则:OTel SDK 默认使用随机数生成器产生 Trace ID,确保全局唯一性。生产环境中应避免使用可预测的 ID 生成算法,防止 ID 冲突。

3.2 Span:追踪的最小工作单元

Span 是追踪中的最小工作单元,代表系统中一个具有开始时间和结束时间的操作。每个 Span 封装了以下关键信息:

属性 说明 示例
Span ID 8 字节(64 位)标识符,十六进制字符串,在 Trace 内唯一 a1b2c3d4e5f67890
Trace ID 所属 Trace 的全局 ID abc123...xyz
Parent Span ID 父 Span 的 ID,根 Span 此字段为空 parentSpanId 或 null
Span Name 描述操作名称的字符串 HTTP GET /api/users
Start / End Timestamp 操作的开始和结束时间(纳秒精度) 1698000000000000000
Attributes 键值对,描述 Span 的元数据 http.method=GET
Events 带时间戳的日志事件 Cache miss
Status 操作结果状态(Unset / Ok / Error) StatusCode.ERROR

3.3 Parent-Child 关系:构建调用树

Span 之间通过 Parent-Child 关系形成树状结构,反映请求的调用层次。根 Span 没有父 Span,是 Trace 的起点。

  • 根 Span:Trace 中的第一个 Span,Parent Span ID 为空。
  • 子 Span:由父 Span 发起的下游调用,通过 Parent Span ID 指向父 Span。
  • 兄弟 Span:拥有相同父 Span 的多个子 Span,表示并行操作。
Trace 与 Span 树状结构 Trace 与 Span 树状结构 根 Span (Frontend) Trace ID: T-001 | Span ID: S-0 子 Span 1 (Auth) Span ID: S-1 | Parent: S-0 子 Span 2 (API) Span ID: S-2 | Parent: S-0 孙 Span (DB Query) Span ID: S-3 | Parent: S-2 说明:根 Span 无父 Span;子 Span 通过 Parent Span ID 指向父 Span;孙 Span 进一步扩展调用深度。

3.4 Span 生命周期

每个 Span 从创建到结束经历四个明确阶段,OTel SDK 通过 SpanProcessor 和 SpanExporter 管理这些阶段:

  1. 创建 (Start):调用 tracer.startSpan() 或通过自动插桩创建。此时记录开始时间戳,生成 Span ID。
  2. 操作 (Active):Span 处于活跃状态,可添加属性、事件、状态更新。此阶段 Span 被设置为当前活跃 Span,用于 Context 传播。
  3. 结束 (End):调用 span.end(),记录结束时间戳。Span 变为不可变,不再接受修改。
  4. 导出 (Export):SpanProcessor 将结束的 Span 传递给 Exporter,发送到后端(如 Jaeger、Zipkin)。
常见陷阱:忘记调用 span.end() 会导致 Span 永远处于活跃状态,造成内存泄漏和追踪数据不完整。务必使用 try-with-resources 或 defer 模式确保 Span 正确结束。

3.5 Context 传播:跨进程传递追踪信息

Context 传播 是分布式追踪的核心机制,确保 Trace ID 和 Span ID 能在不同服务、不同进程间传递,从而将分散的 Span 关联到同一个 Trace 中。

3.5.1 传播机制

  • W3C Trace Context:OTel 默认使用的标准 HTTP 头格式,包含 traceparent 和 tracestate 两个头部。
  • traceparent:携带 Trace ID、Span ID 和 Trace Flags(如采样标记)。格式:00-{trace-id}-{span-id}-{flags}。
  • tracestate:携带厂商特定的追踪元数据,用于扩展 traceparent。

3.5.2 传播流程

Context 传播流程 Context 传播流程 服务 A (Frontend) Span ID: S-A 服务 B (Auth) Span ID: S-B 服务 C (API) Span ID: S-C HTTP 请求 gRPC 调用 传播内容:traceparent = 00-{Trace ID}-{当前 Span ID}-01 服务 A 创建 Span S-A,将 traceparent 注入 HTTP 头;服务 B 提取并创建子 Span S-B,更新 Span ID;服务 C 同理。 关键点:所有服务共享同一个 Trace ID,但 Span ID 逐跳更新,形成父子链。

3.5.3 代码示例:手动 Context 传播

// 服务 A:创建 Span 并注入 Context
Span spanA = tracer.spanBuilder("handleRequest").startSpan();
try (Scope scope = spanA.makeCurrent()) {
    HttpURLConnection connection = (HttpURLConnection) url.openConnection();
    // 将当前 Context 注入 HTTP 头
    OpenTelemetry.getGlobalPropagators()
        .getTextMapPropagator()
        .inject(Context.current(), connection, (carrier, key, value) ->
            carrier.setRequestProperty(key, value));
    connection.connect();
} finally {
    spanA.end();
}

// 服务 B:提取 Context 并创建子 Span
HttpServletRequest request = ...;
Context extractedContext = OpenTelemetry.getGlobalPropagators()
    .getTextMapPropagator()
    .extract(Context.current(), request, (carrier, key) ->
        carrier.getHeader(key));
Span spanB = tracer.spanBuilder("processAuth")
    .setParent(extractedContext)
    .startSpan();
// ... 业务逻辑 ...
spanB.end();

3.6 核心概念总结

概念 关键点 采样影响
Trace ID 全局唯一,关联所有 Span 采样决策基于 Trace ID 的哈希值
Span ID Trace 内唯一,标识单个操作 用于构建父子关系,影响采样一致性
Parent-Child 形成调用树,根 Span 无父 头采样需确保根 Span 被采样
Span 生命周期 Start → Active → End → Export 采样发生在 End 阶段之前
Context 传播 跨进程传递 Trace ID 和 Span ID 采样标记通过 traceparent 传播
课程要点:Trace 和 Span 是分布式追踪的基石。Trace ID 保证全局关联,Span ID 标识局部操作,Parent-Child 关系构建调用树,Context 传播确保跨进程一致性。这些概念直接决定了采样策略的实现方式——尤其是头采样和尾采样的决策时机。