Skip to content

分布式追踪:trace/span 与 OTel 后端架构

基于 Jaeger 1.x + OpenTelemetry · 核于 2026-08

速查

  • span 必备字段:traceID(所属 trace)+ spanID(唯一)+ parentSpanID(父 span,根 span 无)+ operationName + serviceName + startTime + duration + tags + logs。
  • trace 树:多个 span 按 parentSpanID 组成树,根 span 是请求入口(parentSpanID 为空)。瀑布图按树形展开。
  • 上下文传播(Context Propagation):traceID + 当前 spanID 随请求跨服务传递,通过 HTTP header(W3C traceparent / Jaeger uber-trace-id / Zipkin B3)或 gRPC/Kafka metadata。下游读 context 创建 child span。
  • W3C Trace Contexttraceparent: 00-<trace-id>-<span-id>-<flags>):业界标准,OTel 默认,跨厂商兼容。
  • OTel SDK 三件套:①Tracer Provider(创建 tracer);②Tracer(创建 span);③Exporter(导出到后端如 Jaeger)。配合 Instrumentation Library(自动埋点 HTTP/DB/Kafka)。
  • OTel Collector(可选中转):接收 SDK 数据 → 处理(批量/过滤/重写/尾部采样)→ 导出多后端。解耦 SDK 与后端,降低 SDK 耦合。
  • Jaeger 后端架构:①Agent(每机器 sidecar,接收 SDK 数据);②Collector(接收+处理+存后端);③Storage(Cassandra/ES/内存);④Query(查询 API);⑤UI(瀑布图可视化)。
  • OTLP 协议:OTel 原生协议,Jaeger 1.35+ 原生支持,是 OTel SDK 导出 Jaeger 的推荐方式。

一、span 数据结构详解

一个 span 包含的字段:

json
{
  "traceID": "4bf92f3577b34da6a3ce929d0e0e4736",
  "spanID": "a156f3577b34da6",
  "operationName": "GET /api/order",
  "references": [
    { "type": "CHILD_OF", "spanID": "父spanID", "traceID": "同traceID" }
  ],
  "startTime": 1723209600000000,           // 微秒时间戳
  "duration": 180000,                        // 微秒(180ms)
  "tags": {                                  // 属性(可索引检索)
    "http.method": "GET",
    "http.status_code": 500,
    "error": true,
    "otel.scope.name": "order-service"
  },
  "logs": [                                  // 事件日志(span 内)
    { "timestamp": ..., "fields": { "event": "error", "stack": "..." } }
  ],
  "process": {
    "serviceName": "order-service",
    "tags": { "hostname": "pod-abc", "env": "prod" }
  }
}
  • references:父子关系,CHILD_OF(同步调用,父等子)vs FOLLOWS_FROM(异步,父不等子,如发 Kafka 消息)。
  • tags:属性,用于按属性检索(service/operation/tags)。error=true 标记错误 span。
  • logs:span 内的事件日志(如异常栈),不同于应用日志。

二、trace 树与瀑布图

traceID: abc123,根 span = gateway
时间轴 →
0ms        100ms       200ms       300ms       380ms
│          │           │           │           │
gateway    ████████████████████████████████████████  380ms (span A)
user-svc      ████                                    50ms  (span B, parent A)
order-svc        ███████████████████████████          280ms (span C, parent A)
inventory             █████                           80ms  (span D, parent C)
payment                   ████████████████             180ms (span E, parent C) ← 慢
db                              ████                   30ms  (span F, parent E)
  • 瀑布图:Jaeger UI 把 trace 树画成甘特图,每条 span 一行,长度 = duration,缩进 = 层级。
  • 瓶颈定位:最长 span(如 payment 180ms)是优化重点。
  • 并行/串行:从瀑布图可看出子 span 是并行(如 D 和 E 同时开始)还是串行。
  • 错误传播error=true 的 span 标红,一眼看到哪个 span 出错。

三、上下文传播:traceID 跨服务

traceID 必须随请求跨服务传递,靠 context propagation:

W3C Trace Context(标准,OTel 默认)

请求 header:
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
             │  ├────────── traceID ──────────┤├──── spanID ────┤├flags┤
             版本                              当前 span ID       sampled=1
  • traceparent 头:W3C 标准,含 traceID + 当前 spanID + sampled 标志(是否采样)。
  • tracestate 头(可选):厂商特定扩展。

Jaeger / Zipkin 格式(兼容)

uber-trace-id: <traceID>:<spanID>:<parentSpanID>:<flags>     (Jaeger)
X-B3-TraceId: <traceID>; X-B3-SpanId: <spanID>; ...          (Zipkin B3)
  • 传播媒介:HTTP header、gRPC metadata、Kafka message headers、AMQP 属性——任何能带元数据的通道。

跨服务流程

gateway(入口):创建 trace,traceID=abc, spanID=A
  → 发 HTTP 给 user-svc,header 带 traceparent: 00-abc-A-01
user-svc:读 header,得知 traceID=abc, parent=A
  → 创建自己的 span B(spanID=B, parentSpanID=A)
  → 发请求给 order-svc,header 带 traceparent: 00-abc-B-01
order-svc:读 header,得知 traceID=abc, parent=B
  → 创建 span C(spanID=C, parentSpanID=B)
  ...

四、OpenTelemetry SDK 埋点与导出

自动埋点(instrumentation library)

js
// Node.js(@opentelemetry/node + auto-instrumentations)
const { NodeSDK } = require('@opentelemetry/sdk-node');
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http');
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');

const sdk = new NodeSDK({
  traceExporter: new OTLPTraceExporter({
    url: 'http://otel-collector:4318/v1/traces',  // 或直接 Jaeger OTLP
  }),
  instrumentations: [getNodeAutoInstrumentations()],  // 自动埋点 HTTP/Express/DB/Kafka
});
sdk.start();
// 自动给所有 HTTP 请求、DB 查询、Kafka 消息创建 span,无需改业务代码

手动埋点(业务关键逻辑)

js
const { trace } = require('@opentelemetry/api');
const tracer = trace.getTracer('my-app');

async function processOrder(orderId) {
  // 手动创建 span(业务关键逻辑)
  const span = tracer.startSpan('processOrder', { attributes: { 'order.id': orderId } });
  try {
    await doWork();
    span.setAttribute('result', 'success');
  } catch (err) {
    span.recordException(err);            // 记录异常到 span
    span.setStatus({ code: 2, message: err.message });  // 标 error
    throw err;
  } finally {
    span.end();                           // 必须手动 end(记录 duration)
  }
}

OTel Collector(中转)

SDK → OTel Collector → Jaeger / Tempo / Datadog / ES

         ├─ 接收(OTLP/Zipkin/Jaeger 协议)
         ├─ 处理(batch 批量、filter 过滤、attributes 改写、tail sampling 尾部采样)
         └─ 导出(多后端,解耦 SDK 与后端)
  • 价值:SDK 只发 Collector,后端切换/多导出/批量/尾部采样都在 Collector 做——降低 SDK 耦合。
  • 尾部采样在 Collector 实现:等请求结束,按条件(error? latency > 1s?)决定是否采,保证采到错误与慢请求。

五、Jaeger 后端架构

Jaeger 部署的组件(可全部署或部分):

应用 SDK(OTel)──OTLP──> Jaeger Collector ──> Storage(Cassandra/ES)

Jaeger UI <──> Jaeger Query <───┘

                (可选)Jaeger Agent(sidecar,每机器一个,转发给 Collector)
  • Collector:接收 SDK/Agent 数据,处理(去重/采样校验),写 Storage。可水平扩展。
  • Storage:Cassandra(大规模推荐,可扩展)/ Elasticsearch(支持复杂查询)/ 内存(测试用)。
  • Query:查询 API,从 Storage 读 trace,返回给 UI。
  • UI:Jaeger 自带的瀑布图可视化界面。
  • Agent(可选):每机器一个 sidecar,接收本地 SDK 数据批量转发给 Collector——减少 Collector 直连数(新版可省去,SDK 直发 Collector)。

六、OTLP 协议:OTel 原生

Jaeger 1.35+ 原生支持 OTLP(OpenTelemetry Protocol):

  • OTLP/HTTPhttp://jaeger:4318/v1/traces,SDK 用 OTLP HTTP exporter。
  • OTLP/gRPCjaeger:4317,SDK 用 OTLP gRPC exporter(更高效)。
  • 优势:OTel SDK 直接导出到 Jaeger,无需 Collector 中转(小规模);大规模仍推荐经 Collector。

下一步

掌握了 trace/span 模型与 OTel 集成后,下一步看采样与对比——头部 vs 尾部采样、概率/速率策略、Zipkin 并入 OTel、与 Tempo 的检索/存储取舍。