3. Span 与 Trace 基础:定义、生命周期与属性
在分布式系统中,一次用户请求往往跨越多个微服务、数据库和消息队列。为了完整追踪这次请求的路径,OpenTelemetry 引入了两个核心抽象:Span(跨度)和 Trace(追踪)。本章将深入剖析它们的定义、生命周期以及如何通过属性和事件丰富观测数据。
3.1 Span 定义
Span 是分布式追踪中的最小工作单元,代表系统中一个具有开始时间和结束时间的操作。每个 Span 封装了以下关键信息:
| 字段 | 说明 | 示例值 |
|---|---|---|
| Span ID | 全局唯一标识符,用于区分单个 Span | a1b2c3d4e5f6g7h8 |
| Trace ID | 所属 Trace 的唯一标识,所有相关 Span 共享此 ID | abc123def456ghi789 |
| Parent Span ID | 父 Span 的 ID,用于构建调用链层级 | parent-span-id |
| Span Name | 描述操作的人类可读名称 | HTTP GET /api/users |
| Start Timestamp | 操作开始的精确时间(Unix 纳秒) | 1710000000000000000 |
| End Timestamp | 操作结束的精确时间 | 1710000000500000000 |
| Span Kind | 标识 Span 在 Trace 中的角色 | Server, Client, Internal |
| Status | 操作结果状态(Unset / Ok / Error) | Ok |
| Attributes | 键值对形式的元数据 | http.method=GET |
| Events | 带时间戳的结构化日志 | cache.miss |
Span Kind 决定了它在追踪拓扑中的语义。例如,Server 表示服务端接收请求,Client 表示客户端发起调用,Internal 表示内部方法调用。正确设置 Kind 有助于后端系统自动构建服务依赖图。
3.2 Trace 定义
Trace 是一条贯穿整个分布式系统的请求路径记录,由一组具有相同 Trace ID 的 Span 组成。这些 Span 通过父子关系形成一棵有向无环图(DAG),完整还原请求从入口到出口的完整流程。
Trace 的核心特征:
- 全局唯一性:每个 Trace 由唯一的
Trace ID标识,通常由入口服务生成。 - 跨进程传播:通过上下文传播机制(如 W3C TraceContext 协议),Trace ID 在服务间传递。
- 层级结构:根 Span(Root Span)代表请求的起点,子 Span 代表下游调用。
- 时间边界:Trace 的持续时间等于根 Span 的持续时间。
3.3 Span 生命周期
每个 Span 从创建到结束经历四个明确阶段。理解生命周期是正确使用 OpenTelemetry API 的基础。
- 创建(Start):通过 Tracer 创建 Span,记录开始时间戳,设置初始属性。
- 激活(Activate):将 Span 设置为当前活跃 Span,使其成为后续子 Span 的父 Span。
- 操作(Operation):在 Span 活跃期间添加属性、事件或记录错误。
- 结束(End):记录结束时间戳,将 Span 标记为完成并导出。
以下代码展示了 Span 生命周期的典型实现:
// 1. 创建 Span
Span span = tracer.spanBuilder("processOrder")
.setSpanKind(SpanKind.SERVER)
.startSpan();
// 2. 激活 Span(使其成为当前上下文)
try (Scope scope = span.makeCurrent()) {
// 3. 操作:添加属性和事件
span.setAttribute("order.id", "12345");
span.addEvent("payment.processed",
Attributes.of("amount", 99.99));
// 执行业务逻辑...
Thread.sleep(100);
} catch (Exception e) {
span.recordException(e);
span.setStatus(StatusCode.ERROR, "Order processing failed");
} finally {
// 4. 结束 Span
span.end();
}
span.end() 会导致 Span 永远处于打开状态,造成内存泄漏和追踪数据不完整。始终使用 try-finally 或 try-with-resources 确保 Span 正确关闭。
3.4 Span 属性与事件
属性(Attributes) 是附加在 Span 上的键值对,用于描述操作的上下文信息。OpenTelemetry 定义了语义约定(Semantic Conventions),推荐使用标准属性名以提高互操作性。
| 属性类别 | 推荐属性 | 示例 |
|---|---|---|
| HTTP 请求 | http.method, http.url, http.status_code |
http.method=POST |
| 数据库操作 | db.system, db.statement, db.connection_string |
db.system=postgresql |
| 消息队列 | messaging.system, messaging.destination |
messaging.system=kafka |
| 业务指标 | 自定义属性(如 user.id, tier) |
user.id=user_001 |
事件(Events) 是带时间戳的结构化日志记录,用于标记 Span 生命周期内的关键时刻。与属性不同,事件可以多次添加,且每个事件独立携带时间戳和属性集。
// 添加事件示例
span.addEvent("cache.hit",
Attributes.of("cache.key", "user_profile_123"));
span.addEvent("retry.attempt",
Attributes.of("attempt.number", 2, "delay.ms", 500));
// 记录异常事件
span.recordException(new RuntimeException("Connection timeout"),
Attributes.of("peer.address", "10.0.0.1:8080"));
3.5 Span 与 Trace 关系图解
下图展示了一个典型 Trace 的结构,包含根 Span 和多个子 Span,以及它们之间的父子关系:
3.6 上下文传播与 Trace 完整性
Span 和 Trace 的关联依赖于上下文传播(Context Propagation)。OpenTelemetry 使用 Context 对象在进程内和进程间传递 Trace 信息。进程内通过 Scope 管理活跃 Span,进程间通过 W3C TraceContext 或 B3 协议在 HTTP 头或消息元数据中传递。
下图展示了上下文如何在两个服务间传播: