OpenTelemetry 全链路追踪落地实战

微服务一多,慢请求就难定位:网关、A 服务、B 服务、数据库,到底哪一环慢了?只看日志要靠时间戳人工对齐,效率低还容易错。全链路追踪把一次请求经过的所有服务串成一条 Trace,每个 Span 记录耗时与状态,慢在哪一环一目了然。本文用 OpenTelemetry(OTel)+ Collector + Jaeger 落地,讲清接入、采样与成本控制。

先说落地顺序:先接核心链路、再扩到全量服务;先做追踪、再打通日志与指标。不要一上来就全量接入所有服务,采样与存储成本会先失控。

一、架构:SDK、Collector、后端三件套

OTel 的标准架构分三层:应用里集成 SDK 产生 Span、Collector 集中接收并处理(采样、过滤、批量发送)、后端(Jaeger/Tempo)存储与展示。应用不要直连后端,中间必须过 Collector,否则采样策略与后端切换都没法统一管理。

<pre class="wp-block-syntaxhighlighter-code"># Collector 部署(Docker Compose 示例,生产建议用 K8s)
cat > docker-compose.yml <<'EOF'
services:
  otel-collector:
    image: otel/opentelemetry-collector-contrib:0.100.0
    command: ["--config=/etc/otel-collector-config.yaml"]
    volumes:
      - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
    ports:
      - "4317:4317"   # OTLP gRPC
      - "4318:4318"   # OTLP HTTP
  jaeger:
    image: jaegertracing/all-in-one:1.55
    ports:
      - "16686:16686"  # Jaeger UI
      - "14250:14250"
EOF
docker compose up -d
curl -s http://127.0.0.1:16686/ | head -5</pre>

二、Collector 配置:接收、采样与导出

Collector 的配置分三段:receivers(收 OTLP)、processors(批处理与采样)、exporters(发给 Jaeger)。采样是成本控制的关键:全量采样存储成本极高,生产上常用“按比例采样 + 错误全采”的组合。

# otel-collector-config.yaml
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318
processors:
  batch:
    timeout: 10s
    send_batch_size: 1024
  tail_sampling:
    decision_wait: 10s
    policies:
      - name: errors
        type: status_code
        status_code: { status_codes: [ERROR] }
      - name: slow
        type: latency
        latency: { threshold_ms: 1000 }
      - name: default
        type: probabilistic
        probabilistic: { sampling_percentage: 10 }
exporters:
  otlp:
    endpoint: jaeger:4317
    tls: { insecure: true }
service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [tail_sampling, batch]
      exporters: [otlp]

尾部采样(tail_sampling)是推荐做法:等 Trace 完整到达后再决定采不采,错误与慢请求全采、正常请求按比例采,既控制成本又不漏关键 Trace。注意尾部采样需要 Collector 内存里暂存 Trace,decision_wait 不要设得过大。

三、应用接入:SDK 埋点与上下文传递

应用接入分两步:加 SDK 依赖、配 OTLP 地址。主流框架都有自动埋点(auto-instrumentation),HTTP、数据库、消息队列的 Span 自动生成,不用手动写埋点代码。手动埋点只用在关键业务逻辑上,比如“下单”这个业务动作的耗时。

# Python 应用接入示例(Flask + 自动埋点)
pip install opentelemetry-distro opentelemetry-exporter-otlp
opentelemetry-bootstrap -a install
# 启动时注入 OTLP 地址(不用改代码)
OTEL_SERVICE_NAME=order-service \
OTEL_EXPORTER_OTLP_ENDPOINT=http://192.0.2.10:4317 \
OTEL_TRACES_SAMPLER=parentbased_traceidratio \
OTEL_TRACES_SAMPLER_ARG=0.1 \
opentelemetry-instrument python app.py
# Java 应用接入(javaagent 方式,同样不用改代码)
# java -javaagent:opentelemetry-javaagent.jar \
#   -Dotel.service.name=order-service \
#   -Dotel.exporter.otlp.endpoint=http://192.0.2.10:4317 \
#   -jar app.jar

上下文传递靠 HTTP 头(traceparent),跨服务调用时 SDK 自动透传,服务间不用手动传 Trace ID。接入后先验证:发一次请求,在 Jaeger UI 里按服务名搜到完整 Trace,确认 Span 齐全、耗时合理。

四、排障实战:慢请求定位的三步法

有了 Trace,慢请求定位就三步:找到慢 Trace、看 Span 瀑布图、定位最慢的 Span。瀑布图里最长的那一段就是瓶颈,再点进 Span 看它的属性(SQL、URL、错误信息)。

# Jaeger UI 查询:服务名 + 最小时长 + 时间范围
# http://192.0.2.10:16686/search?service=order-service&minDuration=1s
# 常见定位结论:
# - 某个 DB Span 占 90% 时间 → 查 SQL 与索引
# - 某个 HTTP Span 超时 → 查下游服务与网络
# - Span 之间有大段空白 → 查服务间队列或异步等待
# - 大量 ERROR Span 集中在某服务 → 查该服务的日志与告警

五、打通日志与指标:可观测性的闭环

追踪、日志、指标三者打通才算完整:Trace ID 写进日志,日志里能直接跳到 Trace;指标告警里带 Trace ID 链接,收到告警能直接定位到慢 Trace。打通的方法是统一用 Trace ID 做关联键。

<pre class="wp-block-syntaxhighlighter-code"># 日志里带上 Trace ID(以 Python logging 为例,需在 formatter 里加 trace_id)
# 格式示例:2026-11-09 10:00:00 INFO [trace_id=abc123] order created
# Collector 同时收日志与指标(OTLP 统一入口)
# service.pipelines.logs / metrics 与 traces 共用同一个 receivers.otlp
# Jaeger UI 里按 Trace ID 直接查整条链路
# http://192.0.2.10:16686/trace/<trace-id></pre>

六、采样策略进阶:头部采样不够用时上尾部采样

头部采样在请求入口就决定采不采,简单但有个硬伤:慢请求、报错请求和正常请求被采到的概率一样,关键线索经常被漏掉。尾部采样等整条链路走完再决定,错误率高、延迟高的链路优先保留,存储成本反而更低。尾部采样要在 Collector 上做,需要 Collector 暂存一段时间内的 Span,内存占用要提前评估。

# 尾部采样配置(Collector 的 tail_sampling 处理器)
# processors:
#   tail_sampling:
#     decision_wait: 10s          # 等整条链路走完再决定
#     num_traces: 100000          # 内存里暂存的链路数,按内存评估
#     expected_new_traces_per_sec: 100
#     policies:
#       - name: errors
#         type: status_code
#         status_code: {status_codes: [ERROR]}
#       - name: slow-traces
#         type: latency
#         latency: {threshold_ms: 1000}
#       - name: normal-sample
#         type: probabilistic
#         probabilistic: {sampling_percentage: 5}
# 采样效果验证:看后端里错误链路的占比是否明显高于正常链路
# 内存告警:Collector 的 tail_sampling 队列积压要单独监控

尾部采样的决策等待时间(decision_wait)要按业务链路的最大耗时来定,设得太短、链路还没走完就被判了,设得太长、内存占用直线上升。先从小流量服务试点,观察 Collector 内存与采样效果,再推广到核心链路。采样策略变更后,要对比前后一周的错误链路检出率,确认策略真的在起作用。

七、手动埋点与上下文传递:自动埋点覆盖不到的地方

自动埋点能覆盖 HTTP、数据库这些标准调用,但业务逻辑里的关键步骤——比如“下单校验耗时”、“风控决策结果”——需要手动埋点。手动埋点的要点是:Span 名字要体现业务含义、关键属性要结构化、上下文要在异步任务与消息队列里显式传递,否则链路会在异步边界处断掉。

# 手动埋点示例(Python SDK):业务关键步骤单独成 Span
# from opentelemetry import trace
# tracer = trace.get_tracer(__name__)
# with tracer.start_as_current_span("order.validate") as span:
#     span.set_attribute("order.id", order_id)
#     span.set_attribute("order.amount", amount)
#     result = validate_order(order)
#     span.set_attribute("validate.result", result)
#     if not result:
#         span.set_status(trace.Status(trace.StatusCode.ERROR, "校验失败"))
# 异步任务传递上下文:把 traceparent 放进消息头
# headers = {}
# from opentelemetry.propagate import inject
# inject(headers)  # 消费者侧用 extract 恢复上下文
# 消息队列场景:生产者 inject、消费者 extract,链路才能跨服务接上
# 埋点粒度:业务关键步骤埋,循环体内部不要埋,Span 太多会拖垮后端

手动埋点最常见的错误是 Span 名字取得太随意(比如都叫 process、handle),查的时候根本分不清。Span 属性不要带密码、token、用户隐私数据,敏感字段要在 Collector 侧脱敏。异步边界是链路断裂的重灾区,上线前要专门验证一次跨异步任务的链路完整性。

八、后端选型与成本:Jaeger、Tempo 怎么选

追踪后端选型的核心矛盾是查询能力与存储成本:Jaeger 上手快、查询直观,适合中小规模;Tempo 靠对象存储、按 TraceID 查询,存储成本低,适合大规模长期保留。选型前要先估算量:每天多少 Span、保留多少天、查询频率多高,三个数定了,后端基本就定了。

# 估算追踪数据量:先看 Collector 的导出速率
# kubectl -n observability logs -l app=otel-collector --tail=100 | grep -i 'spans.*exported'
# Jaeger 存储估算:每天 Span 数 × 单 Span 大小 × 保留天数
# 示例:每天 1 亿 Span × 约 1KB × 14 天 ≈ 1.4TB(未压缩)
# Tempo + 对象存储:只按 TraceID 索引,存储成本约为 Jaeger 的 1/3 到 1/5
# 成本控制三件套:采样率、保留期、存储分层
# - 采样率:正常链路 1–5%,错误链路 100%
# - 保留期:核心业务 14 天,普通业务 7 天
# - 存储分层:热数据放本地盘,冷数据放对象存储
# 选型验证:用真实流量跑一周,对比查询延迟与存储增长再定

后端选型不要只看功能列表,要看运维成本:Jaeger 配 Elasticsearch 的组合存储与索引成本都不低,Tempo 的对象存储便宜但查询能力弱。团队规模小、链路量不大时,Jaeger + 单机存储是最省事的组合;链路量上到每天上亿 Span,再考虑 Tempo 或商业后端。无论选哪个,保留期与采样率都要先定好再上线,事后补很容易超预算。

九、成本与避坑清单

  • 采样先行:上线前先定采样策略,错误与慢请求全采、正常请求按比例采,不要全量采样。
  • 存储保留期:Jaeger 的 Trace 保留期按磁盘成本定,一般 7–14 天,过期自动清理。
  • 敏感信息:Span 属性里不要带密码、token、用户隐私数据,Collector 的 processors 里可配属性脱敏。
  • 性能影响:SDK 埋点有 1–3% 的性能开销,高并发服务要先压测再全量接入。
  • 先核心后全量:先接核心链路(网关、下单、支付),验证有效后再扩到全量服务。
  • 监控 Collector 自身:Collector 的队列积压、丢弃 Span 数要单独监控,Collector 挂了等于追踪全断。

发表回复

后才能评论