Traces 栈与采样:Jaeger、Tempo、Zipkin、SkyWalking
你在 Jaeger UI 里搜
duration > 500ms,点了
Search,然后去倒了杯咖啡。回来的时候,查询还在转圈。这不是
Jaeger 实现不好——是你让 Jaeger(背后是
Elasticsearch)在几十亿个 Span
上执行了一个全索引范围扫描。你对 Jaeger 说”给我所有慢于
500ms 的 Span”,Jaeger 去问 ES,ES 去扫所有分片——然后你等了
2 分钟。
这就是分布式追踪系统的核心矛盾:Trace 数据的写入量是 Metrics 的几十到几百倍,但查询需求不像 Logs 那样必须支持全文搜索——却也不是简单的 key-value 查找能完全满足的。不同追踪系统对这个矛盾的不同解法,定义了它们的架构灵魂。
一、分布式追踪的演化简史
Google 在 2010 年发表了 Dapper 论文(Sigelman et al.),描述了一个大规模分布式追踪基础设施的架构。Dapper 的核心概念至今仍在沿用:Trace 是 Span 的有向无环图、采样在请求入口决策、Span 携带 application-level annotation。但 Dapper 没有开源——它启发了一整个时代的克隆。
2012 年 Twitter 开源了 Zipkin,第一个广泛使用的开源分布式追踪系统。2015 年 Uber 开源了 Jaeger,基于 Go 重写,内置了自适应采样和 Elasticsearch/Cassandra 存储后端。2019 年 OpenTracing 和 OpenCensus 合并为 OpenTelemetry——从此分布式追踪的 API 和 SDK 被统一,竞争从”用谁的 SDK”转向”用谁的后端”。2021 年 Grafana 发布 Tempo——一个完全不同的设计哲学(不索引 Span attribute),引发了一场关于”Trace 存储到底该不该建索引”的持续讨论。同年,Apache SkyWalking 从中国社区走向全球,成为 Apache 顶级项目。
二、Span 与 Trace:数据结构与传播
分布式追踪的数据模型是建立在两个抽象上的:Span(一个操作单元)和 Trace(一组有向 Span 的树或 DAG)。SpanContext 是跨服务传播的载体——它包含四个字段:
trace_id(16 bytes):全局唯一的 trace 标识符。Jaeger、Tempo、OTel 都用这个 ID 做 sharding key。span_id(8 bytes):当前 Span 的标识符。trace_flags(1 byte):最低 bit 表示”是否被采样”(sampledflag)。这是头部采样决策的传播机制。tracestate(variable):vendor-specific 的扩展字段(AWSx-amzn-trace-id、GoogleX-Cloud-Trace-Context)。
W3C TraceContext 标准(2021 年 W3C Recommendation)定义了
HTTP header
格式:traceparent: 00-{trace_id}-{span_id}-{trace_flags}。这个标准是所有传播实现的基础——任何不遵循这个格式的传播都是兼容性问题。
三、Jaeger:全索引路线的旗舰
Jaeger 的架构由 5 个核心组件构成:
- Agent(已废弃,由 OTel Collector 替代):在宿主机上作为 sidecar 或 DaemonSet 运行,接收 UDP/Thrift 格式的 Span,批量转发给 Collector。
- Collector:接收 Span → 验证 → 处理(采样、索引) → 写入存储后端。
- Query:提供 Jaeger UI 的搜索 API。
- Ingester:从 Kafka 消费 Span 写入存储——用于流量削峰。
- Storage Backends:Cassandra(大容量耐用)、Elasticsearch(方便搜索)、Badger(本地嵌入式)、内存(开发环境)。
Jaeger 对 Span 的核心处理是”把每个 Span 打散写入
Elasticsearch
的索引”——trace_id、operation_name、start_time、duration、tags(attributes)、process(resource
attributes)——全部映射为 ES
的字段并建倒排索引。这让你可以秒级搜索”所有
service=checkout 且
http.status_code=500 的 Span”。
代价也很明确:ES 的存储放大(倒排索引通常是原始数据的 1.5–3 倍)加上 Jaeger 对每个 Span 的多维度索引——在 5000 QPS、每个请求 10 个 Span 的场景下,日增 ES 数据可达数百 GB。Jaeger + ES 的月存储成本通常是同等 Tempo 方案的 5–10 倍。
四、Tempo:无索引路线
Tempo 的设计哲学是”Trace 数据最大的特征是按 trace_id
查找——所以只索引 trace_id 就够了”。它把 Span 按
trace_id 分组 → 在 Ingester 内存中暂存 → WAL →
满即 flush 为 Parquet Block → 上传 S3/GCS/Azure。
查询 trace_id = X:通过 consistent hash ring
定位 → 如果 trace 还在 WAL 从 Ingester 返回,如果在 Block
从对象存储读取 Parquet row group →
解压返回。全程不需要查询任何索引——因为 trace_id
本身就是定位 key。
Tempo 的 attribute 搜索靠
TraceQL:{duration > 500ms && status = error}。TraceQL
的实现是并发扫描对象存储中的 Block,对每个 Block
做过滤——依赖 parquet 的列存特性(只读需要的列)和 predicate
pushdown。在单日数亿 Span 的规模下,搜索延迟通常在 3–15
秒——不是 ES 的毫秒级,但通常够用。
Tempo 的存储成本优势是压倒性的(大约是 Jaeger + ES 的 1/10 到 1/20),但代价是:别期望毫秒级 attribute 搜索;别把 Span attribute 当 SQL WHERE clause 用;如果你的团队每天都在问”上周四 user_id=xxx 的请求有没有报过错”,Tempo 可能不适合(Jaeger + ES 或 Datadog 的全文搜索更适合)。
五、SkyWalking:中国开源的 APM 级方案
Apache SkyWalking 由吴晟于 2015 年创建。它不是”Trace backend”——而是”Application Performance Monitoring 平台”:自动的字节码增强(Java Agent 通过 ByteBuddy 注入埋点,无需改代码)、服务拓扑图、端点依赖、数据库慢查询监控、JVM 指标——这些在 Jaeger/Tempo 中都是缺失的能力。
SkyWalking 的存储支持 ES、H2、MySQL、TiDB、PostgreSQL、BanyanDB(自研)。它使用 ES 做 Trace 存储时同样面临索引膨胀问题,但 SkyWalking 的社区提供了更细粒度的索引控制——可以按 endpoint 和错误状态选择性索引。
SkyWalking 的优势在国内环境特别明显:中文文档、中文社区、Apache 顶级项目的信任背书、以及”开箱即用”的全功能 APM 体验。劣势是在全球范围内不如 Jaeger/Tempo 与 OTel 生态的集成深度——SkyWalking 有 OTel 兼容但它的原生协议不是 OTLP。
六、采样策略
Traces 必须采样——全量 Trace 的量级是 Metrics 的 50–100 倍。采样策略三种:
头部采样:在第一个 Span 创建时按
trace_id hash
决策。简单零开销但决策不可逆——如果一条错误落在 99%
被丢弃的区域,它的 Trace
永远回不来。这是为什么生产环境绝对不应该只用头部采样。
尾部采样:等所有 Span 到达 Collector 后基于完整信息(status_code、duration、attribute)决策。保证所有错误和慢请求不丢失——但需要 Span Buffer(内存开销)。标准生产配置:头部 1% baseline + 尾部全量保留所有 status=ERROR 和 duration > 阈值 的 Trace。
自适应采样:根据实时错误率和延迟分布自动调整采样率。Jaeger v2 实现了这个能力,但在反馈延迟(几十秒到几分钟)上仍有天然限制。
七、选型决策
| 条件 | 推荐 | 理由 |
|---|---|---|
| 团队 < 20 人,自建 LGTM 栈 | Tempo | 与 Grafana/Loki/Mimir 统一运维,存储成本极低 |
| 已有 ES 集群,需要全文搜索 Span | Jaeger + ES | 利用已有 ES 运维经验,毫秒级 attribute 搜索 |
| 国内团队,要 APM 全功能 | SkyWalking | 字节码增强、服务拓扑、中文社区 |
| Trace 数据量极大(> 10B span/day) | Tempo | 只有 Tempo 能在这个量级下保持合理成本 |
| 需要灵活采样和多后端 fan-out | OTel Collector → 任选后端 | Collector 统一接收→处理→路由,后端可插拔 |
八、关键概念回顾
- Jaeger:全索引路线(ES/Cassandra),毫秒级 attribute 搜索,存储成本高。
- Tempo:无索引路线(对象存储),只索引 trace_id + 时间,存储极省,attribute 搜索靠 TraceQL 扫描。
- SkyWalking:中国开源 APM 平台,字节码增强、服务拓扑、开箱即用。
- 采样:头部简单不可逆,尾部保错误但不保 baseline,自适应优雅但有延迟。标准组合:头部 1% + 尾部全部错误+慢请求。
九、工程坑点
跨服务 traceparent 丢失:消息队列不自动传播 trace context → 在 producer 手动注入 traceparent 到 message header,consumer 手动恢复。
Span 时钟偏差:不同节点 NTP 不同步 → 子 Span 看起来晚于父 Span → Jaeger UI 报 “clock skew”。强制全集群 NTP 同步,允许 < 100ms 偏差。
ES 热索引分片爆炸:Jaeger 按天建 index 但分片数不合理 → ES 集群挂掉。根据日均 Span 数量合理设置 index 分片(每个分片 < 50GB)。
十、下一步
Traces 栈选好之后,我们进入可观测性最深的一层——内核追踪。下一篇 内核追踪:ftrace、kprobe、uprobe、tracepoint 生产实战。
上一篇:日志管道:Fluent Bit、Vector、Logstash、Cribl 的取舍
下一篇:内核追踪:ftrace、kprobe、uprobe、tracepoint 生产实战
参考资料
- B. H. Sigelman et al., Dapper, a Large-Scale Distributed Systems Tracing Infrastructure, Google Technical Report, 2010
- W3C, Trace Context, W3C Recommendation, 2021, 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, Documentation, https://skywalking.apache.org/docs/
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【系统架构设计】分布式追踪:OpenTelemetry 与全链路可观测
分布式追踪的采样率设多少?100% 采样的成本和收益分别是什么?本文从 Google Dapper 论文的 Trace/Span 模型出发,拆解 W3C Trace Context 标准的传播机制,深入 OpenTelemetry SDK、Collector、Exporter 三层架构,对比 Jaeger 与 Tempo 的存储设计差异,讨论头部采样、尾部采样与自适应采样的工程取舍,结合 Uber 迁移 OpenTelemetry 的实战经验,给出追踪数据驱动的自动拓扑发现与关键路径分析方法。
【可观测性工程】埋点哲学:粒度、采样、基数爆炸与成本模型
埋点不是多加几行日志,而是一整套关于什么该记、什么该采样、什么该丢弃的工程决策体系。从信号分层、基数控制、采样策略到落地规范与工程坑点,给出可操作的埋点治理框架。
【可观测性工程】数据模型:时间序列、日志、Span、Profile 的内部表达
拆解 Metrics、Logs、Traces、Profiles、Events 五大支柱在磁盘和内存中的内部数据模型。理解为什么 Loki 比 ES 省 5-10 倍存储、Tempo 为什么不索引 Span attribute、火焰图的本质是栈合并。
【可观测性工程】存储与成本:采样、下采样、冷热分层、对象存储
可观测性数据量以每年 2-3 倍的速度增长,存储成本很快就超过计算成本。拆解五大支柱的成本结构、采样是最大的杠杆、冷热分层与压缩的实战策略,以及降本路径图。