3. 链路追踪核心概念:Span、Trace、SpanContext、Baggage、Propagation

在 OpenTelemetry 链路追踪体系中,有五个核心概念构成了分布式追踪的骨架。理解它们之间的关系,是构建值班机器人可观测性的基础。下面我们逐一拆解,并用一张架构图串联全貌。

核心关系速记: Trace 是整条调用链,Span 是链上的每一环,SpanContext 是环上的身份标签,Baggage 是环间传递的上下文数据,Propagation 是传递这些标签和数据的协议。
链路追踪五大核心概念关系图 Trace(追踪) Span A 服务入口 / API duration: 120ms Span B 数据库查询 duration: 45ms Span C 外部 RPC 调用 duration: 80ms Span D 缓存 duration: 5ms SpanContext(跨度上下文) 每个 Span 都携带 SpanContext,包含 trace_id、span_id、trace_flags,用于跨进程关联。 Baggage( baggage ) 键值对集合,随 SpanContext 传播,用于传递业务上下文(如用户ID、请求来源)。 Propagation(传播) 通过 W3C TraceContext / Baggage 协议,将 SpanContext 和 Baggage 序列化并跨服务传递。

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 传播流程(W3C TraceContext) 服务 A 告警引擎 生成 Span A trace_id: T1, span_id: S1 HTTP 请求 Header 注入 traceparent 服务 B 规则查询 生成 Span B trace_id: T1, span_id: S2 传播协议详解 1. W3C TraceContext:通过 HTTP 头 traceparent 传递 trace_id、span_id、trace_flags。 格式:00-trace_id-span_id-01(01 表示已采样) 2. W3C Baggage:通过 HTTP 头 baggage 传递键值对,格式:key1=value1,key2=value2 3. 服务 B 从请求头中提取 SpanContext,创建子 Span,并设置 parent_span_id = S1。

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) 并记录异常事件,便于告警关联。