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 中。
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(传播器) 实现序列化和反序列化。
传播流程:
- 注入(Inject):服务 A 在发送请求前,将当前 Span 的 SpanContext 写入 HTTP 头或 RPC 元数据。
- 提取(Extract):服务 B 在接收请求时,从 HTTP 头或 RPC 元数据中读取 SpanContext。
- 创建子 Span:服务 B 使用提取到的 SpanContext 作为父上下文,创建新的子 Span,从而将两个 Span 关联到同一个 Trace。
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 | 记录操作详情 | 传递追踪上下文 |