3. 链路追踪核心概念:Span、Trace、SpanContext、Baggage、Propagation
在 OpenTelemetry 链路追踪体系中,有五个核心概念构成了分布式追踪的骨架。理解它们之间的关系,是构建值班机器人可观测性的基础。下面我们逐一拆解,并用一张架构图串联全貌。
核心关系速记: Trace 是整条调用链,Span 是链上的每一环,SpanContext 是环上的身份标签,Baggage 是环间传递的上下文数据,Propagation 是传递这些标签和数据的协议。
3.1 Span(跨度)
Span 是链路追踪中最小的工作单元,代表一个操作或一个步骤。在值班机器人场景中,一个 Span 可以是一次告警查询、一条消息推送、一次数据库写入。
| Span 属性 | 说明 | 值班机器人示例 |
|---|---|---|
| span_id | 全局唯一标识,16 字节 | a1b2c3d4e5f6g7h8 |
| trace_id | 所属 Trace 的 ID | abc123... |
| parent_span_id | 父 Span ID,根 Span 为空 | 根 Span 无父 ID |
| name | 操作名称 | query_alert_rules |
| kind | Span 类型(Client/Server/Internal/Producer/Consumer) | SpanKind.CLIENT |
| start_time / end_time | 开始与结束时间戳 | 1710000000.123 |
| attributes | 键值对标签 | db.system="postgresql" |
| events | 带时间戳的事件 | exception 事件 |
| status | OK / ERROR / UNSET | Status.ERROR |
实践建议: 在值班机器人中,每个外部依赖调用(数据库、Redis、gRPC)都应创建一个独立的 Span,并设置
span.set_attribute("db.system", "redis") 等属性,便于后续定位慢调用。
3.2 Trace(追踪)
Trace 是由一个根 Span 及其所有子 Span 组成的树状结构,代表一次完整的请求处理流程。在值班机器人中,一次“告警触发 → 查询规则 → 推送通知”的完整过程就是一个 Trace。
- Trace ID: 全局唯一,贯穿整个请求链。
- 根 Span: 没有父 Span,是整个 Trace 的起点。
- Span 层级: 通过 parent_span_id 形成父子关系,构成调用树。
- 完整性: 一个 Trace 包含从入口到出口的所有 Span。
3.3 SpanContext(跨度上下文)
SpanContext 是 Span 的“身份证”,包含在服务间传递所需的核心标识信息。它不包含业务数据,只负责追踪链路的连接。
| 字段 | 类型 | 说明 |
|---|---|---|
| trace_id | 16 字节 | 全局唯一 Trace 标识 |
| span_id | 8 字节 | 当前 Span 标识 |
| trace_flags | 1 字节 | 采样标记(0x01 表示已采样) |
| tracestate | 键值对列表 | 厂商扩展信息(如采样率、租户 ID) |
3.4 Baggage(上下文数据)
Baggage 是随 SpanContext 传播的键值对集合,用于在服务间传递业务上下文。与 Span 的 attributes 不同,Baggage 会跨进程传播。
注意: Baggage 会随每个请求传播到所有下游服务,因此应避免放入大量数据或敏感信息(如密码、)。建议只放入必要的业务标识,如
user_id、tenant_id、request_source。
Baggage 使用示例(Python)
from opentelemetry import baggage
# 设置 Baggage
baggage.set_baggage("user_id", "u12345")
baggage.set_baggage("tenant_id", "t-abc")
# 获取 Baggage
user_id = baggage.get_baggage("user_id")
print(f"当前用户: {user_id}")
Baggage 使用示例(Go)
import (
"go.opentelemetry.io/otel/baggage"
)
// 创建 Baggage
member, _ := baggage.NewMember("user_id", "u12345")
bag, _ := baggage.New(member)
ctx := baggage.ContextWithBaggage(ctx, bag)
// 获取 Baggage
bag := baggage.FromContext(ctx)
userID := bag.Value("user_id")
fmt.Printf("当前用户: %s\n", userID)
3.5 Propagation(传播)
Propagation 定义了 SpanContext 和 Baggage 如何在服务间传递。OpenTelemetry 默认使用 W3C TraceContext 和 W3C Baggage 协议。
Propagation 核心接口
| 接口 | 作用 | 实现 |
|---|---|---|
| Inject | 将 SpanContext 写入传播载体(如 HTTP Header) | propagator.inject(span_context, carrier) |
| Extract | 从传播载体中读取 SpanContext | propagator.extract(carrier, context) |
| Fields | 返回传播使用的键名列表 | propagator.fields() → ["traceparent", "tracestate"] |
Python 传播示例
from opentelemetry import trace
from opentelemetry.propagate import inject, extract
# 创建 Span
tracer = trace.get_tracer("robot-tracer")
with tracer.start_as_current_span("alert_process") as span:
# 注入到 HTTP 头
headers = {}
inject(headers)
# headers 现在包含 traceparent 和 tracestate
print(headers)
# 输出: {'traceparent': '00-abc123...-def456...-01'}
# 在接收端提取
from opentelemetry.propagate import extract
ctx = extract(request.headers)
# 从上下文中获取当前 Span
span = trace.get_current_span(ctx)
Go 传播示例
import (
"go.opentelemetry.io/otel/propagation"
"net/http"
)
// 注入
propagator := propagation.TraceContext{}
req, _ := http.NewRequest("GET", "http://service-b/query", nil)
ctx := propagator.Inject(req.Context(), propagation.HeaderCarrier(req.Header))
// req.Header 现在包含 "Traceparent: 00-..."
// 提取
propagator := propagation.TraceContext{}
ctx := propagator.Extract(r.Context(), propagation.HeaderCarrier(r.Header))
span := trace.SpanFromContext(ctx)
值班机器人实战要点:
- 确保所有服务使用相同的 Propagator(推荐 W3C TraceContext + Baggage)。
- 在 gRPC 中使用
grpc-metadata传递,在 HTTP 中使用标准 Header。 - Baggage 只放业务标识,不放敏感数据,避免性能开销。
- 每个 Span 设置
span.set_status(Status.ERROR)并记录异常事件,便于告警关联。