OpenTelemetry 概述

2025-11-11T14:11:02+08:00 | 17分钟阅读 | 更新于 2025-11-11T14:11:02+08:00

@

学习目标

学完本章你应该能够:

  1. 说清可观测性三大支柱(Metrics / Traces / Logs)分别回答什么问题、数据形态和存储特点有何不同。
  2. 讲清 OpenTelemetry 的核心理念(统一标准、供应商无关、自动仪表化),以及"供应商无关"为什么能省掉重写埋点的大坑。
  3. 画出 OTel 的整体数据流:应用代码 → API → SDK → Exporter(OTLP)→ Collector(Receiver/Processor/Exporter)→ 后端存储。
  4. 解释 Collector 里 memory_limiter → resource → filter → attributes → batch → tail_sampling 这套 pipeline 顺序为什么不能乱。
  5. 把"三大支柱关联做 AIOps 根因定位"讲成一个闭环:Metric 异常 → Trace 定位瓶颈 → Log 确认根因。

前置知识

  • 一点 Prometheus / Grafana 使用经验(理解指标与看板)
  • 分布式系统基础(一次请求跨多个服务)
  • Go / Java / Python 任一门的埋点概念(无需精通)

本章你会动手做的事

  1. 对照文末的对比表,给自己系统的"指标 / 链路 / 日志"分别归个类,看哪类数据量最大、最该采样。
  2. 抄一遍 Collector 的 processor pipeline 顺序,并标注 memory_limiterbatch 为什么必须放首尾。
  3. 用文末"三支柱关联"的 5 步流程,在一张纸上画出"order-service 变慢"的排查路径。

可观测性三大支柱

可观测性(Observability)是指通过系统的外部输出推断其内部状态的能力。现代分布式系统的可观测性通常围绕三大支柱展开。

类比:把系统想象成一台复杂的机器。Metrics 像仪表盘上的总压、温度表——一眼知道"健不健康";Traces 像产品的物流单——查得到"这件请求在哪些车间转了一圈、卡在哪";Logs 像车间里的流水记录——出了事能翻到"具体哪秒发生了什么"。三者结合起来,你才能既知道"病了",又知道"病在哪、怎么得的"。

flowchart TB
    O["可观测性
由外部输出推断内部状态"] --> M["Metrics
聚合度量 回答'是否健康'"] O --> T["Traces
调用链 回答'慢在哪'"] O --> L["Logs
离散事件 回答'发生了什么'"]

不过我得先泼盆冷水:很多团队上线可观测性系统的第一周热情高涨,第二周就发现"啥都看到了但啥也看不懂"。三大支柱本身只是数据源,能不能用起来还得看你怎么采集、怎么关联、怎么分析。下面我尽量把每根支柱都讲细一点,包括它们到底长啥样。

指标(Metrics)

指标是对系统状态的聚合度量,通常是数值型时间序列数据。例如:

  • CPU 使用率、内存占用
  • 请求 QPS、错误率、P99 延迟
  • 队列深度、连接数

指标的特点是低维度、可聚合、适合告警和趋势分析。

光说概念有点空,来看一条真实 metric 长什么样。这是 Prometheus exposition format 的原始输出,没经过任何加工:

# HELP http_requests_total Total number of HTTP requests
# TYPE http_requests_total counter
http_requests_total{method="GET",status="200",path="/api/orders"} 14352 1731319860000
http_requests_total{method="POST",status="500",path="/api/orders"} 17 1731319860000
# HELP http_request_duration_seconds HTTP request duration
# TYPE http_request_duration_seconds histogram
http_request_duration_seconds_bucket{le="0.005",path="/api/orders"} 12030
http_request_duration_seconds_bucket{le="0.01",path="/api/orders"} 13500
http_request_duration_seconds_bucket{le="0.1",path="/api/orders"} 14300
http_request_duration_seconds_bucket{le="+Inf",path="/api/orders"} 14369
http_request_duration_seconds_sum{path="/api/orders"} 421.88
http_request_duration_seconds_count{path="/api/orders"} 14369

几个关键点说一下:

  • # HELP / # TYPE 是 Prometheus 的元信息行,告诉抓取方这是个啥指标、什么类型。
  • counter 类型只能单调递增,所以你看到的 14352 是"从启动到现在的累计请求数",想看 QPS 得用 rate() 函数。
  • histogram 是个"分桶"结构,le="0.005" 表示"≤5ms 的请求有 12030 个",靠这一堆桶算 P99。
  • 后面那个数字 1731319860000 是毫秒级时间戳,可选,没写就由抓取方打时间戳。

我踩过一次坑:把 counter 类型当成 gauge 用,结果告警曲线一路往下走,因为重启后归零了。counter 只能加不能减,要表达"当前值"用 gauge,别搞混。

链路(Traces)

链路追踪记录一次请求在分布式系统中的完整路径。每个请求被拆分为多个 Span,Span 之间通过 TraceID 关联,形成调用树。

链路数据的价值在于:

  • 定位性能瓶颈
  • 分析服务依赖
  • 理解请求在微服务间的流转

一个真实的 Span(OTLP JSON 格式)长这样:

{
  "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
  "span_id": "00f067aa0ba902b7",
  "parent_span_id": "b7ad6b7169203331",
  "name": "GET /api/orders",
  "kind": "SPAN_KIND_SERVER",
  "start_time_unix_nano": 1731319860000000000,
  "end_time_unix_nano": 1731319861524000000,
  "attributes": {
    "http.method": "GET",
    "http.url": "https://shop.example.com/api/orders",
    "http.status_code": 200,
    "peer.service": "nginx"
  },
  "events": [
    {
      "name": "cache-miss",
      "time_unix_nano": 1731319860050000000,
      "attributes": {"cache.key": "user:123"}
    }
  ],
  "status": {
    "code": "STATUS_CODE_OK"
  },
  "resource": {
    "service.name": "order-service",
    "host.name": "order-pod-7d8b",
    "k8s.namespace.name": "production"
  }
}

几个字段你必须看懂:

  • trace_id 贯穿整条链路,所有相关 span 共享;span_id 是这个 span 自己的。
  • parent_span_id 决定了调用树结构,根 span 这个字段为空。
  • attributes 是业务自定义属性,这里塞 http.status_codeuser.id 这种,是排查问题的关键。
  • events 是 span 内部的"事件点",比如 cache-miss、retry,比 attributes 更强调"发生在某个时刻"。
  • resource 描述这个 span 是"谁"产生的,service.name 是必填,没它整条 trace 就乱了。

这里有个坑:很多人不会用 events,全往 attributes 里塞。但 attributes 是 span 级别的"标签",events 是"带时间戳的日志点"。你想记录"第 50ms 时缓存未命中",必须用 event,attribute 没有时间维度。

flowchart TB
    R[根 Span: GET /api/orders] --> S1[Span: nginx 转发]
    R --> S2[Span: 查缓存]
    R --> S3[Span: 查数据库]
    S2 -. cache-miss event .-> E[第 50ms 缓存未命中]
    S1 -->|parent_span_id 关联| R
    S2 -->|parent_span_id 关联| R
    S3 -->|parent_span_id 关联| R

日志(Logs)

日志是离散的事件记录,包含时间戳、级别、消息和上下文。日志适合记录详细的业务事件和错误信息,是故障排查的重要依据。

一条 OTel 风格的 LogRecord 长这样:

{
  "time_unix_nano": 1731319860123000000,
  "observed_time_unix_nano": 1731319860125000000,
  "severity_number": 17,
  "severity_text": "ERROR",
  "body": "order creation failed: redis timeout",
  "attributes": {
    "order.id": "ord-9982",
    "user.id": "u-123",
    "redis.cmd": "SETNX",
    "redis.timeout_ms": 2000
  },
  "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
  "span_id": "00f067aa0ba902b7",
  "resource": {
    "service.name": "order-service"
  }
}

注意 trace_idspan_id 字段——这是 OTel 日志和传统日志最大的区别。一条日志如果能跟 trace 关联起来,你就能从 trace 跳到日志,从日志跳回 trace,排查效率直接翻倍。severity_number 是数字化的日志级别(ERROR=17, WARN=13, INFO=9, DEBUG=5),方便机器处理。

说实话 OTel 的日志支柱是三个里最不成熟的,Go SDK 的 logs 到现在还标 experimental。我个人的建议是:新项目可以直接上 OTel logs,老项目先用现有日志方案,通过 Collector 把日志"转成"OTLP 格式注入 trace_id,平滑迁移。

支柱数据形态典型问题存储特点
Metrics时间序列数值系统是否健康?数据量小、保留时间长
Traces请求调用链请求慢在哪里?数据量大、采样存储
Logs离散事件具体发生了什么?数据量大、需索引

OpenTelemetry 简介

OpenTelemetry(简称 OTel)是 CNCF 孵化的开源项目,旨在为可观测性数据提供统一的采集、导出和传输标准。它由 OpenTracing 和 OpenCensus 合并而来,是目前云原生可观测性领域的事实标准。

OpenTelemetry 的核心理念:

  • 统一标准:一套 API、SDK 和协议覆盖 Metrics、Traces、Logs。
  • 供应商无关:采集端与后端存储解耦,可自由切换 Prometheus、Jaeger、Grafana、Datadog 等。
  • 自动仪表化:多种语言的自动埋点,降低接入成本。

我个人觉得"供应商无关"这点被严重低估了。早期我们用 A 厂的 APM,后来想换 B 厂,整个埋点代码重写一遍,几个月才换完。OTel 的好处是应用代码只跟 OTel API 打交道,后端怎么换都不动业务代码,最多改 Collector 配置。

OpenTelemetry 架构

OpenTelemetry 的整体架构可分为四层。原文里那张图太简陋了,我重画一张带数据流向的:

flowchart LR
    subgraph 应用进程
        A1[应用代码] --> A2[OTel API
门面 不做具体事] A2 --> A3[SDK
生成/采样/批处理] A3 --> A4[Exporter
OTLP gRPC/HTTP] end A4 -->|"4317 / 4318"| C[OpenTelemetry Collector] subgraph Collector C --> R[Receiver
接收] R --> P[Processor
过滤/聚合/采样] P --> E[Exporter
分发] end E --> B1[(Prometheus)] E --> B2[(Jaeger)] E --> B3[(Loki)]
                     应用进程                                   集群内独立进程                  外部存储
+------------------------------------------------+      +-------------------------+      +-------------+
|  应用代码(业务逻辑)                            |      |  OpenTelemetry Collector |      |             |
|    |                                           |      |    |                    |      |  Prometheus |
|    | 调用 OTel API                              |      |    v                    |      |  (metrics)  |
|    v                                           | OTLP |  Receiver               |      +-------------+
|  SDK (TracerProvider / MeterProvider)         |----->|  (otlp/grpc:http)        |      +-------------+
|    |                                           | (4317|    |                    |
|    | 生成 + 批处理 + 采样                       | /4318|    v                    |      +-------------+
|    v                                           |      |  Processor              |      |   Jaeger    |
|  Exporter (OTLP gRPC/HTTP)                    |      |  (batch/resource/       |      |  (traces)   |
|    |                                           |      |   filter/采样)          |      |             |
|    +-------------------------------------------+------+-->+-------+              |      +-------------+
|                                                       |    |                    |      |             |
|  自动埋点 Agent(Java/Python/.NET)                   |    v                    |      |    Loki     |
|  - 字节码注入,无需改业务代码                          |  Exporter               |      |   (logs)    |
|  - HTTP/DB/gRPC 自动 span                             |  (prometheusremotewrite|      |             |
|                                                       |   /otlp/loki)           |      +-------------+
+-------------------------------------------------------+-------------------------+
                                                                |
                                                                v
                                                          +-------------+
                                                          |  扩展能力    |
                                                          |  health_check|
                                                          |  zpages      |
                                                          |  pprof       |
                                                          +-------------+

几个关键数据流走向要说清楚:

  1. 应用进程里通过 OTel API 创建 span/metric/log,API 本身不做任何事,只是个门面。
  2. SDK 实现 API,负责生成、采样、批处理,然后通过 Exporter 把数据发出去。
  3. 数据通过 OTLP 协议(gRPC 默认 4317,HTTP 4318)发到 Collector。
  4. Collector 做中心化的处理:过滤、聚合、加密、再分发到多个后端。
  5. 同一份 trace 数据可以同时发给 Jaeger 和 ES,互不影响。

Instrumentation

Instrumentation 是指对应用程序进行埋点,生成可观测数据。方式包括:

  • 自动埋点:通过 Agent 或库自动注入,如 OpenTelemetry Java Agent。
  • 手动埋点:在代码中显式创建 Span、记录 Metric。

自动埋点的配置因语言而异,Java 是最成熟的,启动时挂个 agent 就行:

# Java 自动埋点,加个 -javaagent 就完事
java -javaagent:./opentelemetry-agent.jar \
     -Dotel.service.name=order-service \
     -Dotel.exporter.otlp.endpoint=http://otel-collector:4317 \
     -Dotel.traces.exporter=otlp \
     -Dotel.metrics.exporter=otlp \
     -jar order-service.jar

Python 用 opentelemetry-instrument 命令行包一层:

opentelemetry-instrument \
    --service_name order-service \
    --exporter_otlp_endpoint http://otel-collector:4317 \
    --traces_exporter otlp \
    --metrics_exporter otlp \
    python app.py

Go 没有字节码注入能力,自动埋点是"半自动"——用 otelhttpotelgrpcotelsql 这种库包一层中间件。下面是 Go HTTP 服务的最小自动埋点配置:

// 自动给所有 HTTP 请求加 span,不用手动写 tracer.Start
import (
    "net/http"
    "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)

func main() {
    // 用 otelhttp.NewHandler 包一层,所有路由自动埋点
    mux := http.NewServeMux()
    mux.HandleFunc("/api/orders", handleOrders)

    handler := otelhttp.NewHandler(mux, "http-server")
    // 关键:WithServiceName 等选项可以在这里加
    // 默认 span 名是 "http-server",建议按路由细化
    _ = http.ListenAndServe(":8080", handler)
}

踩坑提示:Go 的自动埋点只能覆盖用了 OTel 中间件的库。比如你用 database/sql 但没用 otelsql 包装,那 SQL 查询就不会出现在 trace 里。这点和 Java agent 不一样,Java 是真的"啥都给你埋上"。

SDK

SDK 负责管理数据的生成、处理和导出。关键组件:

  • TracerProvider:管理 Tracer 实例。
  • MeterProvider:管理 Metric 采集。
  • Exporter:将数据发送到 Collector 或后端。
  • Processor:对 Trace 数据进行批处理、采样。

SDK 的配置主要通过环境变量,这样不重建镜像就能调参数。常见的环境变量我列一下:

# 服务名,所有数据都会带上这个标签,必填
OTEL_SERVICE_NAME=order-service

# 导出协议和端点
OTEL_EXPORTER_OTLP_PROTOCOL=grpc
OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317

# 分别控制三支柱是否导出,关掉不用的支柱省资源
OTEL_TRACES_EXPORTER=otlp
OTEL_METRICS_EXPORTER=otlp
OTEL_LOGS_EXPORTER=none

# 采样策略:parentbased_always_on / parentbased_traceidratio / always_off
# traceidratio 后面跟比例,0.1 就是采 10%
OTEL_TRACES_SAMPLER=parentbased_traceidratio
OTEL_TRACES_SAMPLER_ARG=0.1

# 资源属性,会附在所有数据上,方便后续筛选
OTEL_RESOURCE_ATTRIBUTES=deployment.environment=production,team=order

这里有个反例必须讲:采样率不是越低越好。曾经我们把生产采样率设成 1%,结果一个偶发问题复现了 50 次才抓到一次 trace,排查了一周。后来改成"头部采样 + tail-based sampling"——错误请求和慢请求 100% 采样,正常请求 1% 采样,这样既省钱又不漏关键信息。

OpenTelemetry Collector

Collector 是一个独立进程,负责接收、处理和导出可观测数据。其核心组件:

组件作用
Receiver接收来自应用或其他 Collector 的数据
Processor对数据进行过滤、转换、批处理、采样
Exporter将数据导出到后端存储
Extension提供健康检查、性能分析等辅助能力

Collector 配置示例:

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  batch:
  exporters:
  prometheusremotewrite:
    endpoint: http://prometheus:9090/api/v1/write
  otlp/jaeger:
    endpoint: jaeger:4317
    tls:
      insecure: true

service:
  pipelines:
    metrics:
      receivers: [otlp]
      processors: [batch]
      exporters: [prometheusremotewrite]
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [otlp/jaeger]

光看这个简化配置你不一定理解 Collector 到底怎么用,我把几个高频 processor 单独说一下,这些是生产配置里基本必加的:

processors:
  # 1. batch:攒一批再发,降低后端压力,必加
  batch:
    timeout: 5s           # 最多攒 5s
    send_batch_size: 1024 # 或攒够 1024 条
    send_batch_max_size: 2048

  # 2. memory_limiter:防止 OOM,必加
  # 我踩过坑:高 QPS 应用一上来就把 Collector 撑爆
  memory_limiter:
    check_interval: 1s
    limit_mib: 1500       # 内存上限
    spike_limit_mib: 300  # 突发容忍

  # 3. resource:补全/统一资源属性,方便后续筛选
  resource:
    attributes:
      - key: deployment.environment
        value: production
        action: upsert
      - key: k8s.cluster.name
        value: prod-bj
        action: upsert

  # 4. filter:丢弃某些数据,比如健康检查的 trace
  filter:
    traces:
      span:
        - 'attributes["http.route"] == "/healthz"'
        - 'attributes["http.route"] == "/metrics"'

  # 5. tail_sampling:尾部采样,根据 trace 特征决定是否保留
  # 比头部采样智能,但需要缓存整条 trace,资源消耗大
  tail_sampling:
    decision_wait: 10s
    policies:
      [
        { name: errors, type: status_code, status_code: {status_codes: [ERROR]} },
        { name: slow, type: latency, latency: {threshold_ms: 500} },
        { name: random_low, type: probabilistic, probabilistic: {sampling_percentage: 1} }
      ]

  # 6. attributes:修改/删除属性,做数据脱敏
  attributes:
    actions:
      - key: http.request.header.authorization
        action: delete  # 删掉敏感头
      - key: db.statement
        action: hash    # SQL 文本 hash 掉,不存明文

processor 在 pipeline 里的顺序非常关键,错了会出问题。建议的顺序是:memory_limiter → resource → filter → attributes → batch → tail_samplingmemory_limiter 必须放最前面,否则 OOM 保护来不及生效;batch 必须放最后,因为 batch 之后再处理就要拆批了。

flowchart LR
    RC[Receiver
接收 OTLP] --> ML[memory_limiter
防 OOM 必须最前] ML --> RES[resource
补全资源属性] RES --> FL[filter
丢弃健康检查等] FL --> AT[attributes
脱敏/改字段] AT --> BA[batch
攒批 必须最后] BA --> TS[tail_sampling
尾部智能采样] TS --> EX[Exporter
分发到后端]

踩坑提示:tail_sampling 是个吃内存的大户,它要缓存每条 trace 直到决策完成。我们生产环境 1 万 QPS,tail_sampling 占了 Collector 一半内存。要么调小 decision_wait,要么给它单独部署一个 Collector 实例。

后端存储

常见后端组合:

  • Metrics:Prometheus、Thanos、VictoriaMetrics、Mimir
  • Traces:Jaeger、Tempo、Zipkin
  • Logs:Loki、ELK、ClickHouse

光列名字其实没啥用,关键是每个后端适合存什么、不适合存什么。我做了个对比表,加上了"坑点"列:

后端类型适合存不适合存坑点
PrometheusMetrics时序指标高基数指标、日志label 基数爆炸会撑爆内存,单机不支持长期存储
Thanos / MimirMetrics长期指标实时低延迟查询多了一层对象存储,查询延迟变高
VictoriaMetricsMetrics大规模指标极致查询延迟场景集群版配置复杂,单机版够用就别上集群
JaegerTraces调用链大规模 trace 聚合ES 后端存储成本高,纯内存模式只能短期保留
TempoTraces大规模 trace复杂 trace 搜索必须配合 Loki/LogQL 才能做 trace→log 跳转
LokiLogs容器日志全文索引复杂查询没有 ES 那种倒排索引,复杂搜索慢,但便宜
ElasticsearchLogs全文检索、日志聚合极高写入吞吐资源消耗大,集群运维复杂,磁盘是吞金兽
ClickHouse多元高基数 trace/log小数据量场景学习曲线陡,运维要懂分布式

我个人的选型建议:中等规模集群直接 Prometheus + Loki + Tempo(Grafana 全家桶),运维最省心;大规模或者要做复杂 trace 分析上 Jaeger + ES;ClickHouse 适合那种"啥都存"的场景,但别指望开箱即用。

基于可观测数据的 AIOps 实践

可观测数据是 AIOps 的"燃料"。基于 Metrics、Traces、Logs 可以实现:

  1. 异常检测:利用时序算法检测指标突变、周期性异常。
  2. 根因定位:通过 Trace 分析调用链,定位延迟或错误来源。
  3. 日志聚类:对海量日志进行模式识别,发现未知错误。
  4. 容量预测:基于历史指标预测资源需求。
  5. 智能告警:降低告警噪音,实现告警收敛与关联。

光列条目太干,我把三个最常用的思路展开讲讲。

Trace 异常率分析

trace 数据最适合做"慢请求"和"错误请求"分析。思路是:先算每个 service 的错误率/延迟分位,再跟历史基线比,偏离超过阈值就告警。

对每个 service:
  错误率 = 错误 span 数 / 总 span 数
  P99 延迟 = 该 service 所有 span 延迟的 99 分位
  基线 = 过去 7 天同时段平均错误率 / P99
  
  if 当前错误率 > 基线 * 2 and 当前错误率 > 1%:
      告警 "service X 错误率异常"
  if 当前 P99 > 基线 * 1.5 and 当前 P99 > 500ms:
      告警 "service X 延迟异常"

这里的关键是"同时段基线"——周一上午和周日下午的基线完全不一样,不按时间分桶就全是误报。还有 trace 数据通常采样过,做错误率统计要确认采样策略是不是 head-based 等概率采样,否则统计结果会偏。

Metric 突变检测

metric 异常检测最朴素的方法是"滑动窗口 + 标准差",但生产里基本不够用,因为业务指标有明显的周期性。比较实用的做法是 STL 分解(季节性分解)或者直接用 Prophet:

原始指标 = 趋势项 + 周期项 + 残差项

if abs(残差项) > 3 * std(残差项历史):
    标记为异常

我个人觉得实际落地不用太追求花哨算法,先把"3-sigma + 按星期分桶"做出来,能解决 80% 的问题。剩下 20% 的复杂场景再上 Isolation Forest 或者 LSTM。我曾经见过一个团队上来就搞 LSTM 异常检测,结果训练数据都没洗干净,模型准确率 60%,还不如阈值告警好用。

Log 聚类

日志聚类是把"长得像"的日志归到一类,然后看哪一类突然变多。最经典的算法是 Drain,原理是把日志按 token 长度和位置做 Trie 树分组,把变量部分(比如 order_id、timestamp)抽象成通配符。

原始日志:
  order ord-9982 created by user u-123 in 234ms
  order ord-9983 created by user u-456 in 189ms
  order ord-9984 created by user u-789 in 312ms

聚类后模板:
  order <ID> created by user <ID> in <DURATION>ms

如果这个模板的出现次数从平时 10/min 突然涨到 500/min,
说明订单创建流量异常,触发告警。

Drain 算法本身不复杂,但工程实现要考虑内存和增量更新。我个人推荐直接用现成的:Logreduce、EDLA 或者 Loki 的 pattern parser。自己造轮子除非为了学习,否则不划算。

三支柱关联

AIOps 真正的难点不是单支柱分析,而是把三个支柱关联起来。一个典型的关联流程:

1. metric 异常:order-service P99 延迟从 100ms 涨到 800ms
2. 跳到 trace:找出这段时间内 order-service 的慢 trace
3. 顺着 trace 找到瓶颈:在 redis-client span 上停了 600ms
4. 跳到 log:根据 trace_id 查 redis 慢日志
5. 发现根因:redis 在做 BGSAVE,主线程阻塞
flowchart TD
    M[Metric 异常
order-service P99 100ms→800ms] --> T[跳到 Trace
找慢 span] T --> B[定位瓶颈
redis-client span 停 600ms] B --> L[跳到 Log
按 trace_id 查慢日志] L --> R[发现根因
redis 在做 BGSAVE 主线程阻塞]

这套流程手动做下来要 10 分钟,AIOps 的目标是用代码 30 秒做完。关键是 trace_id 要能在三个数据源间传递——metric 这边用 exemplar 关联 trace,log 这边直接带 trace_id 字段。OTel 在这点上做得不错,但 metric exemplar 在 Prometheus 里支持还不太完善,是个已知短板。

收尾

OpenTelemetry 为云原生应用提供了一套统一、开放、可扩展的可观测性方案。掌握其架构与三大支柱数据,是进行 AIOps 异常检测、根因分析和自动化运维的基础。下一篇笔记将深入 OpenTelemetry 的开发实战。

我个人对 OTel 的态度是"用了就回不去"。一开始接入确实有点成本,SDK 配置、Collector 部署、后端选型都要折腾。但接入之后,加一个新服务、换一个后端、做一个新告警,都是改改配置的事。比起以前每个 APM 厂商一套私有 SDK 的日子,真的是天壤之别。如果你还在纠结要不要上 OTel,我的建议是别纠结了,直接上。


自测题与动手练习

自测题(合上书能答出来,才算懂)

  1. Metrics、Traces、Logs 各自最适合回答什么类型的问题?分别举一个"用错支柱会看不出问题"的场景。
  2. OpenTelemetry 的"供应商无关"具体帮你省掉了什么?为什么换后端不用改业务代码?
  3. 画出 OTel 的数据流:应用代码到后端存储之间经过哪几个关键组件?OTLP 默认的两个端口分别是什么协议?
  4. Collector 的 processor pipeline 为什么必须是 memory_limiter → ... → batch 这个顺序?把 batch 放最前会怎样?
  5. “三支柱关联"做根因定位时,为什么 trace_id 是串联三者的关键?metric 这边的 exemplar 目前有什么短板?

动手练习(建议真做一遍)

  1. 给自己正在维护的服务列一张"三支柱清单”:哪些该做 Metric、哪些该埋 Trace、哪些该留 Log,并标出哪类数据量最大、最该采样。
  2. 照着文中顺序写一份最小 Collector 配置,只保留 memory_limiter → batch → exporter,再用 docker 跑起来,确认能收到 OTLP 数据。
  3. 用 Grafana 把"某服务的 Metric 异常"和"对应 trace_id 的慢 trace"手动联动一次,体会三支柱关联排查的完整闭环。

本章小结

  • 三大支柱各司其职:Metrics 看"是否健康"、Traces 看"慢在哪"、Logs 看"发生了什么",三者结合才能既知病又知因。
  • OpenTelemetry 用"统一标准 + 供应商无关 + 自动仪表化"把采集端与后端解耦,换 APM 厂商不再重写埋点。
  • 数据流是:应用代码 → API → SDK → Exporter(OTLP 4317/4318)→ Collector(Receiver/Processor/Exporter)→ 后端存储。
  • Collector 的 processor 顺序不能乱:memory_limiter 必须在最前防 OOM,batch 必须在最后避免拆批。
  • AIOps 的精髓是三支柱关联:Metric 异常 → Trace 定位瓶颈 → Log 确认根因,而 trace_id 是串联三者的纽带。

下一篇深入 OpenTelemetry 开发实战,从 SDK 初始化到自定义 Instrument 一步步落地。

About Me

没什么想介绍的,一个很大众的码农…

喜欢代码,车,马,真的是 🐎

讨厌别人让我给自己的代码写注释 最厌烦别人的程序没有写注释

目标

学AI,加油!加油!