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 头在服务间传播:
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 的自动注入/提取功能,减少手动编码错误。