4. 链路追踪核心概念:Span、Trace、Context Propagation、Baggage
分布式链路追踪是构建可观测性体系的三大支柱之一。要理解链路追踪如何在多服务、多租户环境中传递请求上下文,必须首先掌握其四个核心概念:Span、Trace、Context Propagation 和 Baggage。本节将逐一拆解这些概念,并结合租户隔离场景说明其作用。
4.1 Span:追踪的最小工作单元
Span(跨度)是链路追踪中最基本的逻辑单元,代表一个分布式系统中发生的单个操作或工作步骤。每个 Span 记录了一个操作的开始时间、结束时间、状态以及一组属性(Attributes)和事件(Events)。
一个 Span 的核心数据结构包含以下字段:
| 字段 | 说明 | 租户隔离场景示例 |
|---|---|---|
| SpanId | 当前 Span 的唯一标识符 | 每个租户的请求处理步骤拥有独立 SpanId |
| TraceId | 所属 Trace 的唯一标识符 | 同一租户请求的所有 Span 共享一个 TraceId |
| ParentSpanId | 父 Span 的标识符,用于构建调用树 | 租户请求入口 Span 无父 Span,后续子 Span 指向它 |
| StartTime / EndTime | 操作开始和结束的时间戳 | 用于计算租户请求在各服务中的耗时分布 |
| Attributes | 键值对形式的元数据 | tenant.id、tenant.plan 等租户属性 |
| Events | Span 生命周期内的标注事件 | 记录租户请求的异常或关键业务节点 |
| Status | 操作结果状态(OK / Error / Unset) | 区分租户请求成功或失败 |
tenant.id 属性。这允许你在后续的查询和分析中,按租户维度过滤所有 Span,实现租户级别的性能诊断。
4.2 Trace:一次完整请求的端到端视图
Trace(追踪)由一组具有相同 TraceId 的 Span 组成,这些 Span 跨越一个或多个服务,共同描述一次完整请求从入口到出口的完整路径。Trace 本质上是一个有向无环图(DAG),其中每个 Span 通过 ParentSpanId 形成父子关系。
一个典型的 Trace 结构如下:
TraceId: abc123def456
├── Span A (Frontend Service) — 处理 HTTP 请求
│ ├── Span B (Auth Service) — 验证用户身份
│ │ └── Span C (DB Query) — 查询用户权限
│ └── Span D (Order Service) — 处理订单
│ └── Span E (Payment Service) — 执行支付
└── Span F (Frontend Service) — 返回响应
在租户隔离架构中,Trace 的边界尤为重要:
- 租户 A 的请求 产生的 Trace 不应与 租户 B 的请求 的 Trace 混淆。
- 每个 Trace 的根 Span(Root Span)通常由网关或入口服务创建,并在该 Span 中标记
tenant.id。 - 通过 TraceId 可以完整还原一次租户请求的全链路调用拓扑。
4.3 Context Propagation:跨服务传递追踪上下文
Context Propagation(上下文传播)是分布式链路追踪的核心机制。它确保当请求从一个服务传递到另一个服务时,追踪上下文(TraceId、SpanId 等)能够被正确携带和恢复,从而将分散在各服务中的 Span 关联到同一个 Trace 中。
上下文传播通常通过以下方式实现:
- HTTP 传播:通过 HTTP 头传递上下文,如
traceparent、tracestate(W3C Trace Context 标准)。 - 消息队列传播:通过消息头或消息属性传递上下文,如 Kafka 的 Headers、RabbitMQ 的 Message Properties。
- gRPC 传播:通过 gRPC 的 Metadata 传递上下文。
- 进程内传播:通过线程局部变量(ThreadLocal)或协程上下文传递。
tenant.id 作为 Baggage 的一部分随 Trace 上下文一起传播。如果传播链路中断,租户信息将丢失,导致后续服务无法正确识别请求所属租户。
以下是一个典型的 HTTP 上下文传播示例:
// 服务 A:创建并注入上下文
Span span = tracer.spanBuilder("handleRequest")
.setAttribute("tenant.id", "t-001")
.startSpan();
try (Scope scope = span.makeCurrent()) {
// 将上下文注入到 HTTP 请求头
TextMapPropagator propagator = OpenTelemetry.getGlobalPropagators()
.getTextMapPropagator();
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
propagator.inject(Context.current(), connection, (carrier, key, value) ->
carrier.setRequestProperty(key, value));
connection.connect();
} finally {
span.end();
}
// 服务 B:提取并恢复上下文
TextMapPropagator propagator = OpenTelemetry.getGlobalPropagators()
.getTextMapPropagator();
Context extractedContext = propagator.extract(
Context.current(), httpServletRequest, (carrier, key) ->
carrier.getHeader(key));
try (Scope scope = extractedContext.makeCurrent()) {
// 此时当前 Span 自动成为服务 A 中 Span 的子 Span
Span span = tracer.spanBuilder("processRequest").startSpan();
// 从上下文中获取 tenant.id
String tenantId = Baggage.current().getEntryValue("tenant.id");
span.setAttribute("tenant.id", tenantId);
// ... 处理逻辑
span.end();
}
4.4 Baggage:携带业务上下文的键值对
Baggage(行李)是 OpenTelemetry 提供的一种机制,用于在 Trace 的所有 Span 之间传递键值对形式的业务上下文数据。与 Span Attributes 不同,Baggage 会随着 Context Propagation 自动传播到所有下游服务,而无需在每个 Span 中手动设置。
Baggage 的主要特性:
| 特性 | 说明 | 租户隔离场景价值 |
|---|---|---|
| 自动传播 | 随 Trace 上下文自动传递到所有下游 Span | 租户 ID 无需在每个服务中手动传递 |
| 键值对结构 | 支持字符串类型的键和值 | 可携带 tenant.id、tenant.plan、user.role 等 |
| 跨协议支持 | 通过 W3C Baggage 头(baggage)传播 |
HTTP、gRPC、消息队列均可使用 |
| 低开销 | 仅传递字符串,不参与采样决策 | 适合携带大量租户元数据 |
| 可过滤 | 可在导出时选择是否包含 Baggage | 避免敏感租户信息泄露到外部系统 |
4.5 四个核心概念的关系总结
Span、Trace、Context Propagation 和 Baggage 共同构成了分布式链路追踪的基石。它们之间的关系可以概括为:
- Span 是构建 Trace 的砖块,每个 Span 记录一个操作。
- Trace 是由 Span 组成的树状结构,代表一次完整请求。
- Context Propagation 是粘合剂,确保 Span 能跨服务关联成 Trace。
- Baggage 是附加数据通道,让业务上下文(如租户 ID)随 Trace 流动。
tenant. 前缀。这可以避免团队协作中的命名冲突,并方便后续的自动化处理和监控告警。