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) 事务成功/失败状态
Span 生命周期与事务边界映射 Span A:HTTP 请求 Span B:DB 事务 Span C:消息发送 Span D:响应 事务边界设计要点: • Span 的开始/结束时间戳精确对应事务的 begin/commit/rollback • 父子 Span 关系映射调用链中的事务嵌套(如:HTTP 请求 → 数据库事务) • Span 的 Status.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
Context Propagation 跨服务传递流程 服务 A Span A (TraceId: X) Baggage: user_id=123 服务 B Span B (TraceId: X) Baggage: user_id=123 服务 C Span C (TraceId: X) Baggage: user_id=123 HTTP traceparent gRPC Metadata 传播关键点: 1. 服务 A 在发起 HTTP 请求前,将当前 Span 上下文注入到请求头 traceparent 2. 服务 B 收到请求后,从请求头提取上下文,创建子 Span B,并继承 TraceId 3. Baggage 随 tracestate 或自定义头部传递,保证业务上下文不丢失

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 四者协同:事务边界设计模型

在事务边界与结构化日志协同设计中,四个核心概念形成以下协作关系:

  1. Span 定义每个事务边界的开始和结束,记录事务耗时和状态。
  2. Trace 将多个 Span 组织成完整的事务链路,提供全局视图。
  3. Context Propagation 确保事务上下文在服务间无缝传递,不断裂。
  4. Baggage 携带业务级事务标识(如事务 ID、业务类型),供日志和监控系统关联。
四者协同:事务边界设计模型 事务边界 (业务事务 / 数据库事务) Span Trace Context Propagation Baggage Span 定义边界 → Trace 串联边界 → Context Propagation 传递边界 → Baggage 丰富边界

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 负责“富边界”。四者缺一不可,共同构成分布式事务的可观测性基础。