概述
OpenTelemetry 为何赢得 2026
截至 2026 年,OpenTelemetry(以下简称 OTel)已成为分布式可观测性领域的事实标准。根据 CNCF 年度调查,全球已有超过 85% 的云原生项目采用 OTel 作为首选遥测数据采集方案。1
OTel 成功的核心原因在于其统一性:
| 特性 | OpenTracing | OpenCensus | OpenTelemetry |
|---|---|---|---|
| 统一 API | ✗ | ✗ | ✓ |
| 多语言支持 | 有限 | 有限 | 完整 |
| 供应商无关 | ✓ | ✗ | ✓ |
| 三大支柱融合 | ✗ | Metrics 为主 | Traces + Metrics + Logs |
| CNCF 毕业状态 | 归档 | 归档 | ✓ |
2019 年,CNCF 宣布将 OpenTracing 和 OpenCensus 合并为 OpenTelemetry,这一决策彻底改变了行业格局。两大标准合二为一,结束了「选边站队」的困境,开发者无需再在 Zipkin 和 Jaeger 之间做出取舍。
核心定位
OTel 不仅仅是一个追踪库,而是可观测性的统一层:
┌─────────────────────────────────────────────────────────────┐
│ Your Application │
├─────────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Auto Inst. │ │ Manual API │ │ SDK Core │ │
│ │ (HTTP/gRPC) │ │ (Business) │ │ (Context) │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ └────────────────┼────────────────┘ │
│ ▼ │
│ OTLP Protocol │
└──────────────────────────┬───────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ OpenTelemetry Collector │
└──────────────────────────┬───────────────────────────────────┘
▼
┌─────────────────┼─────────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Jaeger │ │Prometheus│ │ Grafana │
└──────────┘ └──────────┘ └──────────┘
核心概念
三大支柱
OTel 的核心理念是将追踪(Traces)、**指标(Metrics)和日志(Logs)**统一在一个语义模型下:
Traces 记录请求在分布式系统中的完整流转路径,擅长回答「发生了什么」。
Metrics 是聚合后的数值指标,擅长回答「发生了什么多少」。
Logs 是离散的事件记录,擅长回答「详细上下文是什么」。
这三者相辅相成:通过 Trace 定位问题,通过 Metrics 判断影响范围,通过 Logs 深入根因。
Span、Trace ID 与 SpanContext
Span 是分布式追踪的基本单元,代表一个逻辑操作单元:
# Span 的生命周期
span = tracer.start_span("operation_name")
span.set_attribute("user.id", "12345")
span.add_event("Processing started")
# ... 业务逻辑 ...
span.set_status(Status(Code.OK))
span.end()Trace ID 是 128 位全局唯一标识,贯穿整个请求链路。所有 Span 共享同一个 Trace ID。
SpanContext 是跨服务边界传递的上下文载体,包含:
trace_id:链路唯一标识span_id:当前操作标识trace_flags:采样标志(1 位)tracestate:多租户或自定义键值对
上下文传播机制
上下文传播是分布式追踪的基石。当请求跨越服务边界时,SpanContext 必须被序列化和传递:
# W3C Trace Context 是标准传播格式
from opentelemetry.propagate import inject, extract
from opentelemetry.propagation.trace_context import TraceContextTextMapPropagator
# 发送方:注入上下文到 HTTP Headers
headers = {}
inject(headers) # headers["traceparent"] = "00-..."
# 接收方:从 Headers 提取上下文
context = extract(request.headers)
with tracer.start_as_current_span("operation", context=context) as span:
# 自动建立父子关系
passCollector 架构
单 Collector vs 分层架构
OTel Collector 是处理遥测数据的核心组件。架构选择取决于系统规模:
单 Collector 架构:
应用 → Agent(daemonset) → Collector → Backend
适用于:
- 服务数量 < 20
- 请求量 < 500 RPS
- 团队规模 < 10 人
双层 Collector 架构:
应用 → Agent(daemonset) → Gateway(集群) → Backend
适用于:
- 服务数量 > 80
- 请求量 > 5000 RPS
- 多地域部署
| 规模等级 | 服务数 | RPS | 推荐架构 | Collector 实例数 |
|---|---|---|---|---|
| 小型 | < 20 | < 500 | 单层 | 1-2 |
| 中型 | 20-80 | 500-5000 | 单层 + HA | 3-5 |
| 大型 | > 80 | > 5000 | 双层 | 10+ |
Agent 模式:Daemonset vs Sidecar
Daemonset 模式(推荐):
# otel-agent-daemonset.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: otel-agent
spec:
selector:
matchLabels:
app: otel-agent
template:
spec:
containers:
- name: otel-agent
image: otel/opentelemetry-collector-contrib:0.96.0
args: ["--config=/etc/otel-agent.yaml"]
env:
- name: HOST_IP
valueFrom:
fieldRef:
fieldPath: status.hostIP
volumeMounts:
- name: config
mountPath: /etc
ports:
- name: otlp
containerPort: 4317
hostPort: 4317
volumes:
- name: config
configMap:
name: otel-agent-config优势:资源利用率高,节点级别聚合减少网络开销。
Sidecar 模式:
优势:完全隔离,灵活性高。劣势:资源开销大,管理复杂。
推荐场景:多租户环境、需要强隔离的服务。
生产最佳实践
自动埋点:起点而非终点
自动埋点是快速接入 OTel 的最佳方式,但不适合作为唯一手段:
# Python 自动埋点配置
from opentelemetry.instrumentation.flask import FlaskInstrumentor
from opentelemetry.instrumentation.requests import RequestsInstrumentor
from opentelemetry.instrumentation.pymongo import PymongoInstrumentor
# 初始化所有自动埋点
FlaskInstrumentor().instrument_app(app)
RequestsInstrumentor().instrument()
PymongoInstrumentor().instrument()自动埋点的局限:
| 场景 | 自动埋点能力 |
|---|---|
| HTTP 入站请求 | ✓ 完整支持 |
| 数据库调用 | ✓ 主流驱动支持 |
| 消息队列 | △ 部分支持 |
| 业务关键路径 | ✗ 无法覆盖 |
| 复杂异步逻辑 | ✗ 上下文断裂 |
手动埋点:关键业务路径
对于核心业务逻辑,必须使用手动埋点:
# 业务关键路径的手动埋点
tracer = trace.get_tracer(__name__)
def process_payment(order_id: str, amount: Decimal):
with tracer.start_as_current_span("payment.process") as span:
span.set_attribute("payment.order_id", order_id)
span.set_attribute("payment.amount", float(amount))
span.set_attribute("payment.currency", "CNY")
# 调用外部支付网关
with tracer.start_as_current_span("payment.gateway_call") as gateway_span:
gateway_span.set_attribute("gateway.provider", "alipay")
try:
result = payment_gateway.pay(order_id, amount)
span.set_attribute("payment.status", "success")
except PaymentException as e:
span.set_status(trace.Status(trace.StatusCode.ERROR, str(e)))
span.record_exception(e)
raise语义约定
OTel 定义了标准的属性命名约定,遵循语义约定能确保跨服务、跨团队的一致性:
| 类别 | 属性键 | 示例值 |
|---|---|---|
| HTTP | http.method | GET, POST |
| HTTP | http.url | https://api.example.com/users |
| HTTP | http.status_code | 200, 404 |
| 数据库 | db.system | postgresql, mysql, redis |
| 数据库 | db.name | users_db |
| 数据库 | db.operation | SELECT, INSERT |
| RPC | rpc.system | grpc, jsonrpc |
| RPC | rpc.method | /user.Service/GetUser |
| 消息 | messaging.system | kafka, rabbitmq |
| 消息 | messaging.destination | order-created |
采样策略
头部采样 vs 尾部采样
头部采样(Head-based Sampling) 在请求入口处决定采样,决策早、开销低:
# 头部采样配置
processors:
probabilistic_sampler:
sampling_percentage: 10 # 全局 10% 采样优势:低开销、无需等待请求结束。劣势:可能丢失异常和慢请求。
尾部采样(Tail-based Sampling) 在请求结束后根据结果采样:
# 尾部采样配置
processors:
tail_sampling:
decision_wait: 10s
num_traces: 100000
policies:
# 错误请求 100% 采样
- name: errors-policy
type: status_code
status_code: { status_codes: [ERROR] }
# 慢请求 100% 采样
- name: slow-traces-policy
type: latency
latency: { threshold_ms: 2000 }
# 关键服务 100% 采样
- name: critical-services-policy
type: string_attribute
string_attribute: { key: service.name, values: [payment-service, order-service] }
# 其他请求 5% 采样
- name: probabilistic-policy
type: probabilistic
probabilistic: { sampling_percentage: 5 }父级采样
父级采样(Parent-based Sampling)根据父 Span 是否被采样决定子 Span:
processors:
parent_based:
root:
type: probabilistic
probabilistic: { sampling_percentage: 20 }
trace_header:
type: probabilistic
probabilistic: { sampling_percentage: 100 }这种模式确保:如果父请求被采样,所有子 Span 都被包含。
自适应采样
大规模系统推荐使用自适应采样策略:
processors:
tail_sampling:
decision_wait: 10s
policies:
# 高价值请求:认证用户、付费订单
- name: high-value-policy
type: string_attribute
string_attribute:
key: user.tier
values: [premium, enterprise]
sampling_percentage: 100
# 异常请求:始终采样
- name: errors-policy
type: status_code
status_code: { status_codes: [ERROR] }
sampling_percentage: 100
# 慢请求:超过 P99 阈值
- name: slow-traces-policy
type: latency
latency: { threshold_ms: 3000 }
sampling_percentage: 100
# 正常请求:按比例采样
- name: normal-policy
type: probabilistic
probabilistic: { sampling_percentage: 5 }上下文传播
HTTP Header 传播
W3C Trace Context 是 Web 场景的标准:
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
tracestate: congo=t61rcWkgMzE00:版本号4bf92f3577b34da6a3ce929d0e0e4736:Trace ID(32 字符)00f067aa0ba902b7:Span ID(16 字符)01:采样标志(01=已采样)
消息队列传播
异步消息系统需要特殊处理:
# Kafka 消息传播
from opentelemetry import trace
from opentelemetry.propagate import inject, extract
from opentelemetry.instrumentation.confluent_kafka import ConfluentKafkaInstrumentor
ConfluentKafkaInstrumentor().instrument()
# 生产者:注入上下文到消息 Headers
producer = KafkaProducer(...)
headers = {}
inject(headers)
producer.send("order-topic", value=order_data, headers=list(headers.items()))
# 消费者:提取上下文
consumer = KafkaConsumer("order-topic", value_deserializer=...)
for message in consumer:
context = extract(message.headers())
with tracer.start_as_current_span("process_order", context=context) as span:
process_order(message.value())异步任务框架
线程池、Future、AsyncIO 等场景容易出现上下文断裂:
# Python ThreadPoolExecutor 场景
from concurrent.futures import ThreadPoolExecutor
from opentelemetry import trace
executor = ThreadPoolExecutor(max_workers=10)
def async_task(task_id: int):
# 手动传递当前上下文
ctx = trace.set_span_in_context(trace.get_current_span())
return executor.submit(process_task, task_id, ctx)
def process_task(task_id: int, parent_ctx):
# 在新线程中激活父上下文
with tracer.start_as_current_span(f"task_{task_id}", context=parent_ctx):
# 处理任务
pass成本控制
基数管理
高基数是 OTel 成本失控的主因:
| 属性类型 | 低基数示例 | 高基数风险 |
|---|---|---|
http.status_code | 200, 404, 500 | - |
user.id | - | 用户数 = 基数 |
session.id | - | 每次会话 = 新基数 |
request.id | - | 每请求 = 新基数 |
防御策略:
# 避免高基数属性
span.set_attribute("http.status_code", 200) # ✓ OK
# 错误做法:request.id 作为属性会爆炸
span.set_attribute("request.id", request_id) # ✗ 高基数
# 正确做法:将 request.id 作为 Event 而非 Attribute
span.add_event("request_id", {"request.id": request_id})数据保留策略
分层保留控制存储成本:
| 层级 | 保留期 | 存储介质 | 用途 |
|---|---|---|---|
| 热数据 | 0-7 天 | SSD/本地 | 实时排查 |
| 温数据 | 8-30 天 | HDD/网络存储 | 趋势分析 |
| 冷数据 | 31-365 天 | 对象存储 | 合规审计 |
| 归档 | > 1 年 | 归档存储 | 法律要求 |
分层采样策略
生产环境推荐「关键路径 100%,普通路径 5-10%」的分层策略:
processors:
tail_sampling:
decision_wait: 15s
policies:
# 支付链路:100%
- name: payment-path
type: and
and:
conditions:
- string_attribute: { key: service.name, values: [payment-service] }
- string_attribute: { key: operation.type, values: [payment] }
sampling_percentage: 100
# 认证请求:100%
- name: auth-requests
type: string_attribute
string_attribute: { key: auth.required, values: [true] }
sampling_percentage: 100
# 异常和慢请求:100%
- name: errors-and-slow
type: or
or:
conditions:
- status_code: { status_codes: [ERROR] }
- latency: { threshold_ms: 2000 }
sampling_percentage: 100
# 默认采样率:5%
- name: default
type: probabilistic
probabilistic: { sampling_percentage: 5 }常见陷阱
高基数爆炸
症状:存储成本月环比增长超过 50%,查询延迟显著增加。
根因:将 user.id、session.id、request.id 等高唯一性值设为 Span 属性。
解决方案:
# 错误示例
span.set_attribute("db.statement", f"SELECT * FROM users WHERE id={user_id}")
# 正确示例:参数化查询
span.set_attribute("db.system", "postgresql")
span.set_attribute("db.operation", "SELECT")
span.set_attribute("db.table", "users")
span.set_attribute("db.bind_args.count", 1)
# request_id 放入 Event 而非 Attribute
span.add_event("request_id", {"request.id": request_id})异步边界上下文断裂
症状:Trace 中间出现 Span 断开,子服务 Span 不在父 Span 下。
根因:异步任务未正确传递 SpanContext。
解决方案:
# Python asyncio 场景
import asyncio
from opentelemetry import trace
async def async_operation():
# 获取当前上下文
current_span = trace.get_current_span()
ctx = trace.set_span_in_context(current_span)
# 传递上下文到新 Task
task = asyncio.create_task(called_operation(ctx))
await task
async def called_operation(ctx):
with tracer.start_as_current_span("async_child", context=ctx):
passCollector 单点故障
症状:所有追踪数据丢失,系统无任何可观测性。
根因:Collector 部署无高可用,网络链路无冗余。
解决方案:
# Collector 高可用配置
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch, memory_limiter]
exporters: [jaeger, otlp/2] # 多个后端
exporters:
otlp:
endpoint: jaeger-collector-1:4317
otlp/2:
endpoint: jaeger-collector-2:4317
# 失败时重试
retry_on_failure:
enabled: true
initial_interval: 1s
max_interval: 30s
max_attempts: 5Span 命名不一致
症状:同类操作在不同服务中命名各异,难以聚合查询。
根因:缺乏命名规范,开发者各行其是。
解决方案:制定团队命名规范并固化到 Code Review:
| 操作类型 | 命名格式 | 示例 |
|---|---|---|
| HTTP 入站 | {METHOD} {ROUTE} | GET /api/users/{id} |
| 数据库 | {OPERATION} {TABLE} | SELECT users |
| 缓存 | {OPERATION} {KEY_PATTERN} | GET user:{id} |
| 消息 | {PRODUCE/CONSUME} {TOPIC} | CONSUME order-topic |
相关主题
参考资料
Footnotes
-
CNCF Annual Survey 2025 - https://www.cncf.io/reports/cncf-annual-survey-2025/ ↩