---
title: 分布式追踪
url: https://doc.liz6.com/distributed-systems/08-observability/01-distributed-tracing
locale: zh
area: distributed-systems
tags:
- distributed-systems
- 可观测性
date: 2026-06-30
modified: 2026-07-11
description: 一次请求跨越十几个微服务后,怎么知道时间花在了哪?分布式追踪给每个请求分配一个 trace ID,跨服务的调用关系通过 context propagation 传递 span context(trace ID + parent span ID + 采样标志),把散落在各节点的片段拼成完整调用链。采样策略是成本控制的关…
---

# 分布式追踪

> 一次请求跨越十几个微服务后,怎么知道时间花在了哪?分布式追踪给每个请求分配一个 trace ID,跨服务的调用关系通过 context propagation 传递 span context(trace ID + parent span ID + 采样标志),把散落在各节点的片段拼成完整调用链。采样策略是成本控制的关键:全量采太贵,固定比例采会漏掉长尾错误,tail-based 采样在完成后再决定保留哪些 trace。

## Dapper (Google 2010) → OpenTelemetry

Google 的 Dapper 论文启发了开源生态：Zipkin (Twitter) → Jaeger (Uber) → OpenTracing + OpenCensus → **OpenTelemetry** (CNCF, 当前标准)。

## Trace 与 Span

<svg viewBox="0 0 720 406" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="Trace 与 Span 结构:一个 Trace 由多个 Span 组成,每个 Span 记录若干核心字段">
  <rect width="720" height="406" fill="#ffffff"/>
  <text x="360" y="26" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">Trace 与 Span 结构:一个 Trace 由多个 Span 组成</text>
  <rect x="50" y="38" width="620" height="38" rx="8" fill="#4f46e5"/>
  <text x="64" y="61" font-size="12.5" fill="#ffffff"><tspan font-weight="700">Trace</tspan> — 一次端到端请求的所有 spans 的集合</text>
  <line x1="70" y1="76" x2="70" y2="101" stroke="#94a3b8" stroke-width="1.6"/>
  <line x1="70" y1="101" x2="90" y2="101" stroke="#94a3b8" stroke-width="1.6"/>
  <rect x="90" y="84" width="580" height="34" rx="8" fill="#0d9488"/>
  <text x="104" y="105" font-size="12.5" fill="#ffffff"><tspan font-weight="700">Span</tspan> — 一次 RPC 调用的记录,含以下 7 个字段</text>
  <line x1="110" y1="118" x2="110" y2="327" stroke="#94a3b8" stroke-width="1.6"/>
  <line x1="110" y1="147" x2="130" y2="147" stroke="#94a3b8" stroke-width="1.6"/>
  <rect x="130" y="134" width="540" height="26" rx="5" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="142" y="151" font-size="12" font-weight="700" fill="#115e59">trace_id</text>
  <text x="260" y="151" font-size="11" fill="#475569">trace 的唯一 ID</text>
  <line x1="110" y1="177" x2="130" y2="177" stroke="#94a3b8" stroke-width="1.6"/>
  <rect x="130" y="164" width="540" height="26" rx="5" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="142" y="181" font-size="12" font-weight="700" fill="#115e59">span_id</text>
  <text x="260" y="181" font-size="11" fill="#475569">本 span 的唯一 ID</text>
  <line x1="110" y1="207" x2="130" y2="207" stroke="#94a3b8" stroke-width="1.6"/>
  <rect x="130" y="194" width="540" height="26" rx="5" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="142" y="211" font-size="12" font-weight="700" fill="#115e59">parent_id</text>
  <text x="260" y="211" font-size="11" fill="#475569">caller span 的 ID(root span 无 parent)</text>
  <line x1="110" y1="237" x2="130" y2="237" stroke="#94a3b8" stroke-width="1.6"/>
  <rect x="130" y="224" width="540" height="26" rx="5" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="142" y="241" font-size="12" font-weight="700" fill="#115e59">operation</text>
  <text x="260" y="241" font-size="11" fill="#475569">"GET /api/users" / "SELECT * FROM users"</text>
  <line x1="110" y1="267" x2="130" y2="267" stroke="#94a3b8" stroke-width="1.6"/>
  <rect x="130" y="254" width="540" height="26" rx="5" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="142" y="271" font-size="12" font-weight="700" fill="#115e59">start/end</text>
  <text x="260" y="271" font-size="11" fill="#475569">时间戳</text>
  <line x1="110" y1="297" x2="130" y2="297" stroke="#94a3b8" stroke-width="1.6"/>
  <rect x="130" y="284" width="540" height="26" rx="5" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="142" y="301" font-size="12" font-weight="700" fill="#115e59">status</text>
  <text x="260" y="301" font-size="11" fill="#475569">OK / ERROR(含错误详情)</text>
  <line x1="110" y1="327" x2="130" y2="327" stroke="#94a3b8" stroke-width="1.6"/>
  <rect x="130" y="314" width="540" height="26" rx="5" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="142" y="331" font-size="12" font-weight="700" fill="#115e59">attributes</text>
  <text x="260" y="331" font-size="11" fill="#475569">自定义 tags(http.status_code, db.statement, ...)</text>
  <rect x="50" y="352" width="620" height="40" rx="8" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="64" y="368" font-size="12" fill="#3730a3">关键结构:parent_id 把散落在各服务节点的 span 拼回一棵树——</text>
  <text x="64" y="384" font-size="12" fill="#3730a3">root span 无 parent,子 span 都记录 caller 的 span_id。</text>
</svg>

<svg viewBox="0 0 720 370" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="Span 调用层级树:一次 /api/order 请求的父子结构">
  <rect width="720" height="370" fill="#ffffff"/>
  <text x="360" y="26" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">Span 调用层级树:一次 /api/order 请求的父子结构</text>
  <rect x="50" y="40" width="620" height="30" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="64" y="60" font-size="12.5" font-weight="700" fill="#3730a3">Trace: user request → /api/order</text>
  <line x1="70" y1="70" x2="70" y2="102" stroke="#94a3b8" stroke-width="1.6"/>
  <line x1="70" y1="102" x2="90" y2="102" stroke="#94a3b8" stroke-width="1.6"/>
  <rect x="90" y="82" width="560" height="40" rx="8" fill="#4f46e5"/>
  <text x="104" y="100" font-size="12.5" font-weight="700" fill="#ffffff">Span A · GET /api/order</text>
  <text x="104" y="115" font-size="10.5" fill="#e0e7ff">root span(无 parent)</text>
  <line x1="110" y1="122" x2="110" y2="212" stroke="#94a3b8" stroke-width="1.6"/>
  <line x1="110" y1="158" x2="150" y2="158" stroke="#94a3b8" stroke-width="1.6"/>
  <rect x="150" y="138" width="500" height="40" rx="8" fill="#e0e7ff" stroke="#c7d2fe"/>
  <text x="164" y="156" font-size="12" font-weight="700" fill="#3730a3">Span B · auth-service.ValidateToken</text>
  <text x="164" y="171" font-size="10.5" fill="#4f46e5">校验用户 token</text>
  <line x1="110" y1="212" x2="150" y2="212" stroke="#94a3b8" stroke-width="1.6"/>
  <rect x="150" y="192" width="500" height="40" rx="8" fill="#e0e7ff" stroke="#c7d2fe"/>
  <text x="164" y="210" font-size="12" font-weight="700" fill="#3730a3">Span C · order-service.CreateOrder</text>
  <text x="164" y="225" font-size="10.5" fill="#4f46e5">创建订单</text>
  <line x1="170" y1="232" x2="170" y2="268" stroke="#94a3b8" stroke-width="1.6"/>
  <line x1="170" y1="268" x2="210" y2="268" stroke="#94a3b8" stroke-width="1.6"/>
  <rect x="210" y="248" width="440" height="40" rx="8" fill="#0d9488"/>
  <text x="224" y="266" font-size="12" font-weight="700" fill="#ffffff">Span D · postgres: INSERT INTO orders</text>
  <text x="224" y="281" font-size="10.5" fill="#ccfbf1">实际落库操作(叶子 span)</text>
  <rect x="50" y="308" width="620" height="44" rx="8" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="64" y="328" font-size="12" fill="#115e59">parent_id 把 auth-service / order-service / postgres 的 span 拼回一棵树:</text>
  <text x="64" y="344" font-size="12" fill="#115e59">root span(A)无 parent,子 span 记录 caller 的 span_id,层级由此确定。</text>
</svg>

## Context Propagation

跨服务的 trace 传递是关键——每个服务必须知道"我是哪个 trace 的哪个 span":

```
HTTP: traceparent: 00-<trace_id>-<span_id>-<trace_flags>
W3C Trace Context (标准): traceparent header
gRPC: metadata (grpc-trace-bin)
Kafka: message headers
```

`trace_flags`: bit 0 = SAMPLED (是否被采样), 其他 bits reserved。

内部实现：tracer 在调用下游服务时，创建新的 span (作为 caller span 的 child)，将 trace_id + new_span_id 放入 outbound headers。下游的中间件/库（HTTP client, gRPC stub, DB driver）在收到 response 时提取 trace context，自动创建 child span。

## Sampling

全量采集 → 开销不可接受（每条 span 需要 ~1μs overhead + 网络/存储成本）。需要采样策略：

- **Head-based (概率)**: 在 trace 开始时决定是否采样（随机 1/100 或 1/1000）。简单，但可能漏掉包含错误的 trace。
- **Tail-based (智能)**: 缓存所有 spans（本地 buffer），trace 完成后根据 result (ERROR? latency > SLO?) 决定是否保留。能保 error 不丢，但内存开销大。OpenTelemetry 的 `tailsampling` processor 实现此策略。

## Jaeger 部署 (参考)

```bash
# Agent (每 host 一个, 接收 app 通过 UDP 发来的 spans)
# Collector (中心, 从 agents 收 → 写入存储)
# Storage (Cassandra/Elasticsearch)
# Query (UI + API)
```

## 参考

- **论文**: "Dapper, a Large-Scale Distributed Systems Tracing Infrastructure" (Google, 2010)
- **OpenTelemetry**: opentelemetry.io/docs/specs/otel/trace
- **Jaeger**: jaegertracing.io

*Keywords: distributed tracing, Dapper, OpenTelemetry, Span, trace context propagation, W3C Trace Context, sampling*
