Language:Chinese VersionEnglish Version

在整个2010年代的大部分时间里,可观测性是一场供应商之间的战争。Datadog使用一种代理,New Relic使用另一种,Jaeger使用自己的SDK,而Prometheus则抓取自己的导出格式。为应用程序添加可观测性意味着要选择一个供应商,并日后承担切换成本。OpenTelemetry改变了这一局面。到2026年,它已成为遥测仪器的实际标准——这一层将”如何进行仪器化”与”数据去向何处”分离开来。对于后端工程师来说,正确理解它不再是可有可无的选项。

OpenTelemetry到底是什么

OpenTelemetry (OTel) 是一个CNCF毕业项目,为三种信号类型提供供应商中立规范、API、SDK和收集器:追踪、指标和日志。其关键架构洞见是关注点分离:

  • 仪器化库使用OTel API生成遥测数据
  • SDK实现API并处理批处理、采样和导出
  • 收集器接收遥测数据,处理它,并将其路由到任何后端
  • 后端(Jaeger, Tempo, Prometheus, Datadog, Honeycomb)通过OTLP接收标准化数据

这意味着你只需使用OTel为你的应用程序进行一次仪器化,就可以将数据发送到任何后端组合——开发期间使用Jaeger进行分布式追踪,生产环境使用Tempo,特定团队使用Datadog——而无需更改应用程序代码。

三种信号及其各自的作用

追踪:分布式请求流

追踪代表单个请求通过系统的端到端旅程。它由span组成——每个span代表一个操作(HTTP调用、数据库查询、缓存查找),包含开始时间、持续时间、状态和任意属性。

关键特性是上下文传播:追踪ID随着请求跨越服务边界流动。当服务A调用服务B再调用服务C时,所有三个span共享相同的追踪ID,可以可视化为单个请求树。这就是使分布式调试变得可行的原因。

指标:聚合的系统状态

指标是随时间聚合的数值测量。请求速率、错误率、延迟百分位数(p50、p95、p99)、队列深度、缓存命中率。指标的存储和查询成本在规模上很经济——你可以保留多年的指标数据,而这些数据如果作为原始追踪存储将是不可能的。

日志:离散事件

日志是离散事件的时间戳记录。OTel的日志数据模型将追踪上下文(追踪ID、span ID)添加到日志记录中,实现了关联性:当你在日志中看到错误时,可以直接跳转到产生该错误的追踪。

实际应用中的仪器化

自动仪器化:获取追踪的最快途径

对于许多常见框架,OTel 提供了无需代码更改的自动检测功能。它会修补框架的内部结构以自动生成 span:

# Python: 自动检测 FastAPI 应用程序
pip install opentelemetry-distro opentelemetry-exporter-otlp
opentelemetry-bootstrap --action=install   # 为检测到的库安装检测工具

# 使用自动检测运行应用程序
OTEL_SERVICE_NAME=order-api 
OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317 
OTEL_TRACES_EXPORTER=otlp 
OTEL_METRICS_EXPORTER=otlp 
opentelemetry-instrument uvicorn main:app --host 0.0.0.0 --port 8000

无需更改一行应用程序代码,这会为每个 HTTP 请求、SQLAlchemy 查询、Redis 操作和出站 HTTP 调用生成 span。对于前 80% 的可观测性覆盖范围,自动检测是正确的方法。

手动检测:添加业务上下文

自动检测不知道什么对您的业务很重要。为特定领域的操作添加自定义 span 和属性是您捕获使调试变得有意义的上下文的地方:

from opentelemetry import trace
from opentelemetry.trace import Status, StatusCode

tracer = trace.get_tracer("order-service", "1.0.0")

def process_order(order_id: str, customer_id: str) -> dict:
    with tracer.start_as_current_span("process_order") as span:
        # 向 span 添加业务相关属性
        span.set_attribute("order.id", order_id)
        span.set_attribute("customer.id", customer_id)
        span.set_attribute("order.region", get_customer_region(customer_id))

        try:
            # 库存检查 — 如果自动检测,会自动创建子 span
            inventory_result = check_inventory(order_id)
            span.set_attribute("inventory.available", inventory_result.available)

            if not inventory_result.available:
                span.set_attribute("order.outcome", "rejected_inventory")
                span.set_status(Status(StatusCode.ERROR, "Insufficient inventory"))
                return {"status": "rejected", "reason": "inventory"}

            # 支付处理
            with tracer.start_as_current_span("process_payment") as payment_span:
                payment_span.set_attribute("payment.method", get_payment_method(customer_id))
                result = charge_customer(customer_id, order_id)
                payment_span.set_attribute("payment.transaction_id", result.transaction_id)

            span.set_attribute("order.outcome", "fulfilled")
            return {"status": "success", "transaction_id": result.transaction_id}

        except Exception as e:
            span.record_exception(e)
            span.set_status(Status(StatusCode.ERROR, str(e)))
            raise

现在当订单失败时,你可以通过 order.outcome = "rejected_inventory" 来筛选追踪,或者查找来自特定区域的所有订单。这种丰富的上下文信息是将有用的追踪与仅显示调用了哪些端点的追踪区分开来的关键。

OpenTelemetry Collector:架构模式

Collector 是你的应用程序与后端之间的路由层。它通过 OTLP(gRPC 或 HTTP)接收遥测数据,进行处理,然后导出到一个或多个目标。

代理模式:每台主机一个 Collector

# otel-collector-agent.yaml — 作为 DaemonSet 在每个 Kubernetes 节点上运行
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318
  # 同时收集主机指标
  hostmetrics:
    collection_interval: 30s
    scrapers:
      cpu:
      memory:
      disk:
      network:

processors:
  batch:
    timeout: 1s
    send_batch_size: 1024
  # 为所有遥测数据添加 Kubernetes 元数据
  k8sattributes:
    auth_type: serviceAccount
    passthrough: false
    extract:
      metadata:
        - k8s.pod.name
        - k8s.namespace.name
        - k8s.deployment.name
        - k8s.node.name
  # 对高容量、低价值的追踪进行采样
  probabilistic_sampler:
    hash_seed: 22
    sampling_percentage: 10   # 保留 10% 的追踪

exporters:
  otlp/tempo:
    endpoint: tempo.monitoring:4317
    tls:
      insecure: true
  prometheusremotewrite:
    endpoint: http://prometheus.monitoring:9090/api/v1/write

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch, k8sattributes, probabilistic_sampler]
      exporters: [otlp/tempo]
    metrics:
      receivers: [otlp, hostmetrics]
      processors: [batch, k8sattributes]
      exporters: [prometheusremotewrite]

基于尾部的采样:保留重要的追踪

基于头部的采样(在追踪开始时决定是否保留)很简单,但在你知道追踪是否有价值之前就丢弃了它们。基于尾部的采样会缓冲追踪并在追踪完成后才决定保留或丢弃——这样无论采样率如何,你总能保留错误追踪和慢速追踪。

# 尾部采样处理器配置
processors:
  tail_sampling:
    decision_wait: 10s        # 最多等待 10s 让所有 span 到达
    num_traces: 50000         # 最多缓冲 5 万条追踪
    expected_new_traces_per_sec: 100
    policies:
      # 总是保留错误追踪
      - name: errors-policy
        type: status_code
        status_code: {status_codes: [ERROR]}
      # 总是保留慢速追踪(p99 阈值)
      - name: slow-traces-policy
        type: latency
        latency: {threshold_ms: 2000}
      # 保留其他所有追踪的 5%
      - name: sample-policy
        type: probabilistic
        probabilistic: {sampling_percentage: 5}

此配置保留了 100% 的错误和慢速跟踪——这正是调试所需的——同时对健康快速请求进行采样,否则这些请求将主导存储成本。

关联跟踪、指标和日志

当您能够在信号类型之间导航时,OTel 的真正力量才会显现。指标仪表板中的 p99 延迟峰值应直接链接到显示哪些操作缓慢的示例跟踪。日志错误应链接到其父跟踪。这种关联需要在所有信号中保持一致的跟踪上下文。

在 Grafana 堆栈中(Tempo 用于跟踪,Loki 用于日志,Prometheus/Mimir 用于指标),关联是通过数据源链接配置的:

# 带有 OTel 跟踪上下文的 Loki 日志行
{
  "timestamp": "2026-03-28T10:23:45.123Z",
  "severity": "ERROR",
  "message": "支付处理失败:资金不足",
  "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
  "span_id": "00f067aa0ba902b7",
  "service.name": "payment-service",
  "order.id": "ord_abc123"
}

# Grafana 数据源配置将日志中的 trace_id 链接到 Tempo
# datasources.yaml
- name: Loki
  type: loki
  url: http://loki:3100
  jsonData:
    derivedFields:
      - datasourceUid: tempo
        matcherRegex: '"trace_id":"(w+)"'
        name: TraceID
        url: '$${__value.raw}'   # 链接到 Tempo 跟踪查看器

2026 年的 OpenTelemetry:变化之处

截至 2024-2025 年,OTel 规范在所有三种信号类型上已达到稳定状态。对生产采用至关重要的关键发展:

  • 日志现已稳定:OTel 日志数据模型和 SDK 支持已达到稳定状态。用 OTel 路由日志替换直接 Loki/Elasticsearch 发送现在是推荐路径。
  • Profiles 信号类型:连续分析正在作为第四种信号类型添加,Pyroscope 集成已处于预览阶段。这将进一步统一可观测性信号模型。
  • OpAMP(开放代理管理协议):OTel Collector 实例的远程管理——无需重新部署即可更新配置、采样策略和后端路由——现已达到生产稳定状态。
  • 语义约定成熟度:HTTP、数据库、消息传递和云提供商的语义约定已稳定,这意味着属性现在在所有自动检测库中保持一致。

入门:实用的操作顺序

  1. 将 OTel Collector 作为 DaemonSet(Kubernetes)或 sidecar(其他环境)部署
  2. 首先为您的最高流量服务添加自动检测 — 这能以最少的努力立即产生价值
  3. 从一开始就配置基于尾部采样 — 当数据量很大时,后期添加要困难得多
  4. 为最重要的业务操作(结账、支付、用户注册)添加手动检测
  5. 在 Grafana 中设置您的追踪、指标和日志后端之间的关联链接
  6. 基于 OTel 生成的指标定义 SLO 告警 — p99 延迟、错误率、可用性

结论

OpenTelemetry 实现了其作为供应商中立的可观测性检测的承诺。生态系统稳定,自动检测覆盖面广泛,Collector 的路由灵活性意味着您永远不会被锁定在后端选择中。今天使用 OTel 进行检测的团队可以从 Jaeger 切换到 Tempo,从自托管 Prometheus 切换到 Grafana Cloud,或为特定用例添加商业后端 — 而无需触碰应用程序代码。

在适当检测上的投资 — 包含业务相关属性的 span、基于尾部采样、日志-追踪关联 — 会产生复利效应。每次更快解决的故障、每次更早发现性能回归、每次基于真实数据做出的容量规划决策:所有这些都取决于您可观测性基础的质量。OpenTelemetry 提供了这种基础,以不会让您陷入将来会后悔的供应商关系的形式。

By Michael Sun

Founder and Editor-in-Chief of NovVista. Software engineer with hands-on experience in cloud infrastructure, full-stack development, and DevOps. Writes about AI tools, developer workflows, server architecture, and the practical side of technology. Based in China.

Leave a Reply

Your email address will not be published. Required fields are marked *

You missed