Jaeger

技术栈
工具链
微服务链路追踪分布式追踪可观测性CNCFOpenTelemetry

概览

Jaeger

Jaeger 是 Uber 开源的分布式链路追踪系统,是 CNCF 毕业项目,也是云原生可观测性三支柱之一(Metrics + Logging + Tracing)的重要组成部分。

是什么

Jaeger = Trace 采集 + 存储 + 查询 UI。它追踪一个请求在微服务系统中经过的所有服务,记录每一步的耗时、依赖关系和状态,帮助快速定位延迟瓶颈和故障点。

解决什么问题

  • 请求追踪:一个请求经过了哪些服务?每步花了多少时间?
  • 延迟分析:哪个服务/方法是瓶颈?
  • 依赖拓扑:服务之间的调用关系图
  • 根因定位:错误从哪个服务开始?错误上下文是什么?
  • 性能优化:对比不同实现的延迟分布

关键特性

  • OpenTelemetry 兼容:原生支持 OTLP 协议
  • 多种存储后端:Elasticsearch / Cassandra / Badger(内置)
  • 采样策略:固定概率 / 自适应 / 基于错误
  • 自适应采样:保证低流量服务不被漏采
  • 丰富的可视化:火焰图 / 依赖图 / 对比视图
  • 多语言 SDK:Java / Go / Python / Node.js / C++ / .NET

安装

Jaeger 安装与初始化

1. 环境准备

要求 说明
Docker All-in-One 模式推荐 Docker
存储后端 开发用 Badger(内存/文件);生产用 Elasticsearch 7.x+ / Cassandra 3.x+
端口 16686(UI)、4317(OTLP gRPC)、4318(OTLP HTTP)、14268(Jaeger HTTP)

2. 安装命令

方式一:Docker All-in-One(推荐开发)

# 内存存储(重启丢失)
docker run -d --name jaeger \
  -e COLLECTOR_OTLP_ENABLED=true \
  -p 16686:16686 \
  -p 4317:4317 \
  -p 4318:4318 \
  jaegertracing/all-in-one:1.57

# 文件持久化(Badger)
docker run -d --name jaeger \
  -e COLLECTOR_OTLP_ENABLED=true \
  -e SPAN_STORAGE_TYPE=badger \
  -e BADGER_EPHEMERAL=false \
  -e BADGER_DIRECTORY_VALUE=/badger/data \
  -e BADGER_DIRECTORY_KEY=/badger/key \
  -v $(pwd)/jaeger-data:/badger \
  -p 16686:16686 \
  -p 4317:4317 \
  -p 4318:4318 \
  jaegertracing/all-in-one:1.57

方式二:Docker Compose(Jaeger + Elasticsearch)

version: '3.8'
services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.17.0
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false
    ports:
      - "9200:9200"

  jaeger-collector:
    image: jaegertracing/jaeger-collector:1.57
    environment:
      - SPAN_STORAGE_TYPE=elasticsearch
      - ES_SERVER_URLS=http://elasticsearch:9200
    ports:
      - "4317:4317"
      - "4318:4318"
      - "14268:14268"

  jaeger-query:
    image: jaegertracing/jaeger-query:1.57
    environment:
      - SPAN_STORAGE_TYPE=elasticsearch
      - ES_SERVER_URLS=http://elasticsearch:9200
    ports:
      - "16686:16686"

方式三:Kubernetes(Jaeger Operator)

kubectl create namespace observability
kubectl apply -n observability -f https://github.com/jaegertracing/jaeger-operator/releases/download/v1.57.0/jaeger-operator.yaml

# 部署 Jaeger 实例
cat <;<EOF | kubectl apply -f -
apiVersion: jaegertracing.io/v1
kind: Jaeger
metadata:
  name: simplest
spec:
  strategy: allInOne
  allInOne:
    options:
      collector:
        otlp:
          enabled: "true"
EOF

验证安装

# 浏览器打开 UI
open http://localhost:16686

# 查看 OTLP 端点
curl http://localhost:4318/v1/traces
# 返回空 → 正常(等待 trace 上报)

# 查看健康状态
curl http://localhost:14269/

3. 常见安装问题

问题 解决方案
OTLP 连接被拒 确认启动时带 -e COLLECTOR_OTLP_ENABLED=true
ES 存储连接失败 检查 ES_SERVER_URLS 格式(完整 URL,含 http://)
UI 无数据 确认 SDK exporter 地址正确;检查采样率 JAEGER_SAMPLER_PARAM=1
内存占用过高 All-in-One 使用 Badger 内存模式;生产环境分离 Collector + Query
4317 vs 4318 4317 是 gRPC(推荐),4318 是 HTTP;根据 SDK exporter 选择

示例

目标

在 Python Flask 微服务中使用 OpenTelemetry SDK 自动采集 Trace,上报到 Jaeger,可视化请求链路和耗时分布。

完整代码

架构

Client → ServiceA(:5000) → ServiceB(:5001)
          ↓ OTLP              ↓ OTLP
         Jaeger (OTLP Collector :4317/4318)

1. 安装依赖

pip install flask requests \
  opentelemetry-api \
  opentelemetry-sdk \
  opentelemetry-exporter-otlp-proto-grpc \
  opentelemetry-instrumentation-flask \
  opentelemetry-instrumentation-requests

2. ServiceA(Flask + 调用 ServiceB)

# service_a.py
from flask import Flask, jsonify
import requests
import time
import random

# ===== OpenTelemetry 初始化 =====
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.resources import Resource
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.instrumentation.flask import FlaskInstrumentor
from opentelemetry.instrumentation.requests import RequestsInstrumentor

# 设置 Tracer Provider
resource = Resource.create({"service.name": "service-a"})
trace.set_tracer_provider(TracerProvider(resource=resource))

# 配置 OTLP Exporter(发送到 Jaeger)
otlp_exporter = OTLPSpanExporter(endpoint="http://localhost:4317", insecure=True)
trace.get_tracer_provider().add_span_processor(
    BatchSpanProcessor(otlp_exporter)
)

# 启动 Flask 插桩
app = Flask(__name__)
FlaskInstrumentor().instrument_app(app)
RequestsInstrumentor().instrument()

tracer = trace.get_tracer(__name__)


@app.route("/process")
def process():
    """ServiceA 入口:先做本地处理,再调用 ServiceB"""
    with tracer.start_as_current_span("local-computation") as span:
        span.set_attribute("user.id", 42)
        time.sleep(random.uniform(0.05, 0.15))  # 模拟本地计算

    # 调用 ServiceB
    with tracer.start_as_current_span("call-service-b") as span:
        span.set_attribute("peer.service", "service-b")
        try:
            resp = requests.get("http://localhost:5001/analyze", timeout=5)
            result = resp.json()
        except Exception as e:
            span.record_exception(e)
            span.set_status(trace.Status(trace.StatusCode.ERROR))
            result = {"error": str(e)}

    return jsonify({"service": "A", "result": result})


if __name__ == "__main__":
    app.run(port=5000, debug=False)

3. ServiceB(Flask)

# service_b.py
from flask import Flask, jsonify
import time
import random

from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.resources import Resource
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.instrumentation.flask import FlaskInstrumentor

resource = Resource.create({"service.name": "service-b"})
trace.set_tracer_provider(TracerProvider(resource=resource))
otlp_exporter = OTLPSpanExporter(endpoint="http://localhost:4317", insecure=True)
trace.get_tracer_provider().add_span_processor(BatchSpanProcessor(otlp_exporter))

app = Flask(__name__)
FlaskInstrumentor().instrument_app()
tracer = trace.get_tracer(__name__)


@app.route("/analyze")
def analyze():
    """ServiceB:模拟数据分析"""
    with tracer.start_as_current_span("db-query") as span:
        span.set_attribute("db.statement", "SELECT * FROM events WHERE id=42")
        time.sleep(random.uniform(0.1, 0.3))  # 模拟 DB 查询

    with tracer.start_as_current_span("ml-inference") as span:
        span.set_attribute("model.name", "recommender-v3")
        span.set_attribute("model.latency_ms", random.randint(200, 500))
        time.sleep(random.uniform(0.2, 0.5))  # 模拟 ML 推理

    return jsonify({"status": "completed", "score": round(random.random(), 2)})


if __name__ == "__main__":
    app.run(port=5001, debug=False)

运行步骤

# 1. 启动 Jaeger
docker run -d --name jaeger \
  -e COLLECTOR_OTLP_ENABLED=true \
  -p 16686:16686 -p 4317:4317 -p 4318:4318 \
  jaegertracing/all-in-one:1.57

# 2. 启动 ServiceB
python service_b.py &;

# 3. 启动 ServiceA
python service_a.py &;

# 4. 发送请求
curl http://localhost:5000/process

# 5. 打开 Jaeger UI
open http://localhost:16686

# 6. 选择 Service: service-a → Find Traces

预期输出

// curl 响应
{
  "service": "A",
  "result": {
    "status": "completed",
    "score": 0.87
  }
}

Jaeger UI 中看到的 Trace

service-a.process
├── local-computation    (120ms)    ← 自定义 Span
├── call-service-b       (450ms)    ← 自定义 Span
│   └── service-b.analyze
│       ├── db-query     (250ms)    ← 自定义 Span
│       └── ml-inference (200ms)    ← 自定义 Span
Total: ~570ms

关键点

  • Resource 设置 service.name 是 Jaeger 分组的关键
  • 自动插桩(FlaskInstrumentor)无需修改业务代码
  • tracer.start_as_current_span 创建自定义 Span 记录关键步骤
  • OTLP insecure=True 仅在本地开发使用,生产需配置 TLS
  • BatchSpanProcessor 批量上报,降低网络开销

教程

前言

在单体应用中,bug 排查很简单——看日志、查堆栈。但在微服务中,一个请求可能经过 10+ 个服务,日志分散在各处。分布式追踪让你像单步调试一样追踪整个请求链路。


第一章:核心概念

1.1 Trace vs Span

Trace(追踪)
  └── Span A(根 Span - ServiceA)
       ├── Span B(子 Span - ServiceB)
       │    ├── Span C(子 Span - DB 查询)
       │    └── Span D(子 Span - Cache 读取)
       └── Span E(子 Span - ServiceC)
概念 含义 类比
Trace 一次完整请求的全链路 一个完整的"故事"
Span 一个操作单元(含开始/结束时间) 故事的一个"章节"
Trace ID 全局唯一标识 故事编号
Span ID Span 唯一标识 章节编号
Parent Span ID 父 Span 的 ID 父章节引用

1.2 上下文传播(Context Propagation)

ServiceA: TraceID=abc, SpanID=001
  → HTTP Header: traceparent=00-abc-001-01
  → ServiceB: 提取 TraceID=abc, ParentSpanID=001, 创建 SpanID=002

W3C Trace Context 标准:

traceparent: 00-{trace-id}-{parent-span-id}-{trace-flags}
示例: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01

第二章:采样策略

2.1 为什么需要采样

全量追踪会消耗大量存储和计算资源。采样是用少量数据推断整体画像。

2.2 四种采样策略

策略 工作方式 适用场景
常量采样 固定比例(如 1%) 高流量,成本敏感
概率采样 每个 Trace 独立概率 一般生产环境
速率限制 每秒最多 N 个 Trace 控制绝对数据量
自适应采样 低流量全采,高流量降采样 ✅ 推荐!保证所有服务都有样本
# 自适应采样示例(需 jaeger-adaptive-sampling)
# 在 Jaeger Agent 层配置:
# --sampling.strategies-file=/etc/jaeger/sampling.json

2.3 Head-based vs Tail-based

Head-based(SDK 决定):
  入口创建 Trace 时决定是否采样
  ✅ 简单,✅ 无额外开销
  ❌ 错误 Trace 可能被丢弃

Tail-based(Collector 决定):
  所有 Trace 先缓存,根据结果(错误/慢)决定保留
  ✅ 不错过重要 Trace
  ❌ 需要缓存和额外处理

第三章:分析 Trace 的方法

3.1 火焰图分析

Jaeger UI 提供火焰图视图,直观显示耗时分布:

service-a.process:      ████████████████████████████████████ 570ms
  local-computation:     ████ 120ms
  call-service-b:        ██████████████████████ 450ms
    db-query:             ████████ 250ms   ← 瓶颈!
    ml-inference:         ██████ 200ms

3.2 关键查询

# 查找慢请求
service=service-a AND duration>1s

# 查找错误
service=service-a AND status.code=ERROR

# 查找特定用户
service=service-a AND user.id=42

# 查找特定接口
operation=GET /process AND http.status_code=500

第四章:Jaeger 架构

┌──────────┐  SDK   ┌──────────┐
│ 应用 Pod  │──OTLP──│ Collector│
│ (SDK)    │        └────┬─────┘
└──────────┘             │
                    ┌────┴─────┐
                    │ Storage  │ (ES/Cassandra/Badger)
                    └────┬─────┘
                    ┌────┴─────┐
                    │  Query   │
                    └────┬─────┘
                    16686│ UI
                    ┌────┴─────┐
                    │  Browser │
                    └──────────┘

部署模式对比

模式 组件 适用
All-in-One 所有组件打包 开发/测试
分离部署 Collector + Query + Storage 独立 生产
Kubernetes Operator CRD 声明式管理 K8s 生产

思考题

  1. 如果 Trace 数据量巨大导致 ES 撑不住,有哪些优化手段(除采样外)?
  2. Head-based 和 Tail-based 采样各有什么致命的缺点?有没有混合方案?
  3. 上下文传播如果中断(比如经过一个不支持追踪的中间件),后续 Span 怎么办?
  4. OpenTelemetry 相比 Jaeger 原生 SDK 有什么优势?为什么现在都推荐 OTel?

下一步

  • 学习 OpenTelemetry 三朵金花(Trace + Metrics + Logging)统一采集
  • 学习 Jaeger + Elasticsearch 生产部署
  • 学习 Jaeger 自适应采样配置

参考资料

暂无参考文献