分布式链路追踪实战——Jaeger vs Zipkin vs SkyWalking 选型与接入
分布式链路追踪实战——Jaeger vs Zipkin vs SkyWalking 选型与接入
适读人群:需要选型链路追踪方案的工程师 | 阅读时长:约19分钟 | 核心价值:三款主流工具横向对比,结合真实踩坑经验给出选型建议
我在三家不同公司用过这三个工具,每家都有自己的故事。
第一家公司用的是 Zipkin,那时候 2017 年,可选的工具不多。我们用了两年多,总体还好,但有一次排查一个跨 10 个服务的请求问题,Zipkin 的 UI 在 Span 数量超过 200 的时候渲染慢到无法使用,最后是靠 Kafka 日志手工关联才找到问题。
第二家公司用的是 SkyWalking,是大厂选型,接入很方便,UI 功能比 Zipkin 丰富很多,还有服务拓扑图。但某个版本升级之后,agent 和我们用的 Spring Boot 版本有兼容性问题,一个服务的启动时间从 8 秒变成了 45 秒,排查了一周。
第三家公司我主导选型,选了 Jaeger,和 OpenTelemetry 配合使用,到目前为止这是我用着最顺手的组合。
这篇文章把这三款工具做一个横向比较,给你选型时参考。
三款工具概览
Zipkin
Zipkin 是 Twitter 开源的,2012 年发布,是分布式追踪领域的开山鼻祖之一。
优势:
- 成熟稳定,文档丰富
- Spring Boot 原生支持(
spring-cloud-sleuth/Micrometer Tracing) - 部署简单,一个 jar 就能跑
劣势:
- UI 功能相对简单
- 原生只支持追踪,不支持指标
- 大数据量下 UI 性能一般
适合场景:中小规模,已经用 Spring Cloud 全家桶,追求最小化运维成本。
Jaeger
Jaeger 是 Uber 开源的,CNCF 毕业项目,与 OpenTelemetry 深度集成。
优势:
- CNCF 生态,与 K8s/OTel 无缝集成
- UI 体验好,支持 Service Performance Monitoring(SPM)
- 支持多种存储后端(内存、Cassandra、Elasticsearch、ClickHouse)
- Adaptive Sampling(自适应采样)功能强大
劣势:
- 生产部署相对复杂(需要考虑存储选型)
- 学习曲线比 Zipkin 稍高
适合场景:云原生架构,使用 OpenTelemetry,需要长期数据存储和高级分析功能。
SkyWalking
SkyWalking 是国内团队(吴晟主导)贡献给 Apache 的,在国内非常流行。
优势:
- 国内社区活跃,中文文档好
- UI 功能最丰富(服务拓扑、性能分析、日志关联)
- 支持多种语言(Java/Python/Node/Go/PHP)
- 内置服务网格(Service Mesh)支持
劣势:
- 组件较重,运维成本高
- OTel 集成不如 Jaeger 原生
- Agent 版本和框架版本的兼容性问题比较多
适合场景:国内中大型团队,需要完整的 APM 功能,有专门的运维团队。
Jaeger 生产部署
存储选型
Jaeger 支持多种存储后端,选型建议:
| 存储 | 适合规模 | 优缺点 |
|---|---|---|
| 内存 | 开发测试 | 重启丢数据,不用于生产 |
| Badger | 单节点小规模 | 嵌入式,运维简单 |
| Elasticsearch | 中大规模 | 查询灵活,需要运维 ES |
| ClickHouse | 大规模 | 查询极快,社区方案 |
| Cassandra | 大规模 | 写入性能好,查询相对弱 |
我的建议:ES 集群已有的,用 ES;没有的,中小规模用 Badger(单机)或者直接用 Jaeger All-in-one;大规模上 ClickHouse 的性价比最高。
Docker Compose 部署(Jaeger + ES)
version: '3.8'
services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.11.1
environment:
- discovery.type=single-node
- xpack.security.enabled=false
- ES_JAVA_OPTS=-Xms2g -Xmx2g
volumes:
- esdata:/usr/share/elasticsearch/data
ulimits:
memlock:
soft: -1
hard: -1
jaeger:
image: jaegertracing/all-in-one:1.53
environment:
- SPAN_STORAGE_TYPE=elasticsearch
- ES_SERVER_URLS=http://elasticsearch:9200
- LOG_LEVEL=info
ports:
- "16686:16686" # Jaeger UI
- "4317:4317" # OTLP gRPC
- "4318:4318" # OTLP HTTP
depends_on:
- elasticsearch
volumes:
esdata:Kubernetes 部署推荐用 Jaeger Operator:
kubectl create namespace observability
kubectl apply -f https://github.com/jaegertracing/jaeger-operator/releases/latest/download/jaeger-operator.yaml -n observability然后创建 Jaeger 实例:
apiVersion: jaegertracing.io/v1
kind: Jaeger
metadata:
name: production-jaeger
namespace: observability
spec:
strategy: production
storage:
type: elasticsearch
options:
es:
server-urls: http://elasticsearch:9200
index-prefix: jaeger-prod
ingress:
enabled: true
hosts:
- jaeger.company.internal
collector:
replicas: 3 # 多副本保证高可用
resources:
limits:
memory: "2Gi"
cpu: "1"
query:
replicas: 2SkyWalking 接入:Java Agent 配置
SkyWalking 提供独立的 Java Agent,下载解压后:
# 下载
wget https://downloads.apache.org/skywalking/java-agent/9.2.0/apache-skywalking-java-agent-9.2.0.tgz
tar -xzf apache-skywalking-java-agent-9.2.0.tgz
# 启动时加上
java -javaagent:/opt/skywalking-agent/skywalking-agent.jar \
-Dskywalking.agent.service_name=payment-service \
-Dskywalking.collector.backend_service=oap-server:11800 \
-jar payment-service.jarSkyWalking 的配置文件在 skywalking-agent/config/agent.config,也可以通过环境变量覆盖(SW_AGENT_NAME、SW_AGENT_COLLECTOR_BACKEND_SERVICES)。
自定义 Span(SkyWalking 注解):
import org.apache.skywalking.apm.toolkit.trace.Trace;
import org.apache.skywalking.apm.toolkit.trace.ActiveSpan;
import org.apache.skywalking.apm.toolkit.trace.Tag;
@Service
public class PaymentService {
@Trace(operationName = "payment.processOrder")
@Tag(key = "payment.method", value = "arg[0]") // 第一个参数作为 tag
@Tag(key = "payment.amount", value = "arg[1]") // 第二个参数作为 tag
public PaymentResult processOrder(String method, BigDecimal amount) {
// 手动添加 tag
ActiveSpan.tag("order.currency", "CNY");
// 手动记录异常
try {
return doProcess(method, amount);
} catch (Exception e) {
ActiveSpan.error(e);
throw e;
}
}
}踩坑实录
踩坑一:Zipkin 的 clock skew 问题
在分布式系统里,不同服务器的系统时钟可能有偏差(几秒到几十毫秒不等)。Zipkin 在渲染 Trace 时,如果子 Span 的开始时间比父 Span 早(因为时钟偏差),会出现负的 duration,Trace 图渲染异常。
解决方法:
- 确保所有服务器都在用 NTP 同步时间(
chronyc tracking检查) - Zipkin 4 开始对 clock skew 有一定容错处理
- 治本方案:用 Jaeger,它对时钟偏差的处理更鲁棒
踩坑二:SkyWalking Agent 与某些 Spring Boot 版本不兼容
SkyWalking 8.x Agent 对 Spring Boot 3.x 的支持不是很完整,我们升级 Spring Boot 到 3.1 之后,SkyWalking agent 因为对 Spring 6 的反射访问兼容性问题,导致应用启动时大量 InaccessibleObjectException。
临时解决方案是在 JVM 参数里加了一堆 --add-opens,但这不是长久之计。最后是升级到 SkyWalking 9.x 才彻底解决。
教训:在升级框架版本之前,一定要先确认 APM Agent 的兼容性。
最安全的是先在测试环境验证 agent + 新框架版本的组合,没问题再推生产。
踩坑三:Jaeger 的 ES 存储索引膨胀
Jaeger 默认每天会在 ES 里创建新的索引(jaeger-span-2024-01-15 这样的格式),如果没有配置 ILM(Index Lifecycle Management),这些索引会一直堆积,ES 集群的分片数量越来越多,最终影响 ES 性能。
解决方案:配置 Jaeger 的数据保留和 ES ILM:
# Jaeger Operator 配置里加上 ES ILM
spec:
storage:
type: elasticsearch
esIndexCleaner:
enabled: true
numberOfDays: 30 # 保留 30 天数据
schedule: "55 23 * * *" # 每天 23:55 清理
options:
es:
use-ilm: true
ilm-policy-name: jaeger-ilm-policy三款工具的横向对比
把关键维度列出来做一个综合对比,方便选型时参考:
| 维度 | Zipkin | Jaeger | SkyWalking |
|---|---|---|---|
| 开源协议 | Apache 2.0 | Apache 2.0 | Apache 2.0 |
| 社区 | Twitter 主导 | CNCF 毕业 | Apache TLP |
| 接入复杂度 | 低 | 中 | 中 |
| Spring Boot 集成 | 原生 | 通过 OTel | 独立 Agent |
| UI 丰富度 | 基础 | 中等 | 丰富 |
| 存储后端 | Cassandra/ES/MySQL | 多种 | H2/MySQL/ES/TiDB |
| 多语言支持 | Java/Go/Python等 | 通过 OTel 全覆盖 | Java/Python/Node/Go/PHP |
| 服务拓扑 | 无 | 有 | 非常丰富 |
| OTel 支持 | 社区维护 | 官方支持 | 有但不如 Jaeger 原生 |
| 国内社区 | 一般 | 一般 | 非常活跃 |
采样策略:生产环境不能全量采样
这是三款工具都面临的共同问题——如何设置采样率。
全量采样的问题:
- 存储成本随 QPS 线性增长
- 高 QPS 下 Trace 上报会影响业务性能(额外的网络 I/O)
- 大量重复的成功请求 Trace 价值极低
纯随机采样的问题:
- 低频但重要的请求(比如支付)可能刚好没被采样到
- 出了问题,可能恰好找不到对应的 Trace
生产环境推荐的混合采样策略:
# Jaeger Collector 的采样配置
sampling:
strategies-file: /etc/jaeger/sampling.json{
"service_strategies": [
{
"service": "payment-service",
"type": "probabilistic",
"param": 1.0, // 支付服务 100% 采样
"operation_strategies": [
{
"operation": "/health",
"type": "probabilistic",
"param": 0.001 // 健康检查接口只采 0.1%
}
]
},
{
"service": "*",
"type": "probabilistic",
"param": 0.05 // 其他服务默认 5%
}
]
}基于错误的采样(Tail-based Sampling):
只保留包含错误的 Trace,大幅降低存储成本同时保留所有问题现场:
# OTel Collector 的 tail sampling processor
processors:
tail_sampling:
decision_wait: 10s
policies:
- name: keep-errors
type: status_code
status_code:
status_codes: [ERROR]
- name: keep-slow-traces
type: latency
latency:
threshold_ms: 2000 # 超过 2 秒的请求全部保留
- name: probabilistic-sample
type: probabilistic
probabilistic:
sampling_percentage: 5 # 其余 5% 随机采样从 Zipkin 迁移到 Jaeger 的实践
如果你当前用 Zipkin,想迁移到 Jaeger,这是我实践过的迁移路径:
阶段一:并行运行
Jaeger 支持接收 Zipkin 格式的数据(通过 Zipkin-compatible endpoint)。在 Jaeger Collector 开启 Zipkin 接收:
# Jaeger Collector 配置
collector:
zipkin:
host-port: :9411 # 监听 Zipkin 格式的数据现有应用继续发 Zipkin 格式,数据同时流入 Jaeger,观察两个系统的数据是否一致。
阶段二:切换 Exporter
确认数据一致后,应用侧把 Exporter 从 Zipkin 改为 OTel(OTLP 格式):
# Spring Boot application.yml
management:
tracing:
sampling:
probability: 0.1
otlp:
tracing:
endpoint: http://jaeger:4317阶段三:下线 Zipkin
所有服务切换完毕,历史数据根据 Zipkin 的保留策略自然过期后,下线 Zipkin。
这个迁移过程可以做到对应用完全无感知的平滑迁移,零停机。
我的选型建议
综合用过这三款工具的经验,我的建议是:
新项目/绿地项目:选 Jaeger + OpenTelemetry。
- 与 K8s、Prometheus、Grafana 的生态集成最自然
- OpenTelemetry 保证了厂商中立
- Jaeger UI 足够用,如果需要更丰富的 UI 可以配合 Grafana Tempo
Spring Cloud 体系的老项目:选 Zipkin。
- Spring Boot Actuator 内置支持,接入成本最低
- 不想折腾 OTel 就直接用 Micrometer Tracing + Zipkin
- 有迁移需求时参考上面的迁移路径
国内中大型团队,需要完整 APM:选 SkyWalking。
- 国内社区好,找问题有人讨论
- 服务拓扑图对于管理层汇报很有价值
- 但一定要匹配好 agent 版本和框架版本
- 升级框架版本前一定要先验证 agent 兼容性
不管选哪个,都强烈建议:在应用层面只依赖 OpenTelemetry API,不直接依赖 Jaeger/Zipkin/SkyWalking 的 SDK。这样未来切换后端只改配置,不改代码。
深度解析:分布式追踪改变了什么
在分布式追踪普及之前,微服务的问题排查是这样进行的:拿到报错时间戳,SSH 到第一个服务查日志,找到相关的请求 ID,再 SSH 到第二个服务查日志,然后第三个服务……如果调用链有五六层,要在多台机器上来回翻日志,每次都要先确定时间范围,再用 grep 过滤,手动把这些碎片拼成一个完整的故事。这个过程耗时不说,对"同时在很多地方调了什么"的全局视角是完全缺失的。
分布式追踪改变了这一切。有了 Trace,你看到的是一张完整的调用图:请求进来,经过 API Gateway,到 Order Service,调用 Payment Service,Payment Service 去查数据库,调用第三方支付接口……每个步骤的耗时都清晰可见,哪个步骤最慢一目了然,哪个服务返回了错误立刻就能定位。
这种"全局视角"是分布式追踪最核心的价值,也是它相对于日志的最大差异:日志是碎片化的,追踪是整体性的。
追踪数据如何建立因果关系
TraceId 和 SpanId 的传播是分布式追踪的技术基础。每个请求在入口处生成一个全局唯一的 TraceId,这个 ID 随着请求在服务间流转——通过 HTTP Header(traceparent)、消息队列的 message attribute、RPC 的 metadata 传递下去。下游服务收到这个 TraceId,知道自己是这个追踪链路的一部分,产生的 Span 都归属于同一个 Trace。
这个设计建立了分布式系统中的因果关系:你可以从一个 TraceId 找到整条调用链上所有服务的所有操作,就像在分布式的执行路径上打了一根贯穿的线。这根线是分布式系统调试的命根子——没有它,你对系统的理解是局部的;有了它,系统的行为对你是透明的。
追踪数据的性能开销
很多人担心追踪会影响服务性能。实际测量来看,开销主要来自两个地方:Span 的创建(内存分配)和数据上报(网络 I/O)。
创建 Span 的开销在微秒级,对于大多数服务来说可以忽略不计。数据上报是异步的,不在请求的关键路径上,通常也不会影响请求延迟。
真正的性能问题来自"追踪太多无用的东西"——比如对每一次 Redis get 操作都创建 Span,高频服务下这会产生海量数据,既占存储,上报的 goroutine/thread 也会消耗一定 CPU。解决方案是合理设置追踪粒度:高层的业务操作(HTTP 请求、消息处理)一定要有 Span,低层的细碎操作(频繁的缓存读取)可以只追踪慢的或出错的。
深度解析:Jaeger vs SkyWalking 的团队适配
上面已经从功能角度比较了 Jaeger 和 SkyWalking,但选型还有一个重要维度:团队背景和运营能力。
运维成本的差异
Jaeger 在存储上比较灵活:可以用 Cassandra、Elasticsearch,或者更轻量的 Badger(内嵌存储,适合单机开发环境)。Jaeger 本身的组件比较清晰:Collector(接收数据)、Query(提供 UI)、Storage(存储后端)。如果你的团队已经有 Elasticsearch,Jaeger + ES 是非常自然的组合,ELK 团队可以同时运维 Jaeger 存储。
SkyWalking 更"重"一些,有自己的 OAP(Observability Analysis Platform)服务器,这个服务器做数据聚合和分析,自带 UI,功能更丰富。但这也意味着需要多运维一套 SkyWalking OAP 服务。SkyWalking 支持 MySQL、H2、TiDB、Elasticsearch 等多种存储,在国内互联网公司里有很广泛的使用。
中小团队的实际建议
对于中小团队(20 人以下的研发团队),我的建议是优先选 Jaeger。理由是:Jaeger 的 UI 更简洁,核心概念更少,团队成员上手更快。SkyWalking 的功能虽然更丰富,但很多高级功能(服务网格监控、日志关联)需要额外配置才能发挥作用,对于初次建立追踪体系的团队来说是额外的学习曲线。
对于有 Java 技术栈为主、追求"开箱即用"的团队,SkyWalking 的 Java Agent 是个诱人的选择——不改代码就能追踪几乎所有的 Java 框架(Spring MVC、MyBatis、Redis、Kafka……),技术迁移成本极低。
如果团队有时间和精力,值得在测试环境两个都跑一周,对比 UI 体验和接入难度,用实际感受来做选择,而不只是看文档对比。
总结
链路追踪这件事,关键不在于选哪个工具,而在于真的用起来。
很多团队接入了追踪工具,但从来不看 Trace,出了问题还是看日志——那接入了也是白搭。
好的追踪实践应该是:出了问题,第一件事就是打开追踪系统,找到对应请求的 Trace,沿着调用链找到耗时最长或最先报错的那个 Span,再结合日志深挖。这个习惯建立起来,排查时间能砍一半以上。
另外,追踪工具的价值不只是排查问题。定期看 P95/P99 最高的 Trace,可以发现隐藏的性能瓶颈。SkyWalking 的服务拓扑图在做容量规划时也非常有价值。把追踪工具当日常工具用起来,而不只是救火的时候才想起来。工具建立了,文化也要跟上,这才是分布式追踪真正发挥价值的前提。技术债和知识债一样难还——追踪系统搭好了,但工程师习惯不改变,工具只是占用资源的空摆设。培养"先看 Trace,再看日志"的问题排查习惯,是分布式追踪投资真正兑现的关键一步。这个习惯的养成,需要 Tech Lead 在每次事故复盘时带头示范,用实际案例证明 Trace 的价值,而不是空喊口号。当整个团队都习惯了"遇到问题先看 Trace"之后,你会发现排查时间的压缩是实实在在的,而这个习惯本身,比任何工具升级都更有价值。分布式系统的复杂度在增加,追踪是目前已知的应对这种复杂度最有效的工具,投入建立这个体系是值得的。
