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,表示并行操作。
3.4 Span 生命周期
每个 Span 从创建到结束经历四个明确阶段,OTel SDK 通过 SpanProcessor 和 SpanExporter 管理这些阶段:
- 创建 (Start):调用
tracer.startSpan()或通过自动插桩创建。此时记录开始时间戳,生成 Span ID。 - 操作 (Active):Span 处于活跃状态,可添加属性、事件、状态更新。此阶段 Span 被设置为当前活跃 Span,用于 Context 传播。
- 结束 (End):调用
span.end(),记录结束时间戳。Span 变为不可变,不再接受修改。 - 导出 (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 传播流程
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 传播确保跨进程一致性。这些概念直接决定了采样策略的实现方式——尤其是头采样和尾采样的决策时机。