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 |
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 列表维护。
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。
4.5 核心概念关系总结
| 概念 | 作用域 | 生命周期 | 是否跨服务传播 | 租户隔离中的角色 |
|---|---|---|---|---|
| Span | 单个服务内的单个操作 | 操作开始到结束 | 否(本地) | 记录租户 ID 属性,用于过滤与计费 |
| Trace | 端到端请求全路径 | 请求开始到结束 | 是(通过 TraceId) | 按租户聚合 Trace,分析租户级性能 |
| Context Propagation | 服务边界 | 每次跨服务调用 | 是(核心机制) | 确保租户上下文不丢失 |
| Baggage | 整个 Trace 链路 | 随 Trace 持续存在 | 是(自动传播) | 传递租户标识、角色等业务上下文 |
最佳实践: 在多租户场景下,建议将 tenant.id 同时放入 Span Attributes 和 Baggage。Attributes 用于本地过滤与存储,Baggage 用于跨服务自动传递。OpenTelemetry SDK 会自动将 Baggage 中的键值对写入每个新创建的 Span 的 Attributes 中,实现双重保障。