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) 区分租户请求成功或失败
实践要点:在租户隔离场景中,务必在每个 Span 的 Attributes 中注入 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 可以完整还原一次租户请求的全链路调用拓扑。
Trace 与 Span 层级关系图 Trace 与 Span 层级关系 Trace: abc123def456 Span A: Frontend /handleOrder tenant.id: t-001 Span B: Auth /verifyToken Span C: Order /createOrder Span D: Payment /charge Span E: DB /updateBalance 每个 Span 共享同一 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 避免敏感租户信息泄露到外部系统
注意事项:Baggage 中的数据会传播到所有下游服务,因此不要在 Baggage 中放置敏感信息(如密码、令牌、个人身份信息)。如果必须传递敏感数据,请使用加密或仅在受信任的服务间使用自定义传播机制。
Context Propagation 与 Baggage 传播流程 Context Propagation 与 Baggage 传播流程 服务 A (入口) 创建 Trace + Baggage 服务 B (中间件) 提取上下文 + Baggage 服务 C (下游) 使用 Baggage 数据 传播内容:traceparent (TraceId, SpanId) + tracestate + baggage (tenant.id=t-001, tenant.plan=premium) 注入 Baggage Baggage.current().toBuilder() 提取 Baggage Baggage.current().getEntryValue() 使用 Baggage span.setAttribute("tenant.id", ...)

4.5 四个核心概念的关系总结

Span、Trace、Context Propagation 和 Baggage 共同构成了分布式链路追踪的基石。它们之间的关系可以概括为:

  • Span 是构建 Trace 的砖块,每个 Span 记录一个操作。
  • Trace 是由 Span 组成的树状结构,代表一次完整请求。
  • Context Propagation 是粘合剂,确保 Span 能跨服务关联成 Trace。
  • Baggage 是附加数据通道,让业务上下文(如租户 ID)随 Trace 流动。
租户隔离实战建议:在项目初期就建立统一的 Baggage 键命名规范,例如所有租户相关键统一使用 tenant. 前缀。这可以避免团队协作中的命名冲突,并方便后续的自动化处理和监控告警。