Skip to content

Jaeger / Zipkin — 分布式链路追踪

分布式追踪基础

核心概念

Trace(链路):一次完整的请求链路,由多个 Span 组成
  └── Span(跨度):一次操作的时间段
        ├── SpanContext:Trace ID + Span ID(跨进程传播)
        ├── Tags:键值对标签(可索引)
        ├── Logs:时间点事件
        └── References:父子关系

示例:
Trace ID: abc123
  Span: frontend.handleRequest (0ms ~ 150ms)
    └── Span: order-service.createOrder (5ms ~ 120ms)
          ├── Span: db.insert (10ms ~ 30ms)
          └── Span: payment-service.charge (40ms ~ 110ms)
                └── Span: db.update (45ms ~ 60ms)

传播协议

HTTP Header 传播(W3C TraceContext 标准):
  traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
               版本  TraceID(16字节)              SpanID(8字节)    标志

B3 格式(Zipkin):
  X-B3-TraceId: 4bf92f3577b34da6a3ce929d0e0e4736
  X-B3-SpanId: 00f067aa0ba902b7
  X-B3-Sampled: 1

Jaeger 架构

应用(Jaeger Client SDK)
  └──► Jaeger Agent(UDP,本地收集)
              └──► Jaeger Collector(处理、存储)
                          └──► Elasticsearch / Cassandra(存储)
                                      └──► Jaeger Query(查询 API)
                                                  └──► Jaeger UI(可视化)

部署(All-in-One,开发环境)

bash
docker run -d --name jaeger \
  -e COLLECTOR_ZIPKIN_HOST_PORT=:9411 \
  -p 5775:5775/udp \
  -p 6831:6831/udp \
  -p 6832:6832/udp \
  -p 5778:5778 \
  -p 16686:16686 \  # Jaeger UI
  -p 14268:14268 \  # HTTP Collector
  -p 14250:14250 \  # gRPC Collector
  -p 9411:9411 \    # Zipkin 兼容
  jaegertracing/all-in-one:latest

Spring Boot 集成(OpenTelemetry)

xml
<dependency>
    <groupId>io.opentelemetry.instrumentation</groupId>
    <artifactId>opentelemetry-spring-boot-starter</artifactId>
    <version>2.1.0-alpha</version>
</dependency>
yaml
# application.yml
otel:
  service:
    name: order-service
  exporter:
    otlp:
      endpoint: http://jaeger:4317  # OTLP gRPC
  traces:
    sampler:
      arg: "0.1"  # 采样率 10%
      type: parentbased_traceidratio
  propagators: tracecontext,baggage,b3

手动创建 Span

java
@Autowired
private Tracer tracer;

public Order createOrder(OrderRequest request) {
    Span span = tracer.spanBuilder("createOrder")
        .setAttribute("order.user_id", request.getUserId())
        .setAttribute("order.amount", request.getAmount())
        .startSpan();
    
    try (Scope scope = span.makeCurrent()) {
        // 业务逻辑
        Order order = orderRepository.save(buildOrder(request));
        
        // 添加事件
        span.addEvent("order.saved", Attributes.of(
            AttributeKey.stringKey("order.id"), order.getId()
        ));
        
        // 调用支付服务(自动传播 TraceContext)
        paymentService.charge(order);
        
        span.setStatus(StatusCode.OK);
        return order;
        
    } catch (Exception e) {
        span.setStatus(StatusCode.ERROR, e.getMessage());
        span.recordException(e);
        throw e;
    } finally {
        span.end();
    }
}

跨服务传播

java
// RestTemplate 自动传播(通过 OTEL 自动插桩)
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
    return new RestTemplate();
}

// Feign 自动传播
@FeignClient(name = "payment-service")
public interface PaymentClient {
    @PostMapping("/api/payments")
    PaymentResult charge(@RequestBody ChargeRequest request);
}

// Kafka 消息传播
@KafkaListener(topics = "order-events")
public void handleOrderEvent(ConsumerRecord<String, String> record) {
    // OTEL 自动从 Kafka Header 提取 TraceContext
    // 当前 Span 自动关联到生产者的 Trace
}

Zipkin 架构

应用(Brave/OTEL SDK)
  └──► Zipkin Server(收集、存储、查询)
              └──► MySQL / Elasticsearch / Cassandra

Spring Cloud Sleuth + Zipkin(旧方案)

xml
<!-- Spring Boot 2.x 方案(3.x 已迁移到 OTEL) -->
<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>
yaml
spring:
  zipkin:
    base-url: http://zipkin:9411
  sleuth:
    sampler:
      probability: 0.1  # 10% 采样

采样策略

java
// 头部采样(在入口处决定是否采样)
// 优点:简单,性能好
// 缺点:可能错过低频错误

// 尾部采样(收集完整 Trace 后决定是否保留)
// 优点:可以保留所有错误 Trace
// 缺点:需要缓存完整 Trace,内存开销大

// 推荐策略:
// - 正常请求:1%~10% 采样
// - 错误请求:100% 采样
// - 慢请求(>1s):100% 采样
yaml
# OpenTelemetry Collector 尾部采样配置
processors:
  tail_sampling:
    decision_wait: 10s
    policies:
      - name: errors-policy
        type: status_code
        status_code: {status_codes: [ERROR]}
      - name: slow-traces-policy
        type: latency
        latency: {threshold_ms: 1000}
      - name: probabilistic-policy
        type: probabilistic
        probabilistic: {sampling_percentage: 5}

故障处理案例

案例一:Trace 断链

现象:在 Jaeger UI 中,Trace 在某个服务处断开,无法看到完整链路。

排查

java
// 检查 TraceContext 是否正确传播
// 在断链处的服务日志中查找 trace_id
// 确认 HTTP Header 是否包含 traceparent

// 常见原因:
// 1. 使用了自定义 HTTP 客户端,未集成 OTEL
// 2. 异步线程切换时 Context 丢失

解决

java
// 异步场景:手动传播 Context
ExecutorService executor = Context.taskWrapping(Executors.newFixedThreadPool(10));

// 或手动传播
Context context = Context.current();
executor.submit(() -> {
    try (Scope scope = context.makeCurrent()) {
        // 异步操作
    }
});

案例二:Jaeger 存储空间不足

现象:Jaeger 开始丢弃 Span,存储告警。

解决

yaml
# 配置 Span 保留时间
# Elasticsearch 后端
PUT _ilm/policy/jaeger-policy
{
  "policy": {
    "phases": {
      "delete": {
        "min_age": "7d",
        "actions": { "delete": {} }
      }
    }
  }
}

案例三:高采样率导致性能下降

现象:开启 100% 采样后,应用 CPU 和内存使用率明显上升。

解决

  • 降低采样率(生产环境 1%~10% 足够)
  • 使用异步 Exporter(避免阻塞业务线程)
  • 使用 OTEL Collector 作为中间层,减少应用侧压力

Jaeger vs Zipkin 对比

维度JaegerZipkin
开发者UberTwitter
存储后端ES/Cassandra/BadgerMySQL/ES/Cassandra
UI功能丰富简洁
协议Jaeger/Zipkin/OTLPZipkin/OTLP
性能分析支持基础
依赖图支持支持
适用场景大规模,功能需求多轻量,快速上手

PaaS 中间件生态系统深度学习文档