分布式追踪: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/ Jaegeruber-trace-id/ Zipkin B3)或 gRPC/Kafka metadata。下游读 context 创建 child span。 - W3C Trace Context(
traceparent: 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/HTTP:
http://jaeger:4318/v1/traces,SDK 用 OTLP HTTP exporter。 - OTLP/gRPC:
jaeger:4317,SDK 用 OTLP gRPC exporter(更高效)。 - 优势:OTel SDK 直接导出到 Jaeger,无需 Collector 中转(小规模);大规模仍推荐经 Collector。
下一步
掌握了 trace/span 模型与 OTel 集成后,下一步看采样与对比——头部 vs 尾部采样、概率/速率策略、Zipkin 并入 OTel、与 Tempo 的检索/存储取舍。