4. 链路追踪核心概念:Span、Trace、Context Propagation、Baggage

分布式链路追踪是构建可观测性体系的基石。在微服务与多租户架构中,每一次用户请求都可能跨越数十个服务实例与中间件。要准确还原请求的完整路径、定位性能瓶颈并实现租户隔离,必须深入理解链路追踪的四个核心概念:Span、Trace、Context Propagation 与 Baggage。

4.1 Span:追踪的最小工作单元

Span(跨度)是链路追踪中最基本的逻辑单元,代表一个分布式系统中发生的单一操作或工作单元。每个 Span 记录了一个操作的开始时间、结束时间、状态以及一组关联的元数据。

Span 的核心属性

属性 说明 示例值
SpanId 全局唯一的跨度标识符 a1b2c3d4e5f6g7h8
TraceId 所属追踪链路的全局标识符 trace-001-xyz
ParentSpanId 父 Span 的标识符,用于构建父子关系 parent-span-id
SpanName 描述操作名称 HTTP GET /api/orders
StartTime / EndTime 操作的开始与结束时间戳 2025-01-15T10:00:00Z
SpanKind Span 的角色类型 CLIENT, SERVER, INTERNAL
Status 操作执行结果状态 OK, ERROR
Attributes 键值对形式的自定义元数据 tenant.id = "acme-corp"
Events Span 生命周期内发生的带时间戳事件 cache.miss, exception
租户隔离要点: 在 Span 的 Attributes 中注入 tenant.id 属性,是实现租户级链路过滤与成本核算的关键手段。OpenTelemetry 的 SpanProcessor 可以自动从 Context 中提取租户信息并写入 Attributes。

4.2 Trace:端到端的请求全景图

Trace(追踪链路)由一组具有相同 TraceId 的 Span 组成,完整描述了一个请求从入口到出口经过所有服务节点的执行路径。Trace 本质上是一个有向无环图(DAG),Span 之间通过父子关系或因果关系连接。

一个典型的 Trace 结构如下:

  • Root Span:Trace 的起始 Span,通常由网关或入口服务创建,没有父 Span。
  • Child Span:由 Root Span 或其他 Child Span 派生,代表更细粒度的子操作。
  • Follows-From 关系:某些 Span 之间并非严格的父子调用,而是异步触发或消息队列传递,此时使用 Follows-From 关系。

Trace 的完整性约束: 一个 Trace 必须包含且仅包含一个 Root Span。所有 Span 的 TraceId 必须一致。Span 之间的引用关系通过 ParentSpanId 或 SpanId 列表维护。

Trace 与 Span 层级结构示意图 Trace 与 Span 层级结构 Root Span TraceId: T-001 Child Span A Service: Order Child Span B Service: Payment Child Span C Service: Inventory DB Query Cache Check 所有 Span 共享同一个 TraceId,通过 ParentSpanId 形成有向无环图(DAG)

4.3 Context Propagation:跨服务传递追踪上下文

Context Propagation(上下文传播)是分布式链路追踪的核心机制,负责将追踪上下文(TraceId、SpanId、Baggage 等)从一个服务透明地传递到下一个服务。没有正确的上下文传播,Trace 将在服务边界处断裂,无法形成完整的调用链。

传播方式与协议

传播方式 适用协议 传播载体 示例
W3C Trace Context HTTP / gRPC traceparent 与 tracestate 请求头 traceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
W3C Baggage HTTP / gRPC baggage 请求头 baggage: tenant-id=acme-corp,user-role=admin
gRPC Metadata gRPC gRPC 元数据键值对 grpc-trace-bin 二进制传播
消息队列 Kafka / RabbitMQ 消息头或消息属性 Kafka 消息 Header 注入
租户隔离风险: 如果上下文传播未正确实现,租户信息(如 tenant.id)可能在服务边界丢失,导致后续 Span 无法关联到正确的租户。务必在网关层或入口拦截器中统一注入租户上下文,并确保所有跨服务调用都使用 W3C Trace Context 标准。

OpenTelemetry 中的传播器(Propagator)

OpenTelemetry SDK 内置了多种 Propagator,开发者只需在初始化时配置即可自动完成上下文注入与提取:

// Go 示例:配置 W3C Trace Context 与 Baggage 传播器
import (
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/propagation"
)

func initTracer() {
    // 创建组合传播器:同时支持 Trace Context 和 Baggage
    propagator := propagation.NewCompositeTextMapPropagator(
        propagation.TraceContext{},
        propagation.Baggage{},
    )
    otel.SetTextMapPropagator(propagator)
}

4.4 Baggage:跨服务传递业务上下文

Baggage(行李)是 OpenTelemetry 中一种特殊的上下文传播机制,允许用户将一组键值对数据随 Trace 一起跨服务传递。与 Span Attributes 不同,Baggage 中的数据会自动传播到所有下游 Span,无需在每个服务中手动提取和注入。

Baggage 的典型用途

  • 租户标识传递:将 tenant.id 放入 Baggage,确保所有下游服务都能感知当前请求所属租户。
  • 用户身份与角色:传递 user.id、user.role 等身份信息,用于细粒度权限校验。
  • 请求优先级与标签:传递 request.priority、experiment.group 等业务标签,影响下游服务的处理策略。
  • 端到端采样决策:传递 sampling.decision,让所有服务遵循统一的采样策略。

Baggage 与 Span Attributes 的区别: Baggage 是 跨服务传播 的,而 Span Attributes 是 Span 本地 的。Baggage 中的数据会自动出现在所有下游 Span 的 Attributes 中(通过 SDK 自动转换),但开发者也可以选择只读取 Baggage 而不写入 Attributes。

Baggage 跨服务传播流程 Baggage 跨服务传播流程 Gateway 注入 Baggage Order Service 读取 Baggage Payment Service 读取 Baggage Baggage 内容:tenant-id=acme-corp, user-role=admin, request-priority=high Baggage 自动随 Trace 传播,所有下游服务无需额外编码即可获取业务上下文 注意:Baggage 数据量应保持精简,避免传递大量数据导致性能开销

4.5 核心概念关系总结

概念 作用域 生命周期 是否跨服务传播 租户隔离中的角色
Span 单个服务内的单个操作 操作开始到结束 否(本地) 记录租户 ID 属性,用于过滤与计费
Trace 端到端请求全路径 请求开始到结束 是(通过 TraceId) 按租户聚合 Trace,分析租户级性能
Context Propagation 服务边界 每次跨服务调用 是(核心机制) 确保租户上下文不丢失
Baggage 整个 Trace 链路 随 Trace 持续存在 是(自动传播) 传递租户标识、角色等业务上下文

最佳实践: 在多租户场景下,建议将 tenant.id 同时放入 Span Attributes 和 Baggage。Attributes 用于本地过滤与存储,Baggage 用于跨服务自动传递。OpenTelemetry SDK 会自动将 Baggage 中的键值对写入每个新创建的 Span 的 Attributes 中,实现双重保障。