链路追踪(SkyWalking / Zipkin / Jaeger)
为什么需要链路追踪
在微服务架构中,一个用户请求往往需要跨越多个服务节点才能完成。传统的单体应用日志排查方式在这种场景下暴露出严重不足。
单体应用: 请求 → Controller → Service → DAO → DB
日志集中,一条请求一条链路
微服务: 请求 → API Gateway → 服务A → 服务B → 服务C → DB
↓ ↓ ↓
服务A日志 服务B日志 服务C日志
日志分散在不同节点,排查困难核心痛点:
- 调用链排查困难 — 请求跨多个服务,日志分散在不同机器上,人工串联费时费力
- 性能瓶颈定位 — 无法直观地看到哪个服务或哪个方法调用耗时最长
- 异常根因分析 — 一个请求失败,如何快速定位是哪个下游服务导致的
- 依赖关系梳理 — 微服务间调用关系复杂,缺少全局拓扑视图
分布式追踪核心概念
Trace、Span、SpanContext
| 概念 | 说明 | 类比 |
|---|---|---|
| Trace | 从请求入口到最终响应的完整调用链路,由一组 Span 组成 | 一棵调用树 |
| Span | Trace 中的一次远程调用或本地操作,包含名称、开始/结束时间、状态、标签等 | 树中的一个节点 |
| SpanContext | Span 的上下文信息(TraceId、SpanId、Baggage),在跨进程传播时传递给下游 | 节点的身份信息 |
Trace (TraceId = abc123)
├── Span: API Gateway /order/create [0ms → 150ms]
│ ├── Span: 服务A checkAuth [5ms → 20ms]
│ ├── Span: 服务B createOrder [25ms → 120ms]
│ │ ├── Span: 服务B validateStock [30ms → 60ms]
│ │ └── Span: 服务C deductStock [65ms → 110ms]
│ └── Span: 服务A sendNotification [125ms → 145ms]分布式上下文传播
跨进程传递 SpanContext 是关键,业界主要有两种传播协议:
W3C Trace Context(推荐,标准协议)
traceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
│ │ trace-id │ span-id │trace-flags
│ 版本号 采样标记| 字段 | 说明 |
|---|---|
version | 版本号,当前固定为 00 |
trace-id | 全局唯一的 Trace 标识(16 字节,32 位十六进制字符串) |
span-id | 当前 Span 的标识(8 字节,16 位十六进制字符串) |
trace-flags | 追踪标记,01 表示采样,00 表示未采样 |
B3 Propagation(Zipkin 协议)
X-B3-TraceId: 0af7651916cd43dd8448eb211c80319c
X-B3-SpanId: b7ad6b7169203331
X-B3-ParentSpanId: 002f5b1b8b2c8e1a
X-B3-Sampled: 1Baggage(自定义业务上下文)
tracestate: vendor1=value1,vendor2=value2tracestate 可以携带业务自定义的键值对,随调用链传递到所有下游服务,但需要注意避免传递过多数据。
采样策略
| 策略 | 原理 | 适用场景 |
|---|---|---|
| 头采样(Head-based) | 在请求入口决定是否采样,后续全链路遵循同一决策 | 简单直接,大多数场景适用 |
| 尾部采样(Tail-based) | 请求完成后根据条件(如错误、慢调用)决定是否保留 | 节省存储,聚焦异常链路 |
| 概率采样(Probabilistic) | 按固定比例(如 10%)随机采样 | 大规模集群下控制数据量 |
| 自适应采样(Adaptive) | 根据流量动态调整采样率,高流量时降采样,低流量时全采 | 兼顾数据完整性与存储成本 |
OpenTelemetry 规范
OpenTelemetry(简称 OTel)是 CNCF 孵化的可观测性标准规范,统一了链路追踪、指标、日志的数据采集 API 和 SDK,是当前业界公认的标准。
应用服务
│
├── OpenTelemetry SDK (API + SDK)
│ ├── Trace Provider → Span Processor → Span Exporter
│ ├── Meter Provider → Metric Reader → Metric Exporter
│ └── Logger Provider → Log Processor → Log Exporter
│
├── OTel Collector(可选,推荐部署)
│ ├── Receiver(接收 OTLP / Zipkin / Jaeger 等协议)
│ ├── Processor(过滤、采样、批处理)
│ └── Exporter(发送到后端存储)
│
└── Backend(SkyWalking / Zipkin / Jaeger / Prometheus / Loki 等)核心组件:
| 组件 | 说明 |
|---|---|
| OTel API | 定义 Trace、Span、Meter、Logger 等接口,与厂商无关 |
| OTel SDK | API 的实现,提供 SpanProcessor、Exporter、Sampler 等 |
| OTel Collector | 独立网关服务,接收、处理、转发遥测数据 |
| OTLP 协议 | OpenTelemetry 原生协议,支持 gRPC 和 HTTP 传输 |
统一 OpenTelemetry 后的优势:
- 一套 API 适配所有后端(切换后端只需修改 Exporter 配置)
- 多语言 SDK 保持一致的语义和行为
- 自动探针(Auto-instrumentation)支持 Java、Go、Python、Node.js 等主流语言
- 社区标准,避免了厂商锁定
SkyWalking 原理与实战
架构
┌─────────────────┐ gRPC/HTTP ┌──────────────────┐ 写入 ┌──────────────┐
│ SkyWalking │ ────────────────→ │ OAP Server │ ──────────→ │ 存储后端 │
│ Agent (Java) │ │ │ │ ES / MySQL │
│ 字节码增强 │ │ ① 接收 Agent 数据 │ │ BanyanDB │
└─────────────────┘ │ ② 聚合分析 │ └──────────────┘
│ ③ 告警计算 │
┌─────────────────┐ └──────┬───────────┘
│ 其他语言 Agent │ │
│ Go / Python / │ │ HTTP
│ Node.js 等 │ │
└─────────────────┘ ↓
┌──────────┐
│ UI │
│ Web 控制台 │
└──────────┘| 组件 | 角色 |
|---|---|
| Agent | 挂载在业务服务上的探针,负责采集链路和指标数据,通过 gRPC/HTTP 上报 |
| OAP Server | 核心分析引擎,接收数据、聚合计算、生成告警、管理集群 |
| UI | 可视化控制台,展示拓扑、调用链、性能分析、告警等 |
| 存储后端 | 支持 Elasticsearch、MySQL、BanyanDB(SkyWalking 自研)、TiDB |
无侵入 Agent 字节码增强
SkyWalking 的核心优势是 零代码侵入,利用 Java Agent 技术在 JVM 启动时拦截字节码:
JVM 启动参数: -javaagent:/path/to/skywalking-agent.jar
-Dskywalking.agent.service_name=my-service
-Dskywalking.collector.backend_service=oap:11800支持的框架和中间件(自动插桩):
| 类别 | 支持列表 |
|---|---|
| RPC 框架 | Dubbo、gRPC、Spring Cloud Feign、Apache HttpClient、OkHttp |
| Web 容器 | Tomcat、Jetty、Undertow、Spring WebFlux |
| 消息队列 | Kafka、RocketMQ、RabbitMQ、ActiveMQ |
| 数据库 | MySQL、PostgreSQL、Oracle、Elasticsearch SQL |
| 缓存 | Redis(Jedis、Lettuce、Redisson) |
| 其他 | JDBC 连接池、线程池、定时任务 |
服务拓扑图、性能分析与告警
服务拓扑图:
- 自动发现服务间的调用关系,动态生成拓扑图
- 通过节点颜色和连线粗细直观反映服务健康状态和调用量
- 支持实时刷新,点击节点可查看详情
性能分析:
Top 慢 Span 排行榜
┌── /order/create ── avg 320ms ── P99 1.2s ── 5 次/秒
│ └── 服务B:validateStock ── avg 200ms ── P99 800ms
│ └── 服务C:deductStock ── avg 100ms ── P99 500ms
└── /user/login ── avg 150ms ── P99 400ms告警规则(oap/config/alarm-settings.yml):
rules:
service_resp_time_rule:
metrics-name: service_resp_time
op: ">"
threshold: 1000
period: 10
count: 3
message: 服务 {name} 响应时间在过去 10 分钟内超过 1s 达 3 次
service_sla_rule:
metrics-name: service_sla
op: "<"
threshold: 8000
period: 10
count: 2
message: 服务 {name} SLA 在过去 10 分钟内低于 80%存储选型
| 存储 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Elasticsearch | 全文搜索强、成熟生态 | 资源消耗大、运维复杂 | 大规模生产环境 |
| MySQL / TiDB | 运维简单、关系型查询 | 性能上限低、不适合海量数据 | 中小规模、已有 MySQL 运维体系 |
| BanyanDB | SkyWalking 自研,写入性能极高、压缩率高 | 相对较新、生态不够成熟 | 大规模场景,追求极致写入性能 |
Zipkin 原理与实战
架构
应用服务(Brave 客户端)
│
├── Sender(HTTP / Kafka / Scribe)
│ ↓
├── Zipkin Collector
│ ↓
├── Storage(Elasticsearch / Cassandra / MySQL)
│ ↓
└── Zipkin UI| 组件 | 说明 |
|---|---|
| Brave | Zipkin 官方 Java 客户端,提供 Tracing API 和自动配置 |
| Collector | 接收 Span 数据的服务,支持 HTTP、Kafka、Scribe 三种传输方式 |
| Storage | 存储后端,支持 Elasticsearch、Cassandra、MySQL |
| UI | Web 控制台,支持按 TraceId 和条件搜索、Span 时间轴展示 |
Brave 客户端集成
<dependency>
<groupId>io.zipkin.brave</groupId>
<artifactId>brave-spring-beans</artifactId>
<version>5.16.0</version>
</dependency>@Configuration
public class TracingConfig {
@Bean
Tracing tracing() {
return Tracing.newBuilder()
.localServiceName("user-service")
.spanReporter(spanReporter())
.build();
}
@Bean
SpanReporter spanReporter() {
// 通过 HTTP 发送到 Zipkin
return AsyncReporter.create(
URLConnectionSender.create("http://zipkin:9411/api/v2/spans")
);
}
}数据传输方式
| 方式 | 优点 | 缺点 |
|---|---|---|
| HTTP | 配置简单、即开即用 | 性能较低,可能阻塞业务线程 |
| Kafka | 高吞吐、解耦 Collector | 需要额外部署 Kafka 集群 |
| Scribe | 兼容老系统日志收集 | 已逐渐被淘汰,不推荐新项目使用 |
与 Sleuth 的集成
Spring Cloud Sleuth 是 Spring 生态的链路追踪组件,早期与 Zipkin 深度集成:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>spring:
zipkin:
base-url: http://zipkin:9411
sender:
type: web # 可选 web / kafka / rabbit
sleuth:
sampler:
probability: 1.0 # 采样率,1.0 表示全量采集注意:Spring Cloud Sleuth 已进入维护模式,官方推荐迁移至 Micrometer Tracing(底层基于 OpenTelemetry),新项目应直接使用 OpenTelemetry。
Jaeger 原理与实战
架构
应用服务(Jaeger Client / OTel SDK)
│
├── UDP / gRPC
│ ↓
├── Jaeger Agent(可选,已逐步淘汰)
│ ↓
├── Jaeger Collector
│ ↓
├── Storage(Cassandra / Elasticsearch / Badger)
│ ↓
└── Jaeger Query + UI| 组件 | 说明 |
|---|---|
| Client | Jaeger 官方 SDK(已停止维护),推荐直接使用 OpenTelemetry SDK |
| Agent | 本地代理,Sidecar 模式部署,已逐步被 Collector 直连取代 |
| Collector | 接收和处理 Span 数据的服务 |
| Storage | 存储后端,支持 Cassandra、Elasticsearch、Badger |
| Query + UI | 查询服务和 Web 控制台 |
Uber 开源、CNCF 毕业
Jaeger 由 Uber 开源,2017 年捐赠给 CNCF,2019 年成为 CNCF 毕业项目,与 Kubernetes、Prometheus 等并列。目前 Jaeger 官方推荐使用 OpenTelemetry SDK 替代原生 Jaeger Client,Jaeger 自身作为后端存储和可视化平台。
gRPC 元数据传播
在 gRPC 场景下,SpanContext 通过 gRPC 的 metadata(类似 HTTP Header)传播:
import (
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
"google.golang.org/grpc"
)
func main() {
// 服务端拦截器
s := grpc.NewServer(
grpc.StatsHandler(otelgrpc.NewServerHandler()),
)
// 客户端拦截器
conn, _ := grpc.Dial("target:8080",
grpc.WithStatsHandler(otelgrpc.NewClientHandler()),
)
}自适应采样
Jaeger 支持远程自适应采样策略,通过 Collector 动态调整采样率:
# Jaeger Collector 配置
sampling:
strategies-file: /etc/jaeger/sampling_strategies.json{
"service_strategies": [
{
"service": "user-service",
"type": "probabilistic",
"param": 0.1
},
{
"service": "order-service",
"type": "rate_limiting",
"param": 100
}
],
"default_strategy": {
"type": "probabilistic",
"param": 0.01
}
}- probabilistic:按概率采样(如 10%)
- rate_limiting:按每秒固定请求数采样(如 100 req/s)
- remote:从 Collector 远程获取采样配置,支持运行时动态调整
存储后端
| 存储 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Cassandra | 高写入吞吐、水平扩展 | 运维复杂、查询能力弱 | 大规模生产,需要高写入性能 |
| Elasticsearch | 全文搜索、成熟生态 | 资源消耗大 | 中小规模,需要灵活查询 |
| Badger | 嵌入式 KV 存储、零运维 | 单机、不适合集群 | 开发测试环境,小规模部署 |
三大框架对比
| 维度 | SkyWalking | Zipkin | Jaeger |
|---|---|---|---|
| 侵入性 | 无侵入(Java Agent 字节码增强) | 低侵入(需引入 SDK/依赖) | 低侵入(推荐 OTel SDK) |
| 协议支持 | gRPC / HTTP(自研协议) | HTTP / Kafka / Scribe | gRPC / HTTP(Jaeger 原生 + OTLP) |
| 存储 | ES / MySQL / BanyanDB / TiDB | ES / Cassandra / MySQL | ES / Cassandra / Badger |
| UI 能力 | ★★★★★ 拓扑图、性能分析、告警、仪表盘 | ★★★ 时间轴、搜索,功能相对简单 | ★★★★ 时间轴、搜索、依赖图 |
| 告警能力 | ★★★★★ 内置告警引擎,支持丰富规则 | ★ 无内置告警 | ★ 无内置告警 |
| 多语言支持 | ★★★ 重点在 Java,其他语言较弱 | ★★★★ 多语言 SDK 完善 | ★★★★★ OTel SDK 覆盖所有主流语言 |
| 社区活跃度 | ★★★★ 亚太地区流行(尤其中国) | ★★★ 社区稳定但更新较慢 | ★★★★ CNCF 毕业项目,活跃 |
| 运维复杂度 | ★★★ 需部署 Agent + OAP + UI | ★★ Collector + Storage + UI | ★★★ Collector + Storage + UI |
| 与 K8s 集成 | ★★★ 支持但不如 Jaeger 原生 | ★★★ 标准部署 | ★★★★★ OpenShift 生态集成优秀 |
选型参考:
Java 技术栈,追求"零侵入" → SkyWalking(首选)
需要无代码修改的链路追踪,字节码增强自动采集
Spring Cloud 老项目 → Zipkin + Sleuth(如已集成则暂留)
平滑过渡,后续迁移到 OpenTelemetry
多语言、云原生、K8s 环境 → Jaeger + OpenTelemetry(标准方案)
标准 OTLP 协议,避免厂商锁定
不仅需要 Tracing,还需整合
指标和日志的全栈可观测性 → OpenTelemetry Collector + 任意后端
统一数据采集层,后端灵活选择与 ELK / Loki + Grafana 的联动
链路追踪的完整价值需要与日志和指标联动才能发挥。
日志关联 TraceId
通过在日志中打印 TraceId,实现从日志直接跳转到调用链:
# Logback 配置(MDC 注入 TraceId)
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d [%thread] %-5level [%X{traceId}] %logger{36} - %msg%n</pattern>
</encoder>
</appender>2026-07-08 10:30:15 [http-nio-8080] INFO [0af7651916cd43dd] c.u.s.OrderService - 创建订单成功与 ELK 联动
应用日志(含 TraceId)
│
↓ Filebeat / Logstash
│
↓ Elasticsearch
│
↓ Kibana 搜索 traceId: 0af7651916cd43dd
│ 点击 TraceId 跳转到 SkyWalking / Jaeger UI
│ 或者直接使用 Elastic APM(内置 Tracing)联动价值:
- 在 Kibana 中搜索到异常日志,拿到 TraceId,跳转到链路追踪系统查看完整调用链
- 在链路追踪系统中发现慢 Span,跳转到对应服务的日志,查看详细报错堆栈
与 Loki + Grafana 联动
应用日志(含 TraceId)→ Promtail → Loki
↓
链路数据 → Jaeger / Tempo → Grafana(数据源关联)
↑
指标数据 → Prometheus / VictoriaMetricsGrafana 支持通过 Derived fields 功能,从日志中自动提取 TraceId 并关联到链路追踪数据源:
# Grafana Loki 数据源配置
derivedFields:
- name: TraceId
matcherRegex: "traceId=(\\w+)"
url: "$${__value.raw}"
datasourceUid: "jaeger" # 关联 Jaeger 数据源完整可观测性架构:
┌──────────────────────┐
│ Grafana │
│ ┌────┐ ┌────┐ ┌────┐ │
│ │日志 │ │指标 │ │链路 │ │
│ └────┘ └────┘ └────┘ │
└────────┬─────────────┘
│
┌────────────────────┼────────────────────┐
│ │ │
↓ ↓ ↓
Loki Prometheus Jaeger/Tempo/
SkyWalking
↓ ↓ ↓
┌──────────────────────────────────────────┐
│ OpenTelemetry Collector │
│ Receiver ← OTLP / 日志 / 指标 / 链路 │
│ Processor ← 采样 / 过滤 / 批处理 │
│ Exporter → 分发到各后端 │
└──────────────────────────────────────────┘
↑
│
┌──────────────────────────────────────────┐
│ 应用服务(OTel SDK) │
│ Trace + Metrics + Logs │
└──────────────────────────────────────────┘推荐实践
- 统一 TraceId — 所有服务使用同一种 TraceId 生成和传播方式(推荐 W3C Trace Context)
- 日志打印 TraceId — 通过 MDC 将 TraceId 注入日志上下文,实现日志 ↔ 链路双向关联
- Grafana 作为统一入口 — 将日志(Loki)、指标(Prometheus)、链路(Jaeger/Tempo/SkyWalking)汇聚到 Grafana 一个平台
- OpenTelemetry Collector — 作为数据采集和转发层,避免后端锁定