技术博客

OpenTelemetry 分布式追踪实战:Jaeger 部署、链路分析与性能调优

系统讲解分布式追踪的原理与实战:OpenTelemetry SDK 埋点(Go/Java/Python)、Jaeger 分布式追踪系统部署与配置、Trace/Span/Context 核心概念、W3C TraceContext 传播标准、采样策略配置(尾部采样/概率采样)、通过 Jaeger UI 分析慢请求和依赖关系图,以及 OTEL Collector 收集转发配置。

OpenTelemetryJaeger分布式追踪可观测性OTEL链路追踪

微服务环境中,一个请求可能经过 10+ 个服务。当出现慢请求或错误时,日志和指标很难定位是哪个服务、哪个环节出了问题。分布式追踪(Distributed Tracing)给每个请求打上唯一 Trace ID,记录完整调用链,让你能看到“请求 X 在服务 A 花了 200ms,在服务 B 花了 800ms(这里是瓶颈)”。

分布式追踪核心概念

Trace(追踪):
  一次完整请求的全过程,由唯一 Trace ID 标识
  包含多个 Span

Span(跨度):
  Trace 中的一个操作单元
  包含:操作名、开始/结束时间、状态、属性(attributes)、事件(events)
  Span 之间有父子关系,构成树状结构

Context Propagation(上下文传播):
  Trace 信息跨服务传递的机制
  通过 HTTP Header(W3C TraceContext 标准)或消息队列 Header 传播
  格式:traceparent: 00-{trace-id}-{parent-span-id}-{flags}

采样(Sampling):
  不是每个请求都追踪(开销太大)
  头部采样:请求进入时决定是否采样(简单,但错误可能没采到)
  尾部采样:请求完成后决定是否保留(更智能,能保留慢请求和错误)

一、Jaeger 部署

开发环境(All-in-One)

# Docker 快速启动
docker run -d --name jaeger \
  -p 16686:16686 \   # Jaeger UI
  -p 4317:4317 \     # OTLP gRPC 接收端点
  -p 4318:4318 \     # OTLP HTTP 接收端点
  -p 6831:6831/udp \ # Jaeger Agent(UDP,Thrift 协议,旧版)
  jaegertracing/all-in-one:1.62.0

# 访问 UI
open http://localhost:16686

生产部署(Kubernetes + Elasticsearch 存储)

# jaeger 生产部署(使用 Jaeger Operator 简化)
# 1. 安装 Jaeger Operator
kubectl create namespace observability
kubectl apply -f https://github.com/jaegertracing/jaeger-operator/releases/download/v1.62.0/jaeger-operator.yaml -n observability

# 2. 创建 Jaeger 实例(生产配置)
cat << 'EOF' | kubectl apply -f -
apiVersion: jaegertracing.io/v1
kind: Jaeger
metadata:
  name: jaeger-production
  namespace: observability
spec:
  strategy: production     # production 模式:独立部署各组件
  
  collector:
    replicas: 2
    resources:
      limits:
        cpu: 2
        memory: 2Gi
    options:
      collector:
        queue-size: 2000
        num-workers: 50
  
  query:
    replicas: 2
    options:
      query:
        base-path: /jaeger
  
  storage:
    type: elasticsearch
    elasticsearch:
      server-urls: https://elasticsearch:9200
      index-prefix: jaeger
      username: elastic
      password: secret
      tls:
        ca: /es-tls/ca.crt
    secretName: jaeger-secret
    
  # 尾部采样配置(需要 OpenTelemetry Collector)
  sampling:
    options:
      default_strategy:
        type: probabilistic
        param: 0.1    # 采样 10%
      per_service_strategies:
        - service: payment-service
          type: probabilistic
          param: 1.0   # 关键服务采样 100%
        - service: health-check
          type: const
          param: 0     # 健康检查不采样
EOF

二、OpenTelemetry Collector 配置

# otel-collector-config.yaml
receivers:
  # 接收应用发来的追踪数据
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318
        cors:
          allowed_origins:
            - "https://*.company.com"
  
  # 接收 Prometheus 指标(多信号汇总)
  prometheus:
    config:
      scrape_configs:
        - job_name: 'otel-collector'
          static_configs:
            - targets: ['localhost:8888']

processors:
  # 批量处理(减少网络请求数)
  batch:
    timeout: 1s
    send_batch_size: 1024
    
  # 添加资源属性(集群信息)
  resource:
    attributes:
      - key: cluster
        value: "prod-cn-shanghai"
        action: insert
  
  # 尾部采样(关键!根据请求结果决定是否保留 Trace)
  tail_sampling:
    decision_wait: 10s      # 等 10 秒等所有 Span 到达后再决定
    policies:
      # 错误请求 100% 保留
      - name: errors-policy
        type: status_code
        status_code: {status_codes: [ERROR]}
      # 慢请求保留(超过 500ms)
      - name: slow-requests
        type: latency
        latency: {threshold_ms: 500}
      # 关键服务 100% 保留
      - name: critical-services
        type: string_attribute
        string_attribute:
          key: service.name
          values: ["payment-service", "auth-service"]
      # 其他按 1% 概率采样
      - name: default-policy
        type: probabilistic
        probabilistic: {sampling_percentage: 1}

exporters:
  # 发送到 Jaeger
  otlp/jaeger:
    endpoint: jaeger-collector:4317
    tls:
      insecure: true
  
  # 同时发送到 Prometheus(OTEL 指标)
  prometheusremotewrite:
    endpoint: http://prometheus:9090/api/v1/write
    
  # 日志输出(调试用)
  logging:
    verbosity: detailed

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [resource, batch, tail_sampling]
      exporters: [otlp/jaeger]
    
    metrics:
      receivers: [otlp, prometheus]
      processors: [resource, batch]
      exporters: [prometheusremotewrite]

三、应用程序埋点

Go 应用(使用 OTEL SDK)

// main.go - OpenTelemetry 初始化
package main

import (
    "context"
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
    "go.opentelemetry.io/otel/sdk/resource"
    sdktrace "go.opentelemetry.io/otel/sdk/trace"
    semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
)

func initTracer() func() {
    ctx := context.Background()
    
    // 连接 OTEL Collector
    exporter, _ := otlptracegrpc.New(ctx,
        otlptracegrpc.WithInsecure(),
        otlptracegrpc.WithEndpoint("otel-collector:4317"),
    )
    
    // 定义资源(服务信息)
    res, _ := resource.New(ctx,
        resource.WithAttributes(
            semconv.ServiceName("order-service"),
            semconv.ServiceVersion("v1.2.0"),
            semconv.DeploymentEnvironment("production"),
        ),
    )
    
    // 创建 TracerProvider
    tp := sdktrace.NewTracerProvider(
        sdktrace.WithBatcher(exporter),
        sdktrace.WithResource(res),
        sdktrace.WithSampler(
            sdktrace.ParentBased(sdktrace.TraceIDRatioBased(0.1)), // 10% 采样
        ),
    )
    otel.SetTracerProvider(tp)
    
    return func() { tp.Shutdown(ctx) }
}

// 业务代码中使用
func processOrder(ctx context.Context, orderID string) error {
    tracer := otel.Tracer("order-service")
    
    ctx, span := tracer.Start(ctx, "processOrder")
    defer span.End()
    
    // 添加属性
    span.SetAttributes(
        attribute.String("order.id", orderID),
        attribute.String("order.status", "processing"),
    )
    
    // 子 Span(调用数据库)
    ctx, dbSpan := tracer.Start(ctx, "db.query")
    // ... 执行数据库查询
    dbSpan.End()
    
    // 添加事件
    span.AddEvent("order.validated", trace.WithAttributes(
        attribute.Bool("valid", true),
    ))
    
    return nil
}

// HTTP 中间件(自动传播 Trace Context)
func tracingMiddleware(next http.Handler) http.Handler {
    return otelhttp.NewHandler(next, "http-server",
        otelhttp.WithTracerProvider(otel.GetTracerProvider()),
    )
}

Java 应用(Zero-code Instrumentation)

# Java 应用不需要修改代码,只需要 Java Agent 自动埋点
# 下载 OTEL Java Agent
wget https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/v2.9.0/opentelemetry-javaagent.jar

# 启动应用时附加 Agent
java -javaagent:opentelemetry-javaagent.jar \
  -Dotel.service.name=payment-service \
  -Dotel.exporter.otlp.endpoint=http://otel-collector:4317 \
  -Dotel.traces.sampler=parentbased_traceidratio \
  -Dotel.traces.sampler.arg=0.1 \
  -jar payment-service.jar

# Kubernetes 中通过 OTEL Operator 自动注入(不需要改 Dockerfile)
# 安装 OTEL Operator 后,给 Deployment 添加注解:
# annotations:
#   instrumentation.opentelemetry.io/inject-java: "true"

四、Jaeger UI 使用技巧

# 查找慢请求
# 1. 打开 http://jaeger:16686
# 2. Service 选择 api-gateway
# 3. 设置 Min Duration 为 500ms
# 4. 点击 Find Traces → 看到超过 500ms 的请求

# 分析 Trace 详情
# 点击一条 Trace → 看到瀑布图:
# - 横轴:时间(绝对/相对)
# - 每行:一个 Span(服务/操作)
# - 颜色:不同服务
# - 宽度:该操作耗时
# - 红色 Span:错误

# 找到瓶颈的技巧:
# 1. 找最宽的 Span(耗时最长)
# 2. 看 Span 的嵌套关系(子 Span 总耗时 < 父 Span → 差值是父 Span 自身耗时)
# 3. 点击 Span 看 Tags 和 Logs(有详细错误信息)

# 服务依赖图(System Architecture)
# Jaeger UI → System Architecture → DAG(有向无环图)
# 展示服务间调用关系和调用频率
# 用 OTEL 生成的指标做 RED 监控(结合 Prometheus)
# Request Rate
rate(http_server_duration_count{job="api-gateway"}[5m])

# Error Rate  
rate(http_server_duration_count{job="api-gateway", http_response_status_code=~"5.."}[5m])
/
rate(http_server_duration_count{job="api-gateway"}[5m])

# Duration (P99 Latency)
histogram_quantile(0.99, 
  rate(http_server_duration_bucket{job="api-gateway"}[5m])
)

五、采样策略选择

头部采样(Head-based Sampling):
  在请求进入时决定是否采样
  优点:开销最小(不采样的请求不产生任何追踪数据)
  缺点:可能漏掉错误请求(随机决定时可能没选中)
  适合:高流量、错误率低的服务

尾部采样(Tail-based Sampling):
  等请求完成后决定是否保留
  优点:可以智能保留错误和慢请求(100% 捕获问题请求)
  缺点:需要临时缓存所有请求数据(内存开销大)
  适合:需要保证问题请求被采样

混合策略(推荐):
  OTEL Collector 做尾部采样:
    - 错误请求:100% 保留
    - 慢请求(> P95):100% 保留
    - 关键服务:概率采样 10%
    - 其他:概率采样 1%

小结

分布式追踪的核心价值是跨服务的请求链路可视化:当用户说“下单很慢”,不用再猜是哪个服务的问题,直接打开 Jaeger 搜索慢请求的 Trace,瀑布图一目了然。部署路径:OTEL SDK 埋点(或 Java Agent 零代码)→ OTEL Collector(统一接收、处理、尾部采样)→ Jaeger(存储和 UI)。采样策略是关键:生产环境不能全量采样(开销太大),但必须保证错误和慢请求被采到,所以推荐 OTEL Collector 的尾部采样策略,智能保留有价值的 Trace。