2. Jaeger 数据模型:Trace、Span、SpanContext 的关系与 Span 结构

在 Jaeger 的分布式追踪体系中,Trace、Span 和 SpanContext 是三个最核心的数据抽象。理解它们之间的层级关系与 Jaeger 特有的 Span 结构,是后续进行标签复盘与性能分析的基础。

2.1 核心概念定义

概念 定义 生命周期
Trace 一次完整请求或事务在分布式系统中的全路径追踪,由一组具有相同 TraceID 的 Span 组成。 从入口请求开始到最终响应结束。
Span Trace 中的单个工作单元,记录一个操作(如 RPC 调用、数据库查询)的开始、结束、标签和日志。 从操作开始到操作结束。
SpanContext Span 的上下文标识,包含 TraceID、SpanID、Baggage 等,用于跨进程传递追踪状态。 随 Span 创建而生成,随 Span 结束而归档。
关键原则: Trace 是 Span 的有向无环图(DAG),SpanContext 是连接这些 Span 的“信使”。没有 SpanContext,跨服务的 Span 无法关联到同一个 Trace。

2.2 三者的层级关系

下图展示了 Trace、Span、SpanContext 之间的包含与引用关系。一个 Trace 包含多个 Span,每个 Span 内部携带一个 SpanContext,SpanContext 通过 TraceID 和 ParentSpanID 形成父子链路。

Trace、Span、SpanContext 层级关系图 Trace、Span、SpanContext 层级关系 Trace (TraceID: abc123) Span A (Root) SpanID: A1 ParentSpanID: null Span B SpanID: B2 ParentSpanID: A1 Span C SpanID: C3 ParentSpanID: B2 SpanContext 传递内容 TraceID: abc123 | SpanID: (当前 Span) | Baggage: user_id=9527 通过 HTTP 头 / gRPC 元数据 在服务间传递 父子关系规则 Root Span 的 ParentSpanID 为 null;子 Span 通过 ParentSpanID 指向父 Span 的 SpanID
复盘提示: 在 Jaeger UI 中查看 Trace 时,注意观察每个 Span 的 ParentSpanID。如果某个 Span 的 ParentSpanID 指向了一个不存在的 SpanID,说明该 Trace 存在“断链”,通常由采样不一致或上下文丢失导致。

2.3 Jaeger 特有的 Span 结构

Jaeger 的 Span 结构在 OpenTracing 标准基础上进行了扩展,增加了 Jaeger 特有的字段。以下是 Jaeger Span 的完整数据结构(以 JSON 序列化为例):

{
  "traceID": "abc123def456",
  "spanID": "a1b2c3d4",
  "operationName": "HTTP GET /api/users",
  "references": [
    {
      "refType": "CHILD_OF",
      "traceID": "abc123def456",
      "spanID": "root_span_id"
    }
  ],
  "startTime": 1680000000000000,   // 微秒
  "duration": 150000,               // 微秒
  "tags": [
    {"key": "http.method", "vType": "STRING", "vStr": "GET"},
    {"key": "http.status_code", "vType": "INT64", "vInt64": 200},
    {"key": "error", "vType": "BOOL", "vBool": false}
  ],
  "logs": [
    {
      "timestamp": 1680000000050000,
      "fields": [
        {"key": "event", "vType": "STRING", "vStr": "cache.miss"},
        {"key": "cache.key", "vType": "STRING", "vStr": "user:9527"}
      ]
    }
  ],
  "process": {
    "serviceName": "user-service",
    "tags": [
      {"key": "hostname", "vType": "STRING", "vStr": "node-1"},
      {"key": "ip", "vType": "STRING", "vStr": "10.0.0.1"}
    ]
  },
  "spanKind": "client"  // Jaeger 特有字段
}

2.4 Jaeger Span 关键字段说明

字段 类型 说明 复盘关注点
traceID string 全局唯一 Trace 标识,通常为 128 位或 64 位十六进制 检查是否跨服务一致
spanID string 当前 Span 的唯一标识 用于关联父子关系
references array 引用关系列表,常见类型:CHILD_OF、FOLLOWS_FROM 确认链路拓扑是否正确
startTime / duration int64 开始时间与持续时长,单位微秒 性能分析的核心指标
tags array 键值对标签,支持 STRING、INT64、BOOL、FLOAT64 四种类型 复盘规范的主要操作对象
logs array 时间戳事件列表,每条 log 包含多个 field 用于记录异常、缓存命中/未命中等
process object 进程信息,包含 serviceName 和进程级标签 确认服务归属与部署环境
spanKind string Jaeger 特有,标识 Span 角色:client、server、producer、consumer 区分调用方向,辅助定位瓶颈

2.5 Span 引用关系与链路拓扑

Jaeger 支持两种引用关系:

  • CHILD_OF:父 Span 等待子 Span 完成,典型的同步调用关系(如 HTTP 请求)。
  • FOLLOWS_FROM:父 Span 不等待子 Span 完成,典型的异步消息或后台任务关系。

下面的 SVG 展示了两种引用关系在链路拓扑中的区别:

CHILD_OF 与 FOLLOWS_FROM 引用关系对比 CHILD_OF 与 FOLLOWS_FROM 引用关系对比 CHILD_OF(同步调用) Service A Service B A 等待 B 返回结果后才继续执行 Span 生命周期:A 的 Span 包含 B 的 Span FOLLOWS_FROM(异步调用) Service A Service B A 发送消息后立即继续执行,不等待 B Span 生命周期:A 的 Span 与 B 的 Span 独立 复盘要点 CHILD_OF 用于 RPC、数据库查询;FOLLOWS_FROM 用于消息队列、异步任务
常见陷阱: 在 Jaeger 中,如果异步 Span 使用了 CHILD_OF 而非 FOLLOWS_FROM,会导致 Jaeger UI 显示错误的 Trace 时长(父 Span 会等待异步子 Span 完成,造成不必要的超时假象)。复盘时务必检查引用类型。

2.6 SpanContext 的跨进程传递机制

SpanContext 是分布式追踪的“粘合剂”。Jaeger 通过以下方式在服务间传递 SpanContext:

  • HTTP:通过 uber-trace-id 请求头传递,格式为 {traceID}:{spanID}:{parentSpanID}:{flags}。
  • gRPC:通过 gRPC 的 metadata 传递,key 为 uber-trace-id。
  • 消息队列:通过消息的 header 或 properties 传递。
复盘检查清单:
✅ 检查所有跨服务调用是否携带了 uber-trace-id 头。
✅ 检查网关或代理层是否透传了追踪上下文。
✅ 检查异步消息消费者是否正确提取并恢复了 SpanContext。