2. 核心数据模型:Trace(链路)、Span(跨度)、SpanContext(跨度上下文)的定义与关系

在 OpenTelemetry 的数据模型中,Trace、Span 和 SpanContext 是构成分布式追踪的三大基石。理解它们的定义与关系,是掌握 OTel 可观测性的第一步。

2.1 核心概念定义

概念 英文 定义 类比(一次 HTTP 请求)
链路 Trace 一个端到端的请求或事务在分布式系统中的完整执行路径。由多个 Span 组成,共享同一个 TraceId。 一次完整的“用户下单”请求,从浏览器到网关、订单服务、支付服务、数据库的完整旅程。
跨度 Span Trace 中的一个独立工作单元,代表一个操作或一个服务调用。包含名称、开始/结束时间、属性、事件和状态。 “订单服务调用支付服务”这个单一操作,记录其开始时间、耗时、是否成功。
跨度上下文 SpanContext Span 的标识信息集合,用于跨进程传递追踪状态。核心字段包括 TraceId、SpanId、TraceFlags 和 Tracestate。 请求头中的 traceparent 和 tracestate 字段,携带身份信息传递给下游服务。
关键记忆点: Trace 是“树”,Span 是“节点”,SpanContext 是“节点之间的通行证”。

2.2 三者的关系

三者构成一个树形层级结构,关系如下:

  • Trace 包含多个 Span:一个 Trace 由至少一个 Span 组成,通常包含多个 Span,这些 Span 通过父子关系形成一棵有向无环图(DAG)。
  • Span 携带 SpanContext:每个 Span 内部都包含一个 SpanContext 对象,用于唯一标识该 Span 在 Trace 中的位置。
  • SpanContext 用于传播:当服务 A 调用服务 B 时,服务 A 的 SpanContext 通过 RPC 或 HTTP 头传递给服务 B,服务 B 据此创建子 Span,从而将两个 Span 关联到同一个 Trace 中。
Trace、Span、SpanContext 关系图 Trace(链路) Span 1(根) /api/order TraceId: abc123 Span 2(子) order-service ParentSpanId: span1 Span 3(子) payment-service ParentSpanId: span2 Span 4(子) database-query ParentSpanId: span2 SpanContext TraceId SpanId TraceFlags Tracestate 每个 Span 都包含一个 SpanContext

2.3 Span 的详细结构

Span 是 Trace 中最核心的数据单元,其结构包含以下关键字段:

字段 说明 示例
TraceId 全局唯一标识,16 字节(32 位十六进制字符串),标识所属 Trace 0af7651916cd43dd8448eb211c80319c
SpanId Span 唯一标识,8 字节(16 位十六进制字符串) b7ad6b7169203331
ParentSpanId 父 Span 的 SpanId,根 Span 此字段为空 a2fb4a1d1a96d312
Name Span 的操作名称,描述正在执行的操作 HTTP GET /api/users
StartTime / EndTime Span 的开始和结束时间戳,用于计算耗时 2025-01-15T10:30:00.123Z
Attributes 键值对,描述 Span 的元数据(如 HTTP 状态码、服务名) http.status_code=200
Events 带时间戳的日志事件,记录 Span 生命周期中的关键事件 cache.miss
Status Span 的执行状态:Unset、Ok、Error Status.Error
SpanKind Span 的角色:Client、Server、Internal、Producer、Consumer SpanKind.Server

2.4 SpanContext 的传播机制

SpanContext 是跨进程传递追踪信息的核心载体。在 OTel 中,它通过 Propagators(传播器) 实现序列化和反序列化。

传播流程:
  1. 注入(Inject):服务 A 在发送请求前,将当前 Span 的 SpanContext 写入 HTTP 头或 RPC 元数据。
  2. 提取(Extract):服务 B 在接收请求时,从 HTTP 头或 RPC 元数据中读取 SpanContext。
  3. 创建子 Span:服务 B 使用提取到的 SpanContext 作为父上下文,创建新的子 Span,从而将两个 Span 关联到同一个 Trace。
SpanContext 跨进程传播流程 服务 A(前端) Span: HTTP GET /order SpanContext: TraceId=abc 注入 traceparent HTTP 头 traceparent tracestate 提取 服务 B(订单) Span: POST /payment ParentSpanId: span_A SpanContext 通过 W3C TraceContext 标准(traceparent/tracestate)在服务间传递 下游服务根据提取到的 TraceId 和 ParentSpanId 建立父子关系

2.5 代码示例:创建 Span 并获取 SpanContext

以下示例展示如何在 OTel SDK 中创建 Span 并获取其 SpanContext:

// 获取 Tracer
Tracer tracer = openTelemetry.getTracer("order-service");

// 创建并启动 Span
Span span = tracer.spanBuilder("HTTP GET /api/order")
    .setSpanKind(SpanKind.SERVER)
    .startSpan();

// 设置属性
span.setAttribute("http.method", "GET");
span.setAttribute("http.status_code", 200);

// 获取 SpanContext
SpanContext spanContext = span.getSpanContext();
String traceId = spanContext.getTraceId();   // 0af7651916cd43dd8448eb211c80319c
String spanId = spanContext.getSpanId();     // b7ad6b7169203331

// 注入到 HTTP 头(传播)
TextMapPropagator propagator = openTelemetry.getPropagators().getTextMapPropagator();
propagator.inject(spanContext, httpRequest, (carrier, key, value) -> {
    carrier.setHeader(key, value);
});

// 结束 Span
span.end();
常见误区:
  • 不要混淆 TraceId 和 SpanId:TraceId 标识整条链路,SpanId 标识单个操作。
  • SpanContext 不是 Span 本身,它只是 Span 的“身份证”,用于跨进程传递。
  • 根 Span(Root Span)的 ParentSpanId 为空,它是 Trace 的起点。

2.6 总结:三者的关系矩阵

维度 Trace Span SpanContext
粒度 粗(端到端请求) 细(单个操作) 极细(标识信息)
生命周期 请求开始到结束 操作开始到结束 随 Span 创建而创建
传播范围 不直接传播 不跨进程传播 跨进程传播
核心作用 串联所有 Span 记录操作详情 传递追踪上下文