概述

在云原生和微服务时代,一个请求可能跨越数十个服务节点。传统的监控方式将 Metrics、Logs、Traces 分散在不同系统中——Prometheus 看指标、ELK 搜日志、Jaeger 追链路,三者之间没有统一的关联方式。当线上故障发生时,你需要在三个系统之间来回切换,手动拼接关联信息,效率低下。

OpenTelemetry(简称 OTel)是 CNCF 主导的可观测性统一标准,目标是用一套 SDK/API 统一采集三大信号(Metrics、Logs、Traces),通过统一的 Collector 处理后发送到任意后端。它不替代后端存储和可视化,而是解决"数据采集层碎片化"的问题。本文深入讲解 OTel 的规范、架构、实践和迁移策略。

参考来源:OpenTelemetry 官方文档CNCF OpenTelemetry 规范

一、为什么需要 OpenTelemetry

1.1 可观测性碎片化问题

传统可观测性架构(碎片化):

应用代码
├── Prometheus Client (Metrics)
│   └── → Prometheus → Grafana
├── Logback + Filebeat (Logs)
│   └── → Elasticsearch → Kibana
└── Jaeger Client (Traces)
    └── → Jaeger → Jaeger UI

问题:
1. 三套 SDK,三套配置,三套运维
2. Metrics / Logs / Traces 之间无关联(TraceID 未关联到日志)
3. 切换后端成本高(从 Prometheus 换到 Datadog 需要改代码)
4. 每种语言需要维护不同的客户端库

1.2 OpenTelemetry 的解决思路

OpenTelemetry 统一架构:

应用代码
└── OpenTelemetry SDK (统一采集)
    ├── Metrics
    ├── Logs (带 TraceID)
    └── Traces
    OpenTelemetry Collector (统一处理)
    ├── 过滤 / 聚合 / 采样
    ├── 格式转换
    └── 分发到后端
    ┌───────┼───────┐
    ▼       ▼       ▼
Prometheus  ELK   Jaeger
(Grafana)  (Kibana)(Jaeger UI)

核心价值

价值说明
统一采集一套 SDK 采集三种信号
后端无关换后端不需要改代码
上下文关联TraceID 自动关联 Logs 和 Metrics
多语言支持11 种语言的官方 SDK
标准化CNCF 规范,行业共识

二、OpenTelemetry 规范

2.1 核心概念

概念说明
Signal(信号)可观测性数据类型:Metrics、Logs、Traces
Resource(资源)被监控实体的描述信息(服务名、版本、主机名)
InstrumentationScope采集范围标识(库名、包名)
Context(上下文)请求的传播信息(TraceID、SpanID)
Baggage(行李)跨服务传递的键值对(如 tenant_id)
Span(跨度)一次操作的记录(方法调用、HTTP 请求)
Log Record日志记录(带 Trace 关联)
Meter / Tracer / Logger三种信号的采集器接口

2.2 三大信号

┌──────────────────────────────────────────────────────────┐
│                OpenTelemetry 三大信号                      │
│                                                          │
│  Traces(追踪)                                          │
│  ├── 记录请求在分布式系统中的完整路径                      │
│  ├── 每个 Span 包含:操作名、时间、状态、属性              │
│  └── 通过 TraceID 将多个服务的 Span 串联                  │
│                                                          │
│  Metrics(指标)                                         │
│  ├── 记录可聚合的数值数据                                 │
│  ├── 类型:Counter / Gauge / Histogram / Summary         │
│  └── 包含 Resource 和 Attributes(标签)                │
│                                                          │
│  Logs(日志)                                            │
│  ├── 记录离散事件                                         │
│  ├── 关联 TraceID / SpanID(关键特性)                    │
│  └── 包含 Severity、Body、Attributes                     │
└──────────────────────────────────────────────────────────┘

2.3 数据模型

Span 数据模型

{
  "trace_id": "7b3cf5b0123456789abcdef012345678",
  "span_id": "0123456789abcdef",
  "parent_span_id": "fedcba9876543210",
  "name": "GET /api/orders",
  "kind": "SERVER",
  "start_time": "2026-07-10T10:00:00.123456789Z",
  "end_time": "2026-07-10T10:00:00.456789123Z",
  "status": {
    "code": "ERROR",
    "description": "Database connection timeout"
  },
  "attributes": {
    "http.method": "GET",
    "http.url": "/api/orders",
    "http.status_code": 500,
    "db.system": "mysql",
    "db.statement": "SELECT * FROM orders"
  },
  "events": [
    {
      "name": "exception",
      "time": "2026-07-10T10:00:00.400Z",
      "attributes": {
        "exception.type": "java.sql.SQLException",
        "exception.message": "Connection timeout"
      }
    }
  ],
  "resource": {
    "service.name": "order-service",
    "service.version": "1.2.3",
    "host.name": "order-pod-abc123"
  }
}

Log 数据模型

{
  "timestamp": "2026-07-10T10:00:00.500Z",
  "trace_id": "7b3cf5b0123456789abcdef012345678",
  "span_id": "0123456789abcdef",
  "severity_text": "ERROR",
  "severity_number": 17,
  "body": "Failed to process order: Database connection timeout",
  "attributes": {
    "order_id": "ORD-12345",
    "user_id": "USR-67890",
    "retry_count": 3
  },
  "resource": {
    "service.name": "order-service",
    "service.version": "1.2.3"
  }
}

关键:Log 中的 trace_idspan_id 使日志可以直接关联到 Trace,这是 OTel 的核心价值之一——“点击日志中的 TraceID,直接跳转到 Trace 视图”。

三、OpenTelemetry Collector

3.1 Collector 架构

Collector 是 OTel 的数据管道核心,负责接收、处理和导出遥测数据。

┌──────────────────────────────────────────────────────┐
│              OTel Collector 架构                      │
│                                                      │
│  ┌─────────┐  ┌─────────┐  ┌─────────┐             │
│  │ Receiver │  │ Receiver │  │ Receiver │  ← 接收     │
│  │ (OTLP)   │  │ (Jaeger) │  │ (Prom)  │             │
│  └────┬────┘  └────┬────┘  └────┬────┘             │
│       │            │            │                    │
│       └────────────┼────────────┘                   │
│                    ▼                                  │
│              ┌──────────┐                             │
│              │ Processor│  ← 处理(过滤/采样/增强)    │
│              └────┬─────┘                             │
│                   │                                   │
│       ┌───────────┼───────────┐                      │
│       ▼           ▼           ▼                      │
│  ┌─────────┐ ┌─────────┐ ┌─────────┐                │
│  │ Exporter│ │ Exporter│ │ Exporter│  ← 导出          │
│  │ (OTLP)  │ │ (Prom)  │ │ (ES/Loki)│                │
│  └─────────┘ └─────────┘ └─────────┘                │
└──────────────────────────────────────────────────────┘

3.2 Collector 配置

# otel-collector-config.yaml
receivers:
  # 接收 OTLP 协议数据(SDK 直发)
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

  # 接收 Jaeger 格式数据
  jaeger:
    protocols:
      grpc:
        endpoint: 0.0.0.0:14250
      thrift_http:
        endpoint: 0.0.0.0:14268

  # 接收 Prometheus 拉取
  prometheus:
    config:
      scrape_configs:
        - job_name: 'otel-collector'
          scrape_interval: 15s
          static_configs:
            - targets: ['localhost:8888']

processors:
  # 批量处理
  batch:
    timeout: 5s
    send_batch_size: 1000
    send_batch_max_size: 2000

  # 内存限制
  memory_limiter:
    check_interval: 1s
    limit_mib: 512
    spike_limit_mib: 128

  # 过滤
  filter:
    traces:
      span:
        - 'attributes["http.route"] == "/health"'  # 过滤健康检查 Span
    metrics:
      metric:
        - 'name == "process.runtime.jvm.gc.time"'

  # 属性处理
  attributes:
    actions:
      - key: environment
        value: production
        action: upsert
      - key: http.request_header.authorization
        action: delete  # 删除敏感信息

  # 采样(尾部采样)
  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: sample
        type: probabilistic
        probabilistic:
          sampling_percentage: 10

  # 资源处理
  resource:
    attributes:
      - key: deployment.environment
        value: production
        action: upsert

extensions:
  health_check:
    endpoint: 0.0.0.0:13133
  zpages:
    endpoint: 0.0.0.0:55679

exporters:
  # 导出到 Jaeger
  otlp/jaeger:
    endpoint: jaeger:4317
    tls:
      insecure: true

  # 导出到 Prometheus(通过 remote_write)
  prometheusremotewrite:
    endpoint: http://prometheus:9090/api/v1/write

  # 导出到 Loki
  loki:
    endpoint: http://loki:3100/loki/api/v1/push

  # 导出到 Elasticsearch
  elasticsearch:
    endpoints:
      - http://es:9200
    index: otel-logs

service:
  extensions: [health_check, zpages]
  pipelines:
    traces:
      receivers: [otlp, jaeger]
      processors: [memory_limiter, filter, tail_sampling, resource, batch]
      exporters: [otlp/jaeger]

    metrics:
      receivers: [otlp, prometheus]
      processors: [memory_limiter, filter, resource, batch]
      exporters: [prometheusremotewrite]

    logs:
      receivers: [otlp]
      processors: [memory_limiter, filter, attributes, resource, batch]
      exporters: [loki, elasticsearch]

3.3 Collector 部署模式

模式部署位置用途资源消耗
Agent应用节点旁采集本地数据
Gateway独立集群集中处理和转发中-高
SidecarPod 内K8s 应用旁

生产推荐:Agent + Gateway 两层架构

应用节点 → Agent Collector → Gateway Collector → 后端
              (本地采集)        (集中处理)

3.4 K8s 部署

# DaemonSet 模式(Agent Collector)
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: otel-collector-agent
  namespace: monitoring
spec:
  selector:
    matchLabels:
      app: otel-collector-agent
  template:
    metadata:
      labels:
        app: otel-collector-agent
    spec:
      containers:
        - name: collector
          image: otel/opentelemetry-collector-contrib:0.103.0
          args: ["--config=/etc/otel/config.yaml"]
          ports:
            - containerPort: 4317  # OTLP gRPC
            - containerPort: 4318  # OTLP HTTP
            - containerPort: 13133  # Health check
          volumeMounts:
            - name: config
              mountPath: /etc/otel
          resources:
            limits:
              cpu: 500m
              memory: 512Mi
            requests:
              cpu: 100m
              memory: 128Mi
      volumes:
        - name: config
          configMap:
            name: otel-collector-config

四、SDK 自动埋点

4.1 Go 语言示例

package main

import (
    "context"
    "log"
    "net/http"
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
    "go.opentelemetry.io/otel/propagation"
    "go.opentelemetry.io/otel/sdk/resource"
    sdktrace "go.opentelemetry.io/otel/sdk/trace"
    semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
    "go.opentelemetry.io/otel/trace"
    "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)

func initTracer() func() {
    // 创建 OTLP exporter
    exporter, err := otlptracehttp.New(context.Background(),
        otlptracehttp.WithEndpoint("otel-collector:4318"),
        otlptracehttp.WithInsecure(),
    )
    if err != nil {
        log.Fatalf("Failed to create exporter: %v", err)
    }

    // 创建 Resource(标识当前服务)
    res, _ := resource.New(context.Background(),
        resource.WithAttributes(
            semconv.ServiceName("order-service"),
            semconv.ServiceVersion("1.0.0"),
            semconv.DeploymentEnvironment("production"),
        ),
    )

    // 创建 TracerProvider
    tp := sdktrace.NewTracerProvider(
        sdktrace.WithBatcher(exporter),
        sdktrace.WithResource(res),
        sdktrace.WithSampler(sdktrace.TraceIDRatioBased(0.1)), // 10% 采样
    )
    otel.SetTracerProvider(tp)
    otel.SetTextMapPropagator(propagation.TraceContext{})

    return func() {
        tp.Shutdown(context.Background())
    }
}

func main() {
    shutdown := initTracer()
    defer shutdown()

    // 使用 otelhttp 自动埋点 HTTP 请求
    handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        tracer := otel.Tracer("order-service")
        ctx, span := tracer.Start(r.Context(), "processOrder",
            trace.WithAttributes(
                semconv.HTTPMethod(r.Method),
                semconv.HTTPTarget(r.URL.Path),
            ),
        )
        defer span.End()

        // 模拟业务逻辑
        processOrder(ctx, r.URL.Query().Get("order_id"))

        w.Write([]byte(`{"status": "ok"}`))
    })

    wrappedHandler := otelhttp.NewHandler(handler, "HTTP")
    http.ListenAndServe(":8080", wrappedHandler)
}

func processOrder(ctx context.Context, orderID string) {
    tracer := otel.Tracer("order-service")
    _, span := tracer.Start(ctx, "processOrder",
        trace.WithAttributes(
            attribute.String("order.id", orderID),
        ),
    )
    defer span.End()

    // 模拟数据库查询
    queryDatabase(ctx, orderID)
}

func queryDatabase(ctx context.Context, query string) {
    tracer := otel.Tracer("order-service")
    _, span := tracer.Start(ctx, "db.query",
        trace.WithAttributes(
            attribute.String("db.system", "mysql"),
            attribute.String("db.statement", query),
        ),
    )
    defer span.End()
    // 数据库查询逻辑...
}

4.2 自动埋点库

OTel 为多种语言和框架提供了自动埋点库,无需修改业务代码:

语言HTTP 框架数据库RPC
Gonet/http, Gin, Echodatabase/sql, gormgRPC
JavaSpring, ServletJDBC, HibernategRPC
PythonFlask, DjangoSQLAlchemy, psycopg2gRPC
Node.jsExpress, Fastifymysql, pggRPC
.NETASP.NET CoreADO.NET, EF CoregRPC

Java Spring Boot 自动埋点示例

// 添加依赖
// build.gradle
dependencies {
    implementation 'io.opentelemetry.instrumentation:opentelemetry-spring-boot-starter:1.32.0'
}

// application.yml
otel:
  exporter:
    otlp:
      endpoint: http://otel-collector:4318
  service:
    name: order-service
    version: 1.0.0
  traces:
    sampler:
      type: parentbased_traceidratio
      arg: 0.1

只需添加依赖和配置,Spring Boot 的 HTTP 请求、数据库查询、Kafka 消费等都会自动产生 Span,无需修改任何业务代码。

4.3 日志关联 Trace

// Java 使用 MDC 自动注入 TraceID
import org.slf4j.MDC;

// OTel SDK 自动将 TraceID 写入 MDC
// 日志格式中包含 %X{trace_id} 和 %X{span_id}

// logback.xml
<pattern>
  %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level [%X{trace_id},%X{span_id}] %logger - %msg%n
</pattern>

// 输出示例:
// 2026-07-10 10:00:00 [http-nio-8080-exec-1] ERROR [7b3cf5b0123456789abcdef012345678,0123456789abcdef] OrderService - Failed to process order

在 Kibana 或 Loki 中搜索该 TraceID,可以直接跳转到 Jaeger 的 Trace 视图。

五、与后端集成

5.1 集成矩阵

后端TracesMetricsLogs协议
JaegerOTLP / Jaeger
ZipkinOTLP / Zipkin
TempoOTLP
Prometheusremote_write
Mimirremote_write
VictoriaMetricsOTLP / remote_write
ElasticsearchOTLP / ES API
LokiOTLP / Loki API
DatadogOTLP / Datadog API
New RelicOTLP
HoneycombOTLP
Grafana CloudOTLP

5.2 Grafana 全栈集成

OTel + Grafana 全栈是最常见的开源组合:

应用代码 (OTel SDK)
OTel Collector
    ├── Traces → Tempo
    ├── Metrics → Mimir / Prometheus
    └── Logs → Loki
        Grafana (统一可视化)

在 Grafana 中,三种信号通过 TraceID 关联:

  1. 在 Metrics 仪表盘看到错误率飙升
  2. 点击异常时间段的链接,跳转到 Logs 搜索
  3. 在 Logs 中找到错误日志,点击 TraceID
  4. 跳转到 Traces 视图,定位到具体服务和方法

5.3 商业后端集成

OTel 也支持发送到商业可观测性平台:

exporters:
  # Datadog
  datadog:
    api:
      key: ${DD_API_KEY}
      site: datadoghq.com

  # New Relic
  otlp/newrelic:
    endpoint: otlp.nr-data.net:4317
    headers:
      api-key: ${NEW_RELIC_LICENSE_KEY}

  # Honeycomb
  otlp/honeycomb:
    endpoint: api.honeycomb.io:4317
    headers:
      "x-honeycomb-team": ${HONEYCOMB_API_KEY}

后端无关性的价值:使用 OTel 后,从开源后端迁移到商业后端只需修改 Collector 配置,不需要改应用代码。

六、采样策略

6.1 头部采样 vs 尾部采样

策略采样位置优势劣势
头部采样请求入口处简单,资源消耗低无法基于结果采样(可能漏掉错误请求)
尾部采样请求完成后可基于结果采样(保留所有错误请求)需要缓存完整 Trace,资源消耗高

6.2 尾部采样配置

processors:
  tail_sampling:
    decision_wait: 10s        # 等待 10s 收集完整 Trace
    num_traces: 50000          # 内存中缓存的 Trace 数
    expected_new_traces_per_sec: 1000

    policies:
      # 策略 1:保留所有错误请求
      - name: errors
        type: status_code
        status_code:
          status_codes: [ERROR]

      # 策略 2:保留慢请求(> 1s)
      - name: slow
        type: latency
        latency:
          threshold_ms: 1000

      # 策略 3:保留特定服务的请求
      - name: critical-service
        type: string_attribute
        string_attribute:
          key: service.name
          values: ["payment-service", "order-service"]

      # 策略 4:其余 10% 采样
      - name: sample
        type: probabilistic
        probabilistic:
          sampling_percentage: 10

尾部采样的效果:

1000 个请求 → 尾部采样 → 保留的 Trace:
  - 5 个错误请求 → 全部保留
  - 20 个慢请求 → 全部保留
  - 100 个关键服务请求 → 全部保留
  - 其余 875 个 × 10% → 87 个采样保留
  总计: 5 + 20 + 100 + 87 = 212 个 Trace (21.2%)

七、迁移策略

7.1 从现有方案迁移到 OTel

现有方案迁移路径
Jaeger Client → OTelOTel SDK 替换 Jaeger Client,Collector 转发到 Jaeger
Prometheus Client → OTelOTel Metrics SDK + Collector remote_write 到 Prometheus
Logback/Log4j → OTelOTel Logs SDK + Collector 导出到 ELK/Loki
Datadog → OTelOTel SDK 替换 Datadog Agent,Collector 导出到 Datadog

7.2 渐进式迁移步骤

阶段 1:部署 Collector(不改动应用)
  → Collector 接收现有格式数据,转发到现有后端
  → 验证 Collector 稳定

阶段 2:应用接入 OTel SDK(Traces 先行)
  → 新服务使用 OTel SDK
  → 老服务逐步替换
  → Collector 同时接收新旧格式

阶段 3:接入 Metrics 和 Logs
  → 应用日志关联 TraceID
  → Metrics 通过 OTel SDK 采集

阶段 4:统一后端
  → 所有数据通过 Collector 统一处理
  → 按需切换后端

7.3 兼容性配置

Collector 可以同时接收新旧格式数据,实现平滑迁移:

receivers:
  # 新格式:OTel SDK 发送
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317

  # 旧格式:Jaeger Client 发送
  jaeger:
    protocols:
      thrift_http:
        endpoint: 0.0.0.0:14268

  # 旧格式:Prometheus 拉取
  prometheus:
    config:
      scrape_configs:
        - job_name: 'legacy-apps'
          static_configs:
            - targets: ['app-1:9090']

八、性能与资源考量

8.1 SDK 性能影响

因素影响建议
Span 数量CPU 和内存合理采样,过滤健康检查
属性数量网络和存储限制每 Span 属性数
批量大小网络效率调整 batch 大小和超时
采样率数据量和资源生产环境 1-10% 采样

8.2 Collector 资源规划

规模CPU内存副本数
< 1 万 Span/s1 核512MB2
1-10 万 Span/s2 核1GB3
10-50 万 Span/s4 核2GB3-5
> 50 万 Span/s8 核4GB5+

8.3 Collector 调优

processors:
  batch:
    timeout: 5s               # 批量超时
    send_batch_size: 1000     # 批量大小
    send_batch_max_size: 2000 # 最大批量

  memory_limiter:
    check_interval: 1s        # 检查间隔
    limit_mib: 1024           # 内存上限
    spike_limit_mib: 256      # 突发内存

service:
  telemetry:
    logs:
      level: info              # 日志级别
    metrics:
      address: 0.0.0.0:8888   # 自身指标端口

九、生产实践

9.1 资源标识规范

# Resource 属性规范
resource:
  attributes:
    # 必须属性
    service.name: "order-service"           # 服务名
    service.version: "1.2.3"               # 版本号

    # 推荐属性
    deployment.environment: "production"    # 环境
    host.name: "order-pod-abc"              # 主机名
    k8s.namespace.name: "production"        # K8s 命名空间
    k8s.pod.name: "order-pod-abc"           # K8s Pod 名

    # 可选属性
    service.instance.id: "uuid-xxxx"       # 实例 ID
    cloud.provider: "aws"                  # 云提供商
    cloud.region: "us-east-1"             # 区域

9.2 监控 OTel 自身

# Prometheus 采集 Collector 指标
scrape_configs:
  - job_name: 'otel-collector'
    static_configs:
      - targets: ['otel-collector:8888']

# 关键告警
groups:
  - name: otel
    rules:
      - alert: OTelCollectorDroppingData
        expr: rate(otelcol_processor_refused_spans[5m]) > 0
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "OTel Collector 正在丢弃 Span 数据"

      - alert: OTelCollectorQueueFull
        expr: otelcol_exporter_queue_size / otelcol_exporter_queue_capacity > 0.9
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "OTel Collector 导出队列接近满"

总结

OpenTelemetry 正在成为可观测性领域的统一标准:

  • 统一标准:一套 SDK/API 采集三大信号(Metrics/Logs/Traces),消除数据采集层碎片化
  • 后端无关:通过 Collector 解耦采集和后端,切换后端只需修改配置,不改代码
  • Context 关联:TraceID 自动关联日志和指标,实现三种信号的关联分析
  • 自动埋点:丰富的语言/框架库支持自动埋点,降低接入成本
  • 采样策略:尾部采样保留所有错误和慢请求,同时降低正常请求的存储成本
  • 渐进迁移:Collector 兼容新旧格式,可以平滑迁移而不中断现有监控

OTel 不替代后端(Prometheus/Jaeger/ELK 仍然各司其职),而是解决数据采集层的统一问题。如果你正在构建新的可观测性体系,OTel 应该是采集层的默认选择。已有系统也可以通过 Collector 渐进式迁移,享受统一标准带来的长期收益。

参考资料与致谢

本文在撰写过程中参考了以下资料,感谢原作者的贡献:

  1. OpenTelemetry 官方文档 — OpenTelemetry 社区,参考了OpenTelemetry 官方文档相关内容
  2. CNCF OpenTelemetry 规范 — GitHub 开源社区,参考了CNCF OpenTelemetry 规范相关内容