2. OpenTelemetry 核心概念:Span、Trace、Context Propagation、Baggage
在构建可观测系统时,理解 OpenTelemetry 的四大核心概念是掌握事务边界与结构化日志协同设计的基础。这些概念共同构成了分布式追踪的骨架,决定了数据如何被采集、关联和传递。
2.1 Span:工作单元与事务边界
Span 是分布式追踪中最基本的逻辑单元,代表一个具有开始时间和结束时间的操作片段。在事务边界设计中,Span 天然对应一个“事务边界”——无论是数据库事务、RPC 调用、消息队列生产/消费,还是本地函数执行。
关键属性: Span 包含操作名称、开始/结束时间戳、Span 上下文(TraceId、SpanId、ParentSpanId)、属性(Attributes)、事件(Events)、状态(Status)和链接(Links)。
| 属性 | 说明 | 事务边界映射 |
|---|---|---|
| SpanId | 当前 Span 的唯一标识(16 字节) | 事务实例 ID |
| TraceId | 整条追踪链的唯一标识(16 字节) | 全局事务链 ID |
| ParentSpanId | 父 Span 的 SpanId,用于构建父子关系 | 调用方事务 ID |
| Start / End Timestamp | Span 的生命周期时间戳 | 事务开始/提交/回滚时间 |
| Status | 操作结果(Unset / Ok / Error) | 事务成功/失败状态 |
2.2 Trace:端到端事务链路
Trace 是由一组具有相同 TraceId 的 Span 构成的树形结构,完整记录一次请求或事务从入口到出口的完整路径。在事务边界协同中,Trace 就是“全局事务视图”。
- TraceId 共享: 同一 Trace 内的所有 Span 共享同一个 TraceId,这是跨服务关联事务的唯一凭证。
- Span 树结构: 根 Span 代表事务入口(如 API 网关),子 Span 代表内部调用(如服务 A → 服务 B → 数据库)。
- 事务边界聚合: 通过 Trace 可以聚合一次完整事务中所有子事务的耗时、状态和异常信息。
实践建议: 在结构化日志中始终携带 TraceId 和 SpanId,这样日志系统可以按 TraceId 聚合所有相关日志,实现“一次事务,全链路日志”的查询能力。
2.3 Context Propagation:跨进程上下文传递
Context Propagation 是 OpenTelemetry 实现分布式追踪的核心机制,负责将 Span 上下文(TraceId、SpanId、Baggage 等)在进程内和进程间传递。没有正确的传播,事务边界将断裂。
| 传播方式 | 场景 | 协议/格式 |
|---|---|---|
| 进程内 | 线程切换、协程切换、异步回调 | Context 对象(语言原生) |
| HTTP | REST API、gRPC | W3C TraceContext(traceparent / tracestate 请求头) |
| 消息队列 | Kafka、RabbitMQ、RocketMQ | 消息属性/头部注入 |
| gRPC | 微服务间调用 | gRPC Metadata |
2.4 Baggage:业务上下文携带
Baggage 是 OpenTelemetry 中用于在 Span 和 Trace 之间传递键值对信息的机制。与 Span 属性不同,Baggage 会随着 Context Propagation 自动传递到所有下游服务,非常适合携带事务边界所需的业务上下文。
注意: Baggage 会随每个请求传递,因此只应携带轻量级、高价值的业务上下文(如用户 ID、订单 ID、租户 ID),避免携带大量数据或敏感信息(如密码、Token)。
| 特性 | Span Attributes | Baggage |
|---|---|---|
| 传递范围 | 仅当前 Span | 整条 Trace 所有 Span |
| 跨服务传播 | 否 | 是(自动注入请求头) |
| 典型用途 | 操作详情(SQL、URL、参数) | 业务上下文(用户 ID、会话 ID) |
| 数据量限制 | 无严格限制 | 建议 < 1KB,避免头部过大 |
2.5 四者协同:事务边界设计模型
在事务边界与结构化日志协同设计中,四个核心概念形成以下协作关系:
- Span 定义每个事务边界的开始和结束,记录事务耗时和状态。
- Trace 将多个 Span 组织成完整的事务链路,提供全局视图。
- Context Propagation 确保事务上下文在服务间无缝传递,不断裂。
- Baggage 携带业务级事务标识(如事务 ID、业务类型),供日志和监控系统关联。
2.6 代码示例:Span 创建与 Baggage 注入
以下示例展示如何在 Go 中创建 Span、注入 Baggage,并通过 Context Propagation 传递到下游服务:
import (
"context"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/baggage"
"go.opentelemetry.io/otel/trace"
)
func handleTransaction(ctx context.Context, orderID string) {
tracer := otel.Tracer("transaction-service")
// 创建 Span,代表一个事务边界
ctx, span := tracer.Start(ctx, "process-order",
trace.WithAttributes(
attribute.String("order.id", orderID),
attribute.String("transaction.type", "payment"),
),
)
defer span.End()
// 注入 Baggage:携带业务上下文
member, _ := baggage.NewMember("order_id", orderID)
bag, _ := baggage.New(member)
ctx = baggage.ContextWithBaggage(ctx, bag)
// 调用下游服务(Context Propagation 自动传递 Baggage)
callPaymentService(ctx, orderID)
}
func callPaymentService(ctx context.Context, orderID string) {
// 从 Context 中提取 Baggage
bag := baggage.FromContext(ctx)
orderIDFromBag := bag.Member("order_id").Value()
// 创建子 Span,继承 TraceId
_, span := otel.Tracer("payment-service").Start(ctx, "charge")
defer span.End()
span.SetAttributes(
attribute.String("order.id", orderIDFromBag),
attribute.String("service", "payment"),
)
}
核心要点: 在事务边界设计中,Span 负责“画边界”,Trace 负责“连边界”,Context Propagation 负责“传边界”,Baggage 负责“富边界”。四者缺一不可,共同构成分布式事务的可观测性基础。