Traces 栈与采样:Jaeger、Tempo、Zipkin、SkyWalking
用户在 Jaeger UI 搜
duration > 5s,等了两分钟——不是 Jaeger
慢,是 Elasticsearch 在数十亿 Span 上做范围扫描。Trace
写入量是 Metrics
的几十到几百倍;采样策略比后端选型更重要。
本文深入 Jaeger、Grafana Tempo、Apache SkyWalking 的存储模型与架构取舍,讲解 W3C TraceContext / B3 传播、OpenTelemetry Collector tail_sampling 配置,并用假设模型估算存储量。前置:数据模型、OpenTelemetry。
一、从 Dapper 到 OpenTelemetry
Google Dapper(2010)提出 trace tree + 采样;Zipkin(2012)、Jaeger(2017)、Tempo(2021)、SkyWalking(2015)演进。
OpenTelemetry(2019)统一 API/SDK/OTLP——竞争从 SDK 转向后端与采样。
二、Span、Trace 与 SpanContext
Trace:Span DAG
- SpanContext:trace_id(16B)、span_id(8B)、trace_flags、tracestate
- W3Ctraceparent:00-{trace_id}-{span_id}-{flags}计时:`start_time`/`end_time` 纳秒;子 span 开始不得早于父 span(时钟 skew 除外)。
三、Jaeger 深度拆解
组件:Collector(接收/采样/写存储)、Query(UI/API)、Ingester(Kafka 消费)。Agent 已废弃,由 OTel Collector 替代。
**存储后端**:
| 后端 | 模型 | 搜索 |
|------|------|------|
| Elasticsearch | 每 span 文档 + 倒排 | 毫秒级 tag 搜索 |
| Cassandra | trace_id 宽行 | 有限 |
| Badger | 嵌入式 KV | 开发 |
生产 ES mapping 必须预定义——动态 mapping + 高基数 tag = 集群事故。
```mermaid
flowchart LR
SDK[OTel SDK] --> Coll[OTel/Jaeger Collector]
Coll --> ES[(Elasticsearch)]
Query[Jaeger Query] --> ES
```
四、Grafana Tempo 深度拆解
哲学:只索引 trace_id + 时间 + 少量 resource;Span attribute 不建倒排。
路径:Distributor → Ingester(WAL)→ Parquet Block → S3 → Querier。
TraceQL:`{ duration > 500ms && status = error }` 并行扫描 block——秒级。
与 Grafana:Loki `trace_id` → Tempo;Exemplar → Tempo。
```mermaid
flowchart TB
D[Distributor] --> I[Ingester + WAL]
I --> S3[(Object Storage)]
Q[Querier] --> S3
QF[Query Frontend] --> Q
```
五、Apache SkyWalking 深度拆解
中国社区 APM 代表;Apache 顶级项目。
**差异**:
- Java Agent 字节码增强(ByteBuddy),少改代码
- Segment 模型 + OAP 分析层
- 服务拓扑、端点依赖、JVM/DB 监控开箱即用
- 存储:ES、BanyanDB、TiDB 等
vs Jaeger/Tempo:SkyWalking 是 **APM 平台**,不是纯 Trace 存储。
OTel 兼容在增长,但原生 Sw8 协议仍常见于国内存量系统。
六、Zipkin 与生态位
Zipkin 轻量、Thrift/JSON API;适合中小规模或 Kafka 缓冲架构。与 Jaeger 类似全索引 ES 路线,新项目更常 OTel Collector → Tempo/Jaeger v2。
七、采样:为什么必须采
假设:\(Q=10000\) req/s,每请求 10 spans,全量:
$$N_{day} = 10000 \times 86400 \times 10 = 8.64 \times 10^9 \text{ spans/day}$$
见 [数据模型 §4.4](../05-data-model/data-model.html) 存储公式——全量不可规模化。
八、头部采样(Head-based)
入口按 trace_id hash
决策;trace_flags sampled bit 传播。
优点:零缓冲、各节点本地决策。
缺点:**不可逆**——错误请求可能落在未采样分区。
Jaeger:`probabilistic`/`rateLimiting`/`remote` 策略。
九、尾部采样(Tail-based)
Collector 缓冲 trace 完整 span 集,按
status=ERROR 或 duration>阈值
保留。
优点:高价值 trace 不丢。
缺点:内存、延迟、需 `wait` 窗口。

十、OpenTelemetry tail_sampling 配置
yaml processors: tail_sampling: decision_wait: 10s num_traces: 100000 expected_new_traces_per_sec: 1000 policies: - name: errors type: status_code status_code: status_codes: [ERROR] - name: slow type: latency latency: threshold_ms: 2000 - name: baseline type: probabilistic probabilistic: sampling_percentage: 1
**组合策略**:errors OR slow OR 1% baseline——生产常见。
与 [埋点哲学](../04-instrumentation-philosophy/instrumentation-philosophy.html) 采样决策树对齐。
`decision_wait` 须大于服务 p99 尾延迟,否则 span 未到齐误判。
十一、传播协议
traceparent,
tracestate | 标准首选 | | B3 |
X-B3-TraceId, X-B3-SpanId,
X-B3-Sampled | Zipkin 遗留 | | Jaeger |
uber-trace-id | 老 Jaeger 客户端 | OTel:`OTEL_PROPAGATORS=tracecontext,baggage`(按需加 b3)
**翻车**:
- HTTP/2 header 小写;网关 strip trace headers
- Kafka/RabbitMQ 不自动传播——producer inject、consumer extract
- 跨语言 `trace_flags` 未转发 → 下游不记录
十二、Jaeger vs Tempo vs SkyWalking 选型
十三、工程坑点
- traceparent 在网关丢失
2. NTP skew → clock skew 警告
3. 循环内创建 span → ingester OOM
4. ES 按天 index 分片过大
5. tail_samplingnum_traces过小 → 决策队列丢
6. 头部 100% + 尾部重复保留 → 存储翻倍
7.service.name不一致 → 拓扑断裂
十四、落地清单
十五、关键概念回顾
- Jaeger:全索引,搜索强,存储贵
- Tempo:trace_id 索引,TraceQL 扫描,便宜
- SkyWalking:APM 全功能
- 采样:头部省资源不可逆;尾部保错误;组合策略为标准答案
十六、下一步
Traces 之后进入 OpenTelemetry 深入 或内核追踪 17-kernel-tracing。
参考资料
- B. H. Sigelman et al., Dapper, Google Technical Report, 2010
- W3C, Trace Context, https://www.w3.org/TR/trace-context/
- Jaeger Architecture, https://www.jaegertracing.io/docs/latest/architecture/
- Grafana Tempo Architecture, https://grafana.com/docs/tempo/latest/architecture/
- Apache SkyWalking, https://skywalking.apache.org/docs/
- OpenTelemetry Tail Sampling Processor, https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/tailsamplingprocessor
- OpenTelemetry Propagation, https://opentelemetry.io/docs/concepts/context-propagation/
上一篇:日志管道:Fluent Bit、Vector、Logstash、Cribl 的取舍
下一篇:内核追踪:ftrace、kprobe、uprobe、tracepoint 生产实战
读完这篇,下一步读什么
优先读同系列或同问题的下一篇,把单篇消费变成主题集群。
【可观测性工程】埋点哲学:粒度、采样、基数爆炸与成本模型
埋点不是多加几行日志,而是一整套关于什么该记、什么该采样、什么该丢弃的工程决策体系。从信号分层、基数控制、采样策略到落地规范与工程坑点,给出可操作的埋点治理框架。
【可观测性工程】OpenTelemetry 深入:SDK、Collector、语义约定与版本演进
从 OpenTracing 与 OpenCensus 合并到今天的 OTel v1 稳定版,梳理 SDK 生命周期、Collector 流水线、OTLP 协议与 Semantic Conventions 的工程意义,并结合阿里 ARMS、观测云、夜莺等国内实践,给出多租户与尾采样的落地建议。
【可观测性工程】数据模型:时间序列、日志、Span、Profile 的内部表达
拆解 Metrics、Logs、Traces、Profiles、Events 五大支柱在磁盘和内存中的内部数据模型。字段级对照 Prometheus TSDB block、Loki chunk、Tempo block,给出带假设的存储成本估算公式,并解释索引策略如何决定账单与查询延迟。
【可观测性工程】存储与成本:采样、下采样、冷热分层、对象存储
可观测性数据量持续增长,存储成本常超过计算成本。拆解四大支柱的成本结构、采样与保留期策略、冷热分层架构,以及带显式假设的成本估算 worksheet。