3. 链路追踪基础:Trace / Span / Context 概念、OpenTelemetry 协议、分布式传播

在“三信号联动修复”体系中,链路追踪(Tracing)是连接日志与指标的桥梁。它通过记录一次请求在分布式系统中的完整路径,让工程师能够精确回答“哪个服务慢了多少毫秒”以及“错误发生在哪个环节”。本章将深入剖析 Trace / Span / Context 的核心概念、OpenTelemetry 协议标准以及分布式传播机制,为后续的联动修复打下坚实基础。

3.1 核心概念:Trace、Span 与 Context

链路追踪的三大基石是 Trace(追踪)、Span(跨度) 和 Context(上下文)。理解它们的关系是掌握追踪技术的第一步。

概念 定义 类比(一次电商下单)
Trace 代表一次完整的请求或事务在分布式系统中的全生命周期。由一个全局唯一的 TraceID 标识。 用户从点击“下单”到收到“成功”页面的整个过程。
Span Trace 中的一个工作单元,记录一个特定操作(如数据库查询、RPC 调用)的开始、结束、状态和元数据。每个 Span 有唯一的 SpanID,并记录其父 SpanID。 下单过程中的“库存扣减”操作。
Context 携带 TraceID、SpanID 等传播信息的对象,用于在服务间传递追踪状态,确保 Span 能正确关联到同一个 Trace。 快递单上的订单号,让仓库、物流各环节知道这是同一笔订单。
关键原则: 一个 Trace 由一棵 Span 树组成。根 Span(Root Span)代表入口请求,子 Span 代表内部调用。通过 TraceID 串联所有 Span,通过 ParentSpanID 构建层级关系。

3.2 OpenTelemetry 协议与数据模型

OpenTelemetry(OTel) 是 CNCF 的观测性标准,它统一了数据采集、处理和导出的规范。在链路追踪领域,OTel 定义了标准的数据模型和协议。

3.2.1 Span 数据模型核心字段

字段 类型 说明
TraceId 16 字节(Hex 32 字符) 全局唯一追踪标识
SpanId 8 字节(Hex 16 字符) 当前 Span 唯一标识
ParentSpanId 8 字节(Hex 16 字符) 父 Span 标识,根 Span 为空
Name 字符串 Span 名称,如 /api/order
Kind 枚举 Span 类型:CLIENT、SERVER、INTERNAL、PRODUCER、CONSUMER
StartTime Unix 纳秒时间戳 操作开始时间
EndTime Unix 纳秒时间戳 操作结束时间
Status 对象 状态码(Unset / Ok / Error)及描述
Attributes 键值对列表 自定义元数据,如 http.method、db.system
Events 时间戳 + 名称 + 属性 Span 生命周期内的关键事件,如异常堆栈
Links 关联 Span 列表 连接不同 Trace 的 Span,用于异步场景

3.2.2 OTLP 协议(OpenTelemetry Protocol)

OTLP 是 OTel 的原生传输协议,支持 gRPC 和 HTTP/JSON 两种方式。它定义了 Span 数据的序列化格式,确保不同语言 SDK 采集的数据能被后端统一接收。

// OTLP gRPC 服务定义(简化)
service TraceService {
  rpc Export(ExportTraceServiceRequest) returns (ExportTraceServiceResponse);
}

message ResourceSpans {
  Resource resource = 1;
  repeated ScopeSpans scope_spans = 2;
}

message Span {
  bytes trace_id = 1;
  bytes span_id = 2;
  string name = 3;
  SpanKind kind = 4;
  fixed64 start_time_unix_nano = 5;
  fixed64 end_time_unix_nano = 6;
  Status status = 7;
  repeated KeyValue attributes = 8;
  repeated Event events = 9;
  repeated Link links = 10;
}
实践要点: 在生产环境中,推荐使用 gRPC 协议传输 Span 数据,其二进制编码效率更高,延迟更低。HTTP/JSON 协议更适合调试和开发环境。

3.3 分布式传播(Distributed Context Propagation)

分布式传播是链路追踪的“神经系统”。它确保当请求从一个服务传递到另一个服务时,Trace 上下文能够被正确携带和还原,从而将分散的 Span 串联成完整的 Trace。

3.3.1 传播机制原理

传播的核心是 Context 注入与提取。发送方(如 HTTP 客户端)将 TraceID、SpanID 等信息注入到请求头中;接收方(如 HTTP 服务端)从请求头中提取这些信息,并创建新的子 Span。

标准传播格式: OTel 推荐使用 W3C Trace Context 标准,通过 traceparent 和 tracestate 两个 HTTP 头传递上下文。

3.3.2 W3C Trace Context 格式

HTTP 头 格式 示例
traceparent 版本- TraceID- SpanID- TraceFlags 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
tracestate 厂商自定义键值对,逗号分隔 vendor1=value1,vendor2=value2
  • 版本: 固定为 00。
  • TraceID: 32 字符十六进制,全局唯一。
  • SpanID: 16 字符十六进制,当前 Span 标识。
  • TraceFlags: 01 表示采样,00 表示不采样。

3.3.3 传播流程示例(HTTP 场景)

下图展示了在一次典型的 HTTP 调用中,Trace 上下文如何通过 traceparent 头在服务间传播:

分布式上下文传播流程 分布式上下文传播流程(HTTP 场景) 服务 A(客户端) Span: A-Span-1 服务 B(服务端) Span: B-Span-1 HTTP GET /api/data 请求头携带上下文 traceparent: 00-abc123...-def456...-01 tracestate: vendor=myapp 注入 提取 HTTP 200 OK(响应) 传播关键步骤 ① 服务 A 创建 Span → ② 将 TraceID/SpanID 注入 HTTP 头 → ③ 服务 B 提取上下文 → ④ 创建子 Span 并关联

3.3.4 传播方式对比

传播方式 协议/载体 典型场景 注意事项
HTTP 传播 HTTP Headers(traceparent) REST API、微服务间 HTTP 调用 需确保中间件(如网关)不丢弃自定义头
gRPC 传播 gRPC Metadata 服务间高性能 RPC 调用 OTel SDK 自动处理 Metadata 注入与提取
消息队列传播 消息属性(如 Kafka Headers) 异步消息、事件驱动架构 需手动或通过拦截器注入/提取 Context
进程内传播 线程局部变量(ThreadLocal) 同一进程内的异步回调、协程 注意协程切换时 Context 的传递,避免丢失

3.4 实战:使用 OpenTelemetry SDK 创建 Span 并传播

以下是一个使用 Python OTel SDK 创建 Span 并手动传播上下文的示例:

from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.propagate import inject, extract
import requests

# 初始化 Tracer
provider = TracerProvider()
processor = BatchSpanProcessor(OTLPSpanExporter(endpoint="http://localhost:4317"))
provider.add_span_processor(processor)
trace.set_tracer_provider(provider)
tracer = trace.get_tracer(__name__)

def handle_request():
    with tracer.start_as_current_span("handle-request") as span:
        span.set_attribute("http.method", "GET")
        span.set_attribute("http.url", "/api/order")
        
        # 准备传播上下文到下游服务
        headers = {}
        inject(headers)  # 将当前 Trace 上下文注入到 headers 字典
        
        # 发起 HTTP 调用,携带上下文
        response = requests.get(
            "http://service-b/api/data",
            headers=headers
        )
        
        span.set_attribute("http.status_code", response.status_code)

# 接收端(服务 B)
def receive_request(request):
    # 从请求头中提取上下文
    ctx = extract(request.headers)
    
    # 在提取的上下文中创建子 Span
    with tracer.start_as_current_span("process-data", context=ctx) as span:
        span.set_attribute("db.system", "redis")
        # 执行业务逻辑...
常见陷阱: 在异步框架(如 FastAPI、Celery)中,务必确保 Context 在任务切换时被正确传递。使用 contextvars 或框架提供的中间件来管理上下文,避免 Span 错乱或丢失。

3.5 本章小结

  • Trace / Span / Context 是链路追踪的三大核心概念,Trace 由 Span 树构成,Context 负责跨服务传播。
  • OpenTelemetry 提供了标准化的 Span 数据模型和 OTLP 协议,确保多语言、多后端兼容。
  • 分布式传播 通过 W3C Trace Context 标准(traceparent 头)实现,支持 HTTP、gRPC、消息队列等多种载体。
  • 在生产环境中,务必验证传播链路的完整性,使用 OTel SDK 的自动注入/提取功能,减少手动编码错误。