OpenTelemetry 分布式追踪实战:Jaeger 部署、链路分析与性能调优
系统讲解分布式追踪的原理与实战:OpenTelemetry SDK 埋点(Go/Java/Python)、Jaeger 分布式追踪系统部署与配置、Trace/Span/Context 核心概念、W3C TraceContext 传播标准、采样策略配置(尾部采样/概率采样)、通过 Jaeger UI 分析慢请求和依赖关系图,以及 OTEL Collector 收集转发配置。
微服务环境中,一个请求可能经过 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。
