学习目标
学完本章你应该能够:
- 说清可观测性三大支柱(Metrics / Traces / Logs)分别回答什么问题、数据形态和存储特点有何不同。
- 讲清 OpenTelemetry 的核心理念(统一标准、供应商无关、自动仪表化),以及"供应商无关"为什么能省掉重写埋点的大坑。
- 画出 OTel 的整体数据流:应用代码 → API → SDK → Exporter(OTLP)→ Collector(Receiver/Processor/Exporter)→ 后端存储。
- 解释 Collector 里
memory_limiter → resource → filter → attributes → batch → tail_sampling这套 pipeline 顺序为什么不能乱。 - 把"三大支柱关联做 AIOps 根因定位"讲成一个闭环:Metric 异常 → Trace 定位瓶颈 → Log 确认根因。
前置知识:
- 一点 Prometheus / Grafana 使用经验(理解指标与看板)
- 分布式系统基础(一次请求跨多个服务)
- Go / Java / Python 任一门的埋点概念(无需精通)
本章你会动手做的事:
- 对照文末的对比表,给自己系统的"指标 / 链路 / 日志"分别归个类,看哪类数据量最大、最该采样。
- 抄一遍 Collector 的 processor pipeline 顺序,并标注
memory_limiter和batch为什么必须放首尾。 - 用文末"三支柱关联"的 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_code、user.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_id 和 span_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 |
+-------------+
几个关键数据流走向要说清楚:
- 应用进程里通过 OTel API 创建 span/metric/log,API 本身不做任何事,只是个门面。
- SDK 实现 API,负责生成、采样、批处理,然后通过 Exporter 把数据发出去。
- 数据通过 OTLP 协议(gRPC 默认 4317,HTTP 4318)发到 Collector。
- Collector 做中心化的处理:过滤、聚合、加密、再分发到多个后端。
- 同一份 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 没有字节码注入能力,自动埋点是"半自动"——用 otelhttp、otelgrpc、otelsql 这种库包一层中间件。下面是 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_sampling。memory_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
光列名字其实没啥用,关键是每个后端适合存什么、不适合存什么。我做了个对比表,加上了"坑点"列:
| 后端 | 类型 | 适合存 | 不适合存 | 坑点 |
|---|---|---|---|---|
| Prometheus | Metrics | 时序指标 | 高基数指标、日志 | label 基数爆炸会撑爆内存,单机不支持长期存储 |
| Thanos / Mimir | Metrics | 长期指标 | 实时低延迟查询 | 多了一层对象存储,查询延迟变高 |
| VictoriaMetrics | Metrics | 大规模指标 | 极致查询延迟场景 | 集群版配置复杂,单机版够用就别上集群 |
| Jaeger | Traces | 调用链 | 大规模 trace 聚合 | ES 后端存储成本高,纯内存模式只能短期保留 |
| Tempo | Traces | 大规模 trace | 复杂 trace 搜索 | 必须配合 Loki/LogQL 才能做 trace→log 跳转 |
| Loki | Logs | 容器日志 | 全文索引复杂查询 | 没有 ES 那种倒排索引,复杂搜索慢,但便宜 |
| Elasticsearch | Logs | 全文检索、日志聚合 | 极高写入吞吐 | 资源消耗大,集群运维复杂,磁盘是吞金兽 |
| ClickHouse | 多元 | 高基数 trace/log | 小数据量场景 | 学习曲线陡,运维要懂分布式 |
我个人的选型建议:中等规模集群直接 Prometheus + Loki + Tempo(Grafana 全家桶),运维最省心;大规模或者要做复杂 trace 分析上 Jaeger + ES;ClickHouse 适合那种"啥都存"的场景,但别指望开箱即用。
基于可观测数据的 AIOps 实践
可观测数据是 AIOps 的"燃料"。基于 Metrics、Traces、Logs 可以实现:
- 异常检测:利用时序算法检测指标突变、周期性异常。
- 根因定位:通过 Trace 分析调用链,定位延迟或错误来源。
- 日志聚类:对海量日志进行模式识别,发现未知错误。
- 容量预测:基于历史指标预测资源需求。
- 智能告警:降低告警噪音,实现告警收敛与关联。
光列条目太干,我把三个最常用的思路展开讲讲。
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,我的建议是别纠结了,直接上。
自测题与动手练习
自测题(合上书能答出来,才算懂):
- Metrics、Traces、Logs 各自最适合回答什么类型的问题?分别举一个"用错支柱会看不出问题"的场景。
- OpenTelemetry 的"供应商无关"具体帮你省掉了什么?为什么换后端不用改业务代码?
- 画出 OTel 的数据流:应用代码到后端存储之间经过哪几个关键组件?OTLP 默认的两个端口分别是什么协议?
- Collector 的 processor pipeline 为什么必须是
memory_limiter → ... → batch这个顺序?把batch放最前会怎样? - “三支柱关联"做根因定位时,为什么 trace_id 是串联三者的关键?metric 这边的 exemplar 目前有什么短板?
动手练习(建议真做一遍):
- 给自己正在维护的服务列一张"三支柱清单”:哪些该做 Metric、哪些该埋 Trace、哪些该留 Log,并标出哪类数据量最大、最该采样。
- 照着文中顺序写一份最小 Collector 配置,只保留
memory_limiter → batch → exporter,再用docker跑起来,确认能收到 OTLP 数据。 - 用 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 一步步落地。