4. OpenTelemetry 基础:Trace、Span、Context 核心概念与数据模型
在分布式系统中,一个用户请求往往跨越多个微服务、数据库、消息队列和第三方 API。当出现错误或性能瓶颈时,传统的单机日志和指标无法还原请求的完整路径。OpenTelemetry(简称 OTel)正是为了解决这一难题而诞生的统一可观测性标准。
4.1 什么是 OpenTelemetry?
OpenTelemetry 是 CNCF(云原生计算基金会)的孵化项目,由 OpenTracing 和 OpenCensus 合并而来。它提供了一套厂商中立的 API、SDK 和工具,用于生成、采集和导出遥测数据(Traces、Metrics、Logs)。
核心定位: 可观测性领域的“HTTP 协议”——你只需按照 OTel 标准埋点,就能将数据发送到任何兼容的后端(Jaeger、Zipkin、Prometheus、Datadog 等)。
| 特性 | 说明 |
|---|---|
| 统一 API | 一套 API 覆盖 Trace、Metric、Log,避免供应商锁定 |
| 多语言支持 | 官方支持 Python、Go、Java、JavaScript、.NET 等主流语言 |
| 自动/手动埋点 | 提供自动插桩(Instrumentation)库,也支持手动创建 Span |
| 可扩展导出 | 通过 OTLP 协议或 Exporter 适配器对接任意后端 |
| 上下文传播 | 内置 W3C TraceContext 和 Baggage 传播协议 |
4.2 Trace、Span、Context 三大核心概念
理解 OTel 的调用链模型,必须从以下三个概念入手:
- Trace(追踪):代表一个完整的请求或事务在分布式系统中的全生命周期。一个 Trace 由多个 Span 组成,通过唯一的
TraceID标识。 - Span(跨度):Trace 中的最小工作单元,描述一个具体的操作(如“查询数据库”、“调用订单服务”)。每个 Span 包含名称、开始/结束时间、属性、事件和状态。
- Context(上下文):携带 TraceID、SpanID 和 Baggage 等传播信息的对象,负责在进程内和进程间传递调用关系。
记忆口诀: Trace 是“整条链路”,Span 是“链路中的每一步”,Context 是“串联每一步的绳子”。
4.3 Span 数据模型详解
Span 是 OTel 数据模型的核心。以下是一个 Span 的完整结构:
| 字段 | 类型 | 说明 |
|---|---|---|
TraceId |
16 字节 | 全局唯一,标识所属 Trace |
SpanId |
8 字节 | 当前 Span 的唯一标识 |
ParentSpanId |
8 字节 | 父 Span 的 ID,根 Span 为空 |
Name |
string | 操作名称,如 HTTP GET /api/order |
Kind |
enum | Span 角色:CLIENT、SERVER、INTERNAL、PRODUCER、CONSUMER |
StartTime / EndTime |
timestamp | 操作开始和结束的精确时间 |
Attributes |
map | 键值对,记录请求 URL、状态码、数据库语句等 |
Events |
list | 带时间戳的注解,用于记录异常、日志等 |
Status |
StatusCode | UNSET、OK、ERROR |
Links |
list | 关联其他 Trace 的 Span,用于异步场景 |
4.4 Trace 与 Span 的层级关系(SVG 图)
下图展示了一个典型的分布式 Trace 结构:根 Span 代表前端请求,子 Span 分别代表网关、订单服务和数据库操作。
4.5 Context 传播机制
Context 是 OTel 调用链的“粘合剂”。它确保跨进程的 Span 能够正确关联。传播过程分为两步:
- 注入(Inject):在客户端(如 HTTP 调用方)将当前 Context 序列化到请求头中。
- 提取(Extract):在服务端从请求头中反序列化出 Context,并创建子 Span。
OTel 默认使用 W3C TraceContext 协议,通过 traceparent 和 tracestate 两个 HTTP 头传递上下文。
常见陷阱: 如果使用异步消息队列(如 Kafka、RabbitMQ),Context 不会自动传播。必须手动在生产者端注入、消费者端提取,否则调用链会断裂。
4.6 Span Kind 与角色定义
Span Kind 描述了 Span 在分布式调用中的角色,帮助后端系统更好地可视化调用关系:
| Kind | 适用场景 | 示例 |
|---|---|---|
INTERNAL |
进程内部操作,无外部网络调用 | 函数计算、本地缓存查询 |
CLIENT |
发起外部请求的一方 | HTTP 客户端、gRPC 客户端 |
SERVER |
接收外部请求的一方 | HTTP 服务端、gRPC 服务端 |
PRODUCER |
向消息队列发送消息 | Kafka 生产者、RabbitMQ 发布者 |
CONSUMER |
从消息队列接收消息 | Kafka 消费者、RabbitMQ 订阅者 |
4.7 数据模型与传播流程(SVG 图)
下图展示了两个服务之间通过 HTTP 调用时,Context 的注入、提取以及 Span 的创建过程:
4.8 代码示例:Python 手动创建 Span
以下代码展示了如何在 Python 中使用 OTel SDK 手动创建 Trace 和 Span:
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import ConsoleSpanExporter, SimpleSpanProcessor
# 1. 初始化 TracerProvider
provider = TracerProvider()
provider.add_span_processor(SimpleSpanProcessor(ConsoleSpanExporter()))
trace.set_tracer_provider(provider)
# 2. 获取 Tracer
tracer = trace.get_tracer(__name__)
# 3. 创建根 Span
with tracer.start_as_current_span("parent-span") as parent:
parent.set_attribute("http.method", "GET")
parent.add_event("开始处理请求")
# 4. 创建子 Span
with tracer.start_as_current_span("child-span") as child:
child.set_attribute("db.system", "mysql")
child.set_attribute("db.statement", "SELECT * FROM orders")
child.add_event("查询完成")
# 5. 设置状态
parent.set_status(trace.Status(trace.StatusCode.OK))
关键点:
start_as_current_span 会自动将新 Span 的父 Span 设置为当前激活的 Span。如果当前没有激活的 Span,则新 Span 会成为根 Span。
4.9 本章小结
- OpenTelemetry 是统一的可观测性标准,解决厂商锁定问题。
- Trace 代表完整请求链路,Span 是链路中的最小工作单元,Context 负责跨进程传播。
- Span 数据模型包含 TraceId、SpanId、ParentSpanId、Attributes、Events、Status 等核心字段。
- Context 通过 W3C TraceContext 协议在 HTTP 头中传播,异步场景需手动处理。
- Span Kind 定义了 Span 在调用中的角色,影响后端可视化效果。