土法炼钢兴趣小组的算法知识备份

【可观测性工程】可观测性全景:Metrics、Logs、Traces、Profiles、Events 五大支柱

文章导航

分类入口
architectureobservability
标签入口
#observability#metrics#logs#traces#profiling#events#opentelemetry#prometheus#jaeger#grafana

目录

可观测性全景:Metrics、Logs、Traces、Profiles、Events 五大支柱

可观测性五大支柱与现代栈全景

在微服务、容器与多云并行的当下,一次订单请求可能跨越十几个服务、三到四个可用区、两套数据中心。过去运维同学盯着一块大屏、几十条 Zabbix 告警就能”看懂”系统的时代,已经彻底过去。可观测性(Observability)不再是”监控”的同义词,而是一套围绕从外部输出推断内部状态的系统工程方法论。本文作为”可观测性工程”系列的第一篇,尝试把这一领域的来龙去脉、五大信号支柱、现代技术栈、国内外生态与工程落地中常见的坑,一次性铺开。

本文的目的,不是给出一份”可观测性是什么”的百科式定义,而是希望帮助读者建立一个可以映射到真实系统、真实工具、真实账单的心智模型。读完之后,读者应当能回答三个问题:第一,给定一个线上故障,我应该从哪个支柱开始查、以什么顺序跳转?第二,我公司目前的技术栈处在演进曲线的哪个阶段,下一步该补齐什么?第三,面对一张动辄数百张组件的”CNCF Landscape”图,我如何快速筛出与自己规模和团队能力匹配的方案?

一、可观测性的起源

1.1 控制论(Cybernetics)中的数学定义

“可观测性”这个词,并不是 2010 年代才被发明的营销名词。它的源头可以一路追溯到 1960 年,匈牙利裔美国控制论学者鲁道夫·卡尔曼(Rudolf E. Kalman)在经典论文《On the General Theory of Control Systems》中,对线性动态系统给出的一个严格定义:

一个系统是可观测的,当且仅当它的全部内部状态,能够在有限时间内,仅通过系统的外部输出被唯一确定。

写成状态空间形式,对于一个线性时不变系统:

x'(t) = A·x(t) + B·u(t)
y(t)  = C·x(t) + D·u(t)

其中 x(t) 是状态向量,u(t) 是外部输入,y(t) 是可观察到的输出。该系统是完全可观测的,当且仅当可观测性矩阵:

O = [ C; C·A; C·A^2; …; C·A^(n-1) ]

是满秩的(rank(O) = nn 是状态维度)。换言之,如果我们把一个数据中心看成一个庞大的动态系统,x(t) 是几十万个服务、几百万个线程、几十亿条连接的瞬时状态,那么我们之所以”看不清”,本质上是因为我们把 y(t) 设计得太稀疏——只有几条 CPU 曲线、几条 5xx 告警——这个矩阵根本远远不满秩。

卡尔曼的定义是严格数学化的,用在软件系统上当然不能照搬,但其中的核心命题却几乎可以原样沿用:可观测性的本质,是输出到状态的反解问题。这也是为什么后来 Charity Majors、Cindy Sridharan 等人都反复强调:“可观测性是系统设计层面的属性,不是一个可以外挂上去的工具”。

1.2 Cindy Sridharan 的 2017 博文

把可观测性这个概念从控制论”搬进”软件工程界的关键一步,是 Cindy Sridharan 在 2017 年发表在 Medium 上的博文《Monitoring and Observability》。这篇不长的文章第一次明确地把 Monitoring 与 Observability 做了切割:

Monitoring is the action of observing and checking the behavior and outputs of a system over time. Observability is a property of a system that allows it to be debugged.

她的论点大致可以概括为三句话。第一,监控是动作,可观测性是属性。第二,监控服务于”已知的未知”(known unknowns),可观测性服务于”未知的未知”(unknown unknowns)。第三,以 Dashboards 和 Alerts 为中心的传统监控体系,在面对 N+1 查询、尾部延迟、长尾错误这类”新类别问题”时会迅速失效,必须补齐高基数(high-cardinality)、高维度(high-dimensionality)的事件数据。

Cindy 的这篇文章之所以重要,是因为它第一次把”为什么 Prometheus + Grafana 还不够”这个工程界的共同困惑,用清晰的语言解释清楚了。在那之后,可观测性才真正脱离”监控 2.0”这种营销话术的泥潭。

1.3 Google SRE Book 的阐述

另一条平行的思想主线来自 Google。2016 年 O’Reilly 出版的《Site Reliability Engineering》(俗称 SRE Book)专门讨论了监控、告警和事后复盘。书中第六章(Monitoring Distributed Systems)强调:

The output of monitoring should drive a small set of decisions, rather than a long list of metrics.

这背后是一个很朴素的工程观:人类 on-call 工程师的注意力是有限的,半夜两点被叫醒时不可能看 200 张 Dashboard。因此监控体系的设计应当围绕四个黄金信号(Four Golden Signals):延迟(Latency)、流量(Traffic)、错误(Errors)、饱和度(Saturation)。这四个信号与后来的 RED(Rate/Errors/Duration)和 USE(Utilization/Saturation/Errors)方法论共同构成了指标层面的最佳实践基石。

SRE Book 没有直接使用 “Observability” 这个词,但它反复提到的 “white-box monitoring”(从系统内部指标观察)和 “black-box monitoring”(从用户视角探测)的区分,已经隐含了”输出 → 状态”的反解思路。特别是白盒监控部分,Google 明确建议:除了 Dashboards,还要有结构化的调试接口(如 /varz/statusz/healthz),允许工程师在不重启、不发版的前提下探查运行时细节。这一点,几乎就是今天 OpenTelemetry 的雏形。

1.4 从”知道系统挂了”到”知道为什么挂”

工程界围绕可观测性讨论的真正痛点,可以用一句话概括:知道系统挂了,和知道为什么挂了,是两件事

传统监控擅长前者。PING 超时、CPU 打满、某个端口连不上、HTTP 5xx 比例超过 1%,这些都是可以用阈值表达的”已知问题”。但在微服务时代,越来越多的故障呈现出一种令人无力的特征:每一项单项指标都看似正常,但用户就是在报障。比如,某个下游依赖的 P99 延迟从 50ms 漂到 180ms,恰好拖慢了调用链中的某一个环节,再叠加上当天线程池大小刚刚被某个发布调小,最终导致上游 API 的 P99 超出 SLO。这种故障,在任何一条单一指标曲线上都是”正常”的。

这就是为什么可观测性的核心命题必须被明确表述为:我们要让系统能够回答任何事前没有预想到的问题。Charity Majors 把这句话写成了她的招牌金句:“Can you debug your production system without shipping new code?”——如果一个生产问题必须加几行日志、重新发布才能定位,那么你的可观测性就不及格。

1.5 可观测性的核心命题

综合上述来源,我们可以把可观测性的核心命题写成如下更工程化的版本:

  1. 外部可测、内部可推。所有系统状态的变化都应能通过外部信号反映出来。
  2. 高基数、高维度。系统必须能记录每一条请求的上下文(用户 ID、版本号、区域、实验分组),并允许按任意维度下钻查询。
  3. 关联性。Metrics、Logs、Traces、Profiles、Events 必须能通过共同的关联键(如 trace_idservice.namehost.name)互相跳转。
  4. 低成本探索。在不重新发布、不修改代码的前提下,能对生产环境发起新的查询。

这四条原则,后面几节会反复出现。它们是评判一个可观测性栈是否”成熟”的最终标准。

二、三支柱到五支柱的演进

2.1 2017 年”三支柱”的确立

“Three Pillars of Observability”这个说法的流行,同样发生在 2017 年前后。Peter Bourgon 在其个人博客中系统地把可观测性切分为三种数据类型:Metrics、Logs、Traces。这种切法之所以能迅速被接受,是因为它精确地对应了当时三类已经独立发展多年的开源生态:

三支柱模型最大的贡献,是给工程师提供了一个清晰的采购清单:要做可观测性,至少要有一套指标系统、一套日志系统、一套追踪系统。后来 OpenTracing(2016)和 OpenCensus(2018)的诞生,进一步在 API 层面把三支柱的接口标准化。2019 年两者合并为 OpenTelemetry(OTel),成为 CNCF 的第二大活跃项目(按 commit 量仅次于 Kubernetes)。

但三支柱的划分并不完美。最明显的两个痛点是:第一,三类数据在存储和查询上的成本差异巨大——Metrics 每指标每天不到几 KB,Logs 可以动辄 TB 级,Traces 如果不采样也会爆炸——三支柱模型把它们并列起来,容易给人一种”三个都要做全量”的错觉。第二,有些问题,三支柱都答不上来,比如:“为什么这个 Go 服务在流量一样的情况下,今天的 CPU 比昨天高 30%?”——这需要的是 CPU 的堆栈级采样,而不是任何单条指标或日志。

2.2 Profiles 作为第四支柱

Profiles(持续性能剖析,Continuous Profiling)进入主流视野,有两个关键节点。

一是 2010 年 Google 发表的论文《Google-Wide Profiling: A Continuous Profiling Infrastructure for Data Centers》。这篇论文详细描述了 Google 内部如何在整个数据中心以很低的采样率(每核每秒几次)持续采集所有进程的 CPU/内存堆栈,用于容量规划和性能优化。它奠定了”持续性能剖析”这一概念。

二是 2020 年前后 Felix Geisendörfer(Parca 项目的核心作者之一)、Frederic Branczyk(前 Prometheus 核心开发者,Polar Signals 创始人)等人把基于 eBPF 的低开销持续剖析能力工程化。Parca、Pyroscope(后被 Grafana Labs 收购)、Polar Signals Cloud 相继出现,标志着 Profiles 正式作为可观测性的第四支柱。

Profiles 与前三支柱在数据模型上有本质差异。它不是时序数值(Metrics),不是离散事件(Logs),也不是调用图(Traces),而是对程序执行栈的统计采样。典型的一份 CPU Profile 长这样(pprof 文本格式简化):

Type: cpu
Time: 2026-04-20 14:00:00
Duration: 30s, Total samples = 2.4s (8%)
Showing nodes accounting for 2.4s, 100% of 2.4s total

      flat  flat%   sum%        cum   cum%
     0.80s 33.33% 33.33%      1.20s 50.00%  encoding/json.(*decodeState).object
     0.50s 20.83% 54.16%      0.50s 20.83%  runtime.mallocgc
     0.30s 12.50% 66.66%      0.30s 12.50%  syscall.Syscall
     …

在 OpenTelemetry 中,Profiles 的数据协议(基于 pprof 演进的 profiles.proto)在 2024 年正式进入 Beta,这是第四支柱被主流标准接纳的里程碑。

2.3 Events 作为第五支柱

第五支柱 Events 的引入,源于工程界的另一个反复出现的痛点:线上故障中,有一大半是”变更引起的”。无论是新版本发布、配置变更、限流阈值调整、数据库 DDL,还是 Kubernetes 的 Pod 调度决策,这些”变更”本身在传统三支柱中都没有一类天然的数据形态。日志系统可以记录,但非常稀疏,且和业务日志混在一起;追踪系统更是完全无视这些带外事件。

CloudEvents 规范(CNCF 毕业项目,v1.0 于 2019 年发布)为这一类”离散变更事件”提供了标准化的数据结构:

{
  "specversion": "1.0",
  "type": "com.example.deploy.completed",
  "source": "/ci/jenkins/job/order-service",
  "subject": "order-service",
  "id": "A234-1234-1234",
  "time": "2026-04-22T10:15:30Z",
  "datacontenttype": "application/json",
  "data": {
    "version": "v2.14.3",
    "cluster": "prod-sh-01",
    "operator": "ltl",
    "rollout_strategy": "canary-10%"
  }
}

类似地,Kubernetes 自身的 Events 资源(kubectl get events)记录了 Pod 调度、OOMKill、镜像拉取失败等运行时事件,这些本质上也是可观测性信号。

为什么 Events 值得成为独立支柱?因为它在根因定位中的价值密度极高。一个经验法则是:在真实的生产故障中,如果你能把 Events 与 Metrics 曲线叠在一张图上,大约 60%-70% 的故障可以被”目视”归因到某次变更。Grafana 的 Annotations、Datadog 的 Events、腾讯云 CLS 的变更事件流,本质上都在解决这同一个问题。

2.4 为什么需要五支柱而不是三支柱

三支柱回答的是”发生了什么”与”哪条调用链慢了”的问题,但在真实故障排查中,还有两个维度的问题三支柱答不上来:

这两个问题正是所谓根因定位的”最后一公里”。在一个成熟的可观测性平台中,典型的排障路径是:

  1. Metrics 告警触发:某服务 P99 延迟突增。
  2. 跳转 Traces:通过 exemplar 跳到一条慢调用的具体 Span。
  3. 跳转 Logs:通过 trace_id 关联到该请求期间的错误日志。
  4. 跳转 Profiles:用 Span 的起止时间在 Continuous Profiling 中检索对应时段的 CPU 热点。
  5. 叠加 Events:发现问题发生前 15 分钟有一次灰度发布。

五支柱缺一不可。

2.5 五支柱关联图

上面流程的可视化,就是本文开头的 landscape-pillars.svg。五根柱子通过三类”桥”互联:

一套合格的可观测性平台,衡量的标准不是”我有几个支柱”,而是”我的支柱之间能不能一键跳转,跳转后时间对齐、维度对齐、上下文对齐”。这一点我们在第九节的坑点部分会反复强调。

三、五种信号的形态、成本与用途

3.1 总览对比表

下表是对五种信号形态、采集方式、查询模型、相对存储成本和典型适用问题的总览。相对存储成本按”★“的多少表示,同一规模(如每日 1 亿次请求)下的数量级对比。

信号 数据形态 典型采集方式 查询模型 相对存储成本 典型问题
Metrics 时间序列(timestamp, label-set, value) Prometheus scrape / OTLP metrics push PromQL / Flux / MetricsQL 趋势、容量、SLO
Logs 离散事件(timestamp, attrs, body) Agent 尾随文件 / stdout / OTLP logs LogQL / SQL / Lucene ★★★ 错误定位、审计
Traces 有向无环图(Span 及其父子关系) SDK 埋点 / eBPF / Service Mesh TraceQL / SpanQL ★★(采样后) 跨服务慢请求
Profiles 堆栈采样(stack, weight, pprof labels) pprof / JFR / async-profiler / eBPF FlameQL / 属性过滤 ★★ CPU/内存/锁热点
Events 结构化消息(CloudEvents 或自定义) K8s API / CI Webhook / 业务打点 属性过滤 / SQL 变更归因

3.2 Metrics:最廉价也最受限

Metrics 是五支柱中最成熟、最廉价的一类。一个典型的 Prometheus 采集配置如下:

# prometheus.yml
global:
  scrape_interval: 15s
  scrape_timeout: 10s
  external_labels:
    cluster: prod-sh-01
    region: cn-east-1

scrape_configs:
  - job_name: order-service
    metrics_path: /metrics
    kubernetes_sd_configs:
      - role: pod
        namespaces:
          names: [trading]
    relabel_configs:
      - source_labels: [__meta_kubernetes_pod_label_app]
        action: keep
        regex: order-service
      - source_labels: [__meta_kubernetes_pod_name]
        target_label: instance
      - source_labels: [__meta_kubernetes_pod_label_version]
        target_label: version

Prometheus 的暴露端点上会看到类似这样的数据:

# HELP http_requests_total Total HTTP requests processed.
# TYPE http_requests_total counter
http_requests_total{method="GET",path="/api/orders",status="200"} 938421
http_requests_total{method="GET",path="/api/orders",status="500"} 37
http_requests_total{method="POST",path="/api/orders",status="200"} 201344

# HELP http_request_duration_seconds Request latency histogram.
# TYPE http_request_duration_seconds histogram
http_request_duration_seconds_bucket{path="/api/orders",le="0.01"} 521003
http_request_duration_seconds_bucket{path="/api/orders",le="0.05"} 823994
http_request_duration_seconds_bucket{path="/api/orders",le="0.1"}  901233
http_request_duration_seconds_bucket{path="/api/orders",le="0.5"}  938321
http_request_duration_seconds_bucket{path="/api/orders",le="+Inf"} 938458
http_request_duration_seconds_sum{path="/api/orders"} 12034.7
http_request_duration_seconds_count{path="/api/orders"} 938458

一条典型的 PromQL 查询——“最近 5 分钟内 order-service 的 P99 延迟按 path 分组”:

histogram_quantile(
  0.99,
  sum by (path, le) (
    rate(http_request_duration_seconds_bucket{job="order-service"}[5m])
  )
)

Metrics 的成本之所以低,是因为 Prometheus 这类 TSDB 对同一 label-set 的值做了极高效的压缩(Facebook Gorilla 算法的变种),每个样本平均只需 1-2 字节。代价则是高基数标签是毒药:如果你不小心把 user_idrequest_idtrace_id 放进 label,Series 数量会爆炸,Prometheus 内存 OOM 只在分秒之间。生产上要严格控制”标签的笛卡尔积规模”,一般每个 Job 不超过 10 万 Series。

3.3 Logs:最灵活也最昂贵

Logs 是最接近”程序员最初调试手段”的信号,其强项是承载任意上下文。现代系统里,结构化 JSON 日志已成事实标准:

{
  "timestamp": "2026-04-22T10:15:32.481Z",
  "level": "ERROR",
  "service.name": "order-service",
  "service.version": "v2.14.3",
  "host.name": "order-prod-sh01-pod-abc123",
  "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
  "span_id": "00f067aa0ba902b7",
  "logger": "com.example.order.PaymentHandler",
  "thread": "http-nio-8080-exec-12",
  "msg": "payment gateway timeout after 3 retries",
  "user_id": 10023421,
  "order_id": "O-20260422-1015-9981",
  "gateway": "alipay",
  "elapsed_ms": 3012
}

一条典型的 LogQL 查询——“最近 15 分钟 order-service 里所有包含 ‘payment gateway timeout’ 的 ERROR 日志,按 gateway 聚合计数”:

sum by (gateway) (
  count_over_time(
    {service_name="order-service", level="ERROR"}
    |~ "payment gateway timeout"
    [15m]
  )
)

Logs 的成本高,主要来自三个来源:一是数据量本身——一条请求日志加上堆栈可以轻松上 KB,日活亿级的应用日均日志动辄几十 TB;二是索引成本——Elasticsearch 为全文检索构建的倒排索引,通常是原始数据的 1.5-2 倍;三是查询成本——无索引扫描在 TB 级上是分钟级响应。Loki 的思路是”只索引 label,不索引正文”,以一定的查询速度为代价换取存储成本的显著下降。ClickHouse 则走了第三条路:列存 + 主键排序 + 稀疏索引,靠硬扫描的极致速度取胜(单节点每秒 GB 级扫描)。

3.4 Traces:跨服务的时间刻度

Trace 是由若干 Span 组成的有向图(严格说是 DAG),每个 Span 描述一次工作单元(可以是 RPC、数据库调用、内部函数段)。一条 OTLP Trace 数据(protobuf 文本形式示意)如下:

resource_spans {
  resource {
    attributes { key: "service.name" value { string_value: "order-service" } }
    attributes { key: "service.version" value { string_value: "v2.14.3" } }
    attributes { key: "deployment.environment" value { string_value: "prod" } }
  }
  scope_spans {
    scope { name: "io.opentelemetry.http" version: "1.29.0" }
    spans {
      trace_id: "4bf92f3577b34da6a3ce929d0e0e4736"
      span_id: "00f067aa0ba902b7"
      parent_span_id: ""
      name: "POST /api/orders"
      kind: SPAN_KIND_SERVER
      start_time_unix_nano: 1745316932481000000
      end_time_unix_nano:   1745316935493000000
      attributes {
        key: "http.method" value { string_value: "POST" }
      }
      attributes {
        key: "http.status_code" value { int_value: 500 }
      }
      status { code: STATUS_CODE_ERROR message: "payment timeout" }
    }
  }
}

一条典型的 TraceQL 查询——“过去 1 小时 order-service 里耗时超过 2 秒且命中了 payment-service 的 Trace”:

{ resource.service.name = "order-service" && duration > 2s }
  && span.service.name = "payment-service"

Trace 数据量理论上与请求数成正比,成本可以很高。生产中常见的控制手段是采样:头部采样(Head-based sampling,SDK 端按比率决定是否上报整条 Trace)快但会丢失稀有故障样本;尾部采样(Tail-based sampling,在 Collector 收齐整条 Trace 后根据错误码/延迟决定是否留下)精确但需要 Collector 具备状态和延迟容忍。大厂的典型做法是:错误 Trace 100% 保留,慢 Trace 100% 保留,其余按 1% 甚至更低采样。

3.5 Profiles:函数级热点归因

Profiles 的数据结构与前三者差异最大,它是一组带权重的调用栈样本。以 Go 的 CPU Profile 为例,底层使用 pprof 格式(gzip 压缩的 protobuf),语义上可以简化为:

sample_type: [ "cpu", "nanoseconds" ]
period_type: "cpu/nanoseconds"

samples:
  - stack:
      - "encoding/json.(*decodeState).object"
      - "encoding/json.(*decodeState).value"
      - "encoding/json.Unmarshal"
      - "main.handleOrder"
      - "net/http.HandlerFunc.ServeHTTP"
    value: [ 823000000 ]   # 823 ms of CPU
    labels:
      service: "order-service"
      endpoint: "POST /api/orders"
      trace_id: "4bf92f3577b34da6a3ce929d0e0e4736"

  - stack: [ ... ]
    value: [ 410000000 ]
    labels: { ... }

关键点在于 labels。pprof 的 labels 机制允许在采样栈上附加任意标签,这是把 Profiles 与 Traces 关联起来的钥匙。例如,Go 运行时从 1.9 起支持 pprof.Labels,在请求入口处打上 trace_id,采样时该标签会跟随整条栈被记录下来,后续在火焰图中就可以按 trace_id 过滤出”这条慢请求的 CPU 到底花在哪些函数里”。

3.6 Events:低量高价值

Events 的数据形态最灵活,关键是 typesourcesubjecttime 四元组。一次 Kubernetes OOMKill 的 Event(kubectl get events -o yaml 简化)如下:

apiVersion: v1
kind: Event
type: Warning
reason: OOMKilling
message: "Container order-service was killed due to OOM"
source:
  component: kubelet
  host: node-sh01-07
involvedObject:
  kind: Pod
  namespace: trading
  name: order-service-7f8c-abc
  labels:
    app: order-service
    version: v2.14.3
firstTimestamp: "2026-04-22T10:14:21Z"
lastTimestamp:  "2026-04-22T10:14:21Z"
count: 1

如果能把这条 Event 和同一时间的 P99 延迟曲线、错误日志自动关联起来,故障定位几乎是”目视完成”。这就是为什么 Events 虽然量极小,却值得作为独立支柱。

3.7 五类信号在一次故障中的协同

为了更直观地展示五种信号如何协同,我们来走一遍一次典型故障的排障轨迹。背景:2026-04-22 晚上 20:15,一名用户下单失败。on-call 工程师收到告警。

第一步,Metrics。告警规则是 order-service 的 5xx 比例超过 1%。Grafana 打开,看到这张熟悉的曲线:20:12 左右 5xx 开始抬升,20:14 达到 8%,同时 P99 延迟从 80ms 飙到 2.5s。到这里,工程师知道”出问题了”,但还不知道原因。

第二步,Events。Grafana 的 annotation 面板上叠加了 CI 的发布事件与 Kubernetes 事件。工程师立刻看到 20:10 有一次 payment-service 的灰度发布,灰度比例 10%。同时 20:13 有一次 order-service 的 Pod OOMKill。两个事件高度可疑。

第三步,Traces。点击 Metrics 图上的 exemplar(Prometheus 2.26+ 的 Histogram 带着 trace_id),跳转到 Jaeger 中一条典型的慢调用。Trace 上可以看到:整条调用链跨 order-service → payment-service → alipay-adapter,其中 payment-service 的 Span 耗时 2.3 秒,是瓶颈。进一步对比新旧版本:v2.14.3 的 Span 中多了一个叫 verifyRiskV2 的子 Span,耗时稳定在 1.8 秒。这是个新代码路径。

第四步,Logs。用 Trace ID 跳到 Loki,筛选 payment-service 在 20:12-20:15 的 ERROR 日志,立即看到:“connection pool exhausted, waiting 1500ms for conn”——连接池不够。

第五步,Profiles。用同一 Trace ID(通过 pprof.labels)在 Pyroscope 中筛选 verifyRiskV2 的 CPU Profile,火焰图显示 70% 的 CPU 花在一个 JSON 反序列化上。

五步定位,约 8 分钟,根因是 v2.14.3 新增的 verifyRiskV2 用了一个默认 10 连接的 HTTP Client 调用外部风控接口,高并发下连接池耗尽。这个例子展示的,正是”五支柱协同”的真实价值。

3.8 信号之间的转换关系

进一步观察五种信号,会发现它们之间存在一种”可降维但不可升维”的单向转换关系。从 Traces 可以聚合出 Metrics(按 service.name 聚合 Span 的数量、错误率、延迟分布),从 Logs 可以解析出 Metrics(把 “订单创建” 这类结构化日志按分钟计数),从 Profiles 可以得到 Metrics(process_cpu_seconds_total)。反过来则不成立:拿到 Metrics 无法推出具体的 Trace,拿到聚合计数无法还原单条日志。

这意味着在数据流水线设计上有一个重要的”生产原则”:让信号尽量从最细粒度的那一层产生,在 Collector 或查询层做聚合。典型的实现是 OTel Collector 的 spanmetrics 连接器,它从 Traces 流实时生成 RED 指标送入 Prometheus;logstometrics 连接器从 Logs 流实时生成计数类指标。这种”一次采集,多次转换”的架构可以显著降低应用侧埋点的复杂度。

3.9 统一的 Resource 模型

把五种信号在逻辑上缝合起来的关键,是共享一个 Resource。在 OpenTelemetry 规范中,Resource 是一组描述”信号来自哪个实体”的属性集合,比如:

resource:
  attributes:
    service.name: "order-service"
    service.version: "v2.14.3"
    service.namespace: "trading"
    deployment.environment: "prod"
    k8s.cluster.name: "prod-sh-01"
    k8s.namespace.name: "trading"
    k8s.pod.name: "order-service-7f8c-abc"
    k8s.container.name: "app"
    host.name: "node-sh01-07"
    host.arch: "amd64"
    os.type: "linux"
    telemetry.sdk.language: "go"
    telemetry.sdk.version: "1.29.0"

无论是 Metrics、Logs、Traces 还是 Profiles,同一个进程产生的所有信号都会共享同一份 Resource。这份 Resource 就是跨支柱关联的主键。一旦团队在 CI 上把 Resource Schema 检查作为硬门槛,大量第九节里提到的坑点会被提前消灭。

四、监控(Monitoring)与可观测性(Observability)的区别

4.1 问题的两种类别

在理论层面,监控与可观测性的区别可以从 Donald Rumsfeld 那句著名的”known unknowns vs unknown unknowns”说起。借用这对概念:

监控关心”系统是否按我预设的方式运行”,可观测性关心”系统当前是什么状态,以及为什么是这个状态”。这是两个完全不同的认知模式。

4.2 工具层面的差异

这种认知差异直接映射到工具选型上。下表是一份更细粒度的对比。

维度 传统监控 现代可观测性
代表工具 Zabbix、Nagios、Cacti、Icinga Prometheus + Jaeger + Loki + Pyroscope + OTel
数据形态 预先聚合的标量指标 高基数结构化事件 + 时序 + 图 + 栈
基数能力 低(标签少、固定) 高(每条事件可携带任意上下文)
采样模型 定时 pull/push,固定周期 pull + push + stream(Trace 逐条、Metric 定时、Profile 持续)
告警模型 阈值/表达式触发 SLO/Burn Rate + 异常检测 + 关联告警
典型输出 Dashboard + 告警邮件/短信 Explore 式自由查询 + 关联跳转 + on-demand ad-hoc
运维角色 运维 DBA SRE + 应用开发同学共同负责
开发侵入性 几乎为零(外部探测) 代码侧有埋点成本,但有 OTel auto-instrumentation 降本
数据粒度 秒级/分钟级聚合 事件级、每请求、每栈帧

但必须指出,“监控 vs 可观测性”不是非此即彼的对立关系。任何成熟系统都需要监控作为 SLO 的保障,也需要可观测性作为根因定位的能力。真实的演进路径通常是”监控 + 可观测性”并存,只是重心随规模变化。

4.3 一个容易被忽视的细节:Dashboard 的”死亡”

Charity Majors 有一句经常被引用的话:“Dashboards are dead。” 这句话当然有夸张成分,真实含义是:预先定义的 Dashboard 只能回答预先定义的问题。如果你的排障靠 Dashboard,那么遇到未预见的问题就只能现做图表,效率极低。

现代可观测性鼓励的是”Explore 模式”:Grafana 的 Explore 面板、Honeycomb 的 BubbleUp、Datadog 的 Watchdog,本质上都是让你从任意一条异常事件出发,随时按任意维度下钻、对比、聚合。Dashboard 从”排障的主战场”退化为”SLO 的展板”,这是工具哲学上的巨变。

4.4 从”告警”到”SLO”的范式迁移

与 Dashboard 的退化相对应,告警的范式也在迁移。传统监控的告警是阈值触发的:“CPU > 80% 连续 5 分钟”、“5xx 超过 1%”。这种告警的问题是:阈值是静态的,无法反映业务重要性;短暂抖动会误报;长期缓慢恶化会漏报。

现代 SRE 实践引入了SLO 驱动的告警。简化模型:

  1. 定义 SLI:可量化的服务质量指标,如”请求成功率”。
  2. 定义 SLO:SLI 在时间窗口上的目标,如”过去 30 天成功率 ≥ 99.9%“。
  3. 计算错误预算(Error Budget):1 - SLO,上例即 0.1%。
  4. 基于”燃烧率”(Burn Rate)告警:如果当前错误速率按比例会在多长时间内耗尽 30 天预算,则触发告警。

典型的多窗口多燃烧率告警规则(Google SRE Workbook 推荐):

groups:
  - name: order-service-slo
    rules:
      - alert: OrderServiceSLOFastBurn
        expr: |
          (
            sum(rate(http_requests_total{job="order-service",status=~"5.."}[5m]))
            /
            sum(rate(http_requests_total{job="order-service"}[5m]))
          ) > (14.4 * 0.001)
          and
          (
            sum(rate(http_requests_total{job="order-service",status=~"5.."}[1h]))
            /
            sum(rate(http_requests_total{job="order-service"}[1h]))
          ) > (14.4 * 0.001)
        for: 2m
        labels: { severity: page }
        annotations:
          summary: "order-service is burning SLO at fast rate"

      - alert: OrderServiceSLOSlowBurn
        expr: |
          (
            sum(rate(http_requests_total{job="order-service",status=~"5.."}[1h]))
            /
            sum(rate(http_requests_total{job="order-service"}[1h]))
          ) > (1 * 0.001)
          and
          (
            sum(rate(http_requests_total{job="order-service",status=~"5.."}[6h]))
            /
            sum(rate(http_requests_total{job="order-service"}[6h]))
          ) > (1 * 0.001)
        for: 15m
        labels: { severity: ticket }

这里 14.41 是 Google SRE Workbook 给出的经典燃烧率系数,对应 30 天、2% 预算的 1 小时和 6 小时窗口。这种告警的关键优点是:短促但严重的问题(快速燃烧)会立刻 Page,缓慢累积的问题(慢速燃烧)会进 Ticket 池不打扰 on-call。

五、现代可观测栈全景

本节以图中六层结构为线索,依次介绍每一层的代表性开源项目和工程选择。

5.1 数据采集层

采集层的任务是把应用运行时的状态转成结构化信号,并从宿主机上送出去。主流做法有三类:

第一类,SDK 埋点。OpenTelemetry SDK(Java、Go、Python、Node.js、.NET 等 11 种语言)提供了统一 API 和 auto-instrumentation。对于 Java,只要挂载 opentelemetry-javaagent.jar 就能自动织入 Servlet、JDBC、Redis、Kafka 等几十种框架的埋点,零代码改动。OTel 的关键优势是规范驱动:它的 Resource Semantic Conventions(例如 service.namehttp.methoddb.system)是标准化的,跨语言可比。

第二类,Agent/Sidecar 采集。以 Fluent Bit、Vector、Filebeat 为代表的日志 Agent 负责尾随文件或容器 stdout。以 node-exporter、cAdvisor、DCGM-exporter 为代表的系统指标 Agent 负责暴露主机层指标。这类 Agent 的优势是对应用零侵入,劣势是语义层面贫瘠(只能拿到主机/容器级别的通用字段)。

第三类,eBPF。这是最近三年增长最快的一类。以 Cilium Hubble、Pixie(Apache 2.0)、DeepFlow(CNCF Sandbox)、Coroot 为代表,eBPF 能在内核层拦截系统调用、网络包、函数调用,无需修改应用代码。典型能力包括 TCP 黄金信号、HTTP/gRPC 请求拓扑、SQL/Redis 协议解析、持续 CPU/内存 Profile。它的代价是内核版本依赖(需 4.18+,推荐 5.10+)和一定的 CPU 开销。

5.2 数据传输层

传输层的核心标准是 OpenTelemetry Protocol(OTLP),它基于 gRPC 或 HTTP/protobuf,在一个连接内可以同时传输 Metrics/Logs/Traces/Profiles。老协议如 Prometheus Remote Write(WAL 式推送)、StatsD、Zipkin Thrift 仍然广泛存在,但未来的主流会是 OTLP。

在中大型场景下,生产者直连存储会导致写入风暴。常见的缓冲层方案:

典型的 OpenTelemetry Collector 配置(生产级别):

# otel-collector-config.yaml
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318
  prometheus:
    config:
      scrape_configs:
        - job_name: otel-collector
          scrape_interval: 15s
          static_configs:
            - targets: ['0.0.0.0:8888']
  filelog:
    include: [ /var/log/apps/*.log ]
    start_at: end
    operators:
      - type: json_parser

processors:
  batch:
    send_batch_size: 8192
    timeout: 5s
  memory_limiter:
    check_interval: 2s
    limit_mib: 1024
    spike_limit_mib: 256
  resource:
    attributes:
      - key: deployment.environment
        value: prod
        action: upsert
  tail_sampling:
    decision_wait: 10s
    num_traces: 100000
    policies:
      - name: errors
        type: status_code
        status_code: { status_codes: [ERROR] }
      - name: slow
        type: latency
        latency: { threshold_ms: 1000 }
      - name: baseline
        type: probabilistic
        probabilistic: { sampling_percentage: 1 }

exporters:
  otlp/tempo:
    endpoint: tempo.obs.svc:4317
    tls: { insecure: true }
  prometheusremotewrite:
    endpoint: http://vmagent.obs.svc:8429/api/v1/write
  loki:
    endpoint: http://loki.obs.svc:3100/loki/api/v1/push

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [memory_limiter, resource, tail_sampling, batch]
      exporters: [otlp/tempo]
    metrics:
      receivers: [otlp, prometheus]
      processors: [memory_limiter, resource, batch]
      exporters: [prometheusremotewrite]
    logs:
      receivers: [otlp, filelog]
      processors: [memory_limiter, resource, batch]
      exporters: [loki]

这份配置示范了生产环境的关键要点:内存限制、批处理、资源标签、尾部采样、按信号分管道。

5.3 数据存储层

按支柱划分,主流存储选择如下:

选型的核心矛盾是写入速率 vs 查询灵活度 vs 存储成本三角。Loki 极致压缩,但对自由文本搜索很慢;ES 查询灵活,但成本高;ClickHouse 是近几年平衡点最好的选择,但运维复杂度高。

5.4 数据查询层

查询语言层面,事实标准是:PromQL(Metrics)、LogQL(Logs)、TraceQL(Traces)。一个成熟的平台会让三者语法尽量一致,并在 UI 层支持统一的时间范围与标签选择器。

PromQL 示例——按版本分组看 P95 延迟,验证灰度发布是否影响性能:

histogram_quantile(
  0.95,
  sum by (version, le) (
    rate(http_request_duration_seconds_bucket{job="order-service"}[5m])
  )
)

LogQL 示例——按主机聚合某错误的增长率:

sum by (host_name) (
  rate(
    {service_name="order-service"} |= "payment gateway timeout" [5m]
  )
)

TraceQL 示例——查找从 gateway 起,跨越 order-servicepayment-service,最终被错误终结的 Trace:

{ span.service.name = "gateway" } >> { span.service.name = "order-service" } >> { span.service.name = "payment-service" && status = error }

5.5 可视化层

Grafana 是事实上的开源可视化霸主。它的核心竞争力不在”图做得漂亮”,而在于统一 Datasource 抽象——同一个 Dashboard 可以在 Panel 级别混用 Prometheus、Loki、Tempo、Pyroscope、MySQL、CloudWatch、阿里云 SLS,并提供 Correlations 功能实现一键跳转。

除 Grafana 外,Kibana(ES 原生)、SkyWalking UI(APM 一体)、Datadog/New Relic 自家 UI、阿里云 ARMS 控制台也是常见选择。

5.6 告警层

告警层的核心是降噪。典型栈是:Prometheus / VMAlert 产生原始告警 → Alertmanager 做分组、抑制、静默 → PagerDuty / OpsGenie / 钉钉 / 飞书 / 企微做分发和升级。

成熟团队越来越倾向用 SLO 与 Burn Rate(燃烧率)替代单一阈值告警。燃烧率告警的思路是:“在未来 30 天的错误预算中,如果按当前速率继续消耗,会在 X 小时内耗尽”——这比”5xx > 1%“更精确,能过滤掉短暂抖动。Google SRE Workbook 对此有详细讨论。

5.7 一个参考架构:中型互联网公司的”全栈”部署

把上述六层组合起来,下图(文字版)是一个典型中型互联网公司(50-500 人研发)的可观测性部署:

┌──────────────────────────────────────────────────────────────────┐
│  应用进程(K8s Pod)                                               │
│    - OTel Java/Go/Node Agent (auto-instrumentation)              │
│    - 业务代码显式 OTel API 埋点                                     │
│    - pprof endpoint / JFR                                         │
└───────────────┬──────────────────────────────────────────────────┘
                │ OTLP/gRPC(localhost:4317)
                ▼
┌──────────────────────────────────────────────────────────────────┐
│  节点级 OTel Collector(DaemonSet)                               │
│    - receivers: otlp, filelog, hostmetrics, kubeletstats         │
│    - processors: resource, k8sattributes, batch                  │
│    - exporters: otlp(到 gateway)                                │
└───────────────┬──────────────────────────────────────────────────┘
                │ OTLP/gRPC(集群内 Service)
                ▼
┌──────────────────────────────────────────────────────────────────┐
│  集群级 OTel Collector Gateway(Deployment,水平扩展)             │
│    - tail_sampling                                               │
│    - attribute transform(PII 脱敏、字段映射)                      │
│    - routing processor(按环境/租户分发)                          │
└──┬──────────────┬──────────────┬──────────────┬──────────────────┘
   │              │              │              │
   ▼              ▼              ▼              ▼
┌────────┐   ┌────────┐   ┌────────┐   ┌──────────┐
│VMStack │   │ Loki   │   │ Tempo  │   │Pyroscope │
│(metrics)│  │(logs)  │   │(traces)│   │(profiles)│
└────┬────┘  └────┬───┘   └────┬───┘   └─────┬────┘
     └────────────┴─────────────┴────────────┘
                      │
                      ▼
                 ┌─────────┐
                 │ Grafana │  ◄── Alertmanager ──► 钉钉 / 企微 / PagerDuty
                 └─────────┘

这个架构有几个值得注意的设计点:DaemonSet + Gateway 两层 Collector(节点级收敛降低连接数,Gateway 做重处理)、按环境路由(routing processor 让 prod/staging 数据物理分开)、存储层按支柱各选最优(不强行用 ES 存一切)、告警集中由 Alertmanager 分发(避免多源重复告警)。

六、开源 vs 商业 vs SaaS 的分类

6.1 分类全景表

下表按照部署模式、付费方式、数据主权、典型用户画像做了一个粗略的分类。

类别 代表产品 部署 付费模式 数据主权 典型用户
开源自建 Prometheus、Thanos、VictoriaMetrics、Loki、Tempo、Jaeger、Pyroscope、OpenTelemetry Collector、SkyWalking On-prem / 自建 K8s 免费(人力成本) 完全自主 有 SRE 团队的中大型公司
开源商业发行版 Grafana Enterprise、Elastic (xpack)、SigNoz、Uptrace On-prem + 付费支持 许可证 + 订阅 自主 预算中等、希望有商业支持
商业软件 Dynatrace、New Relic、Datadog、AppDynamics、Splunk SaaS 为主,少量 on-prem 按主机/Host/容量计费 厂商托管 跨国企业、金融、传统行业
国内公有云 SaaS 阿里云 ARMS、腾讯云 APM、华为云 AOM、百度云 BCM 公有云 按量 云厂商托管 云上中小企业
国内独立 SaaS 观测云(Guance)、OneAPM、博睿数据、听云 SaaS / 私有化 订阅 + 许可证 可自主 金融、运营商、传统 IT
国内开源 夜莺(Nightingale)、DeepFlow、HertzBeat、SkyWalking(Apache) 自建 免费 自主 偏 CN 本土生态的团队

6.2 几个值得单独说明的项目

6.3 选型维度

从实际落地看,选型至少要考虑以下五个维度:

  1. TCO(总拥有成本):不仅算许可证费,也要算人力成本。一个 10 人 SRE 团队自建 Prometheus+Loki+Tempo 至少要 3-6 个月才能稳定。
  2. 数据主权与合规:金融、政务、医疗行业几乎没得选,只能自建或私有化。SaaS 方案会卡在等保、数据出境、客户审计这些关口。
  3. 定制能力:自建生态可以无限魔改,商业 SaaS 的扩展点通常只到 API 和告警 Webhook。
  4. 学习曲线:Prometheus/PromQL 易学,Grafana 上手快;但 ClickHouse 运维、Kafka 调优、eBPF 排障都是硬核活。
  5. 厂商锁定风险:Datadog 的私有数据格式、非 OTel 兼容的 Agent 是典型锁定点。相比之下,以 OTel 为输入的方案更易迁出。

七、中国实际:国内互联网公司三大支柱落地现状

7.1 典型演进路径

观察过去十年国内主流互联网公司的可观测性建设,可以提炼出一条大致清晰的演进路径:

阶段一(2010-2015):Zabbix + Nagios + 自研 Dashboard。这一阶段以主机级监控为主,告警靠短信/邮件,日志分散在各台物理机上,用 grepssh 排障。

阶段二(2015-2018):ELK + StatsD/Graphite + Zipkin。开始引入结构化日志和集中式存储,链路追踪进入视野。这一阶段的代表是美团 CAT、大众点评的监控平台、淘宝鹰眼(EagleEye)等自研方案。

阶段三(2018-2022):Prometheus + Grafana + ES + 自研 APM。Kubernetes 推动 Prometheus 成为事实标准,字节、B 站、滴滴等大规模落地 Prometheus 联邦/分片。ELK 被替换为 ClickHouse 或自研列存。

阶段四(2022-至今):OpenTelemetry + eBPF + 一体化平台。头部公司开始统一在 OTel 规范下重做埋点,底层探索 eBPF 实现零侵入采集,逻辑层搭建跨支柱一体化排障平台。

7.2 美团:从 CAT 到统一监控平台

美团的 CAT(Central Application Tracking)最初是大众点评团队在 2012 年左右开源的 APM 框架,基于 Java Agent,当时主要解决 Trace 与部分 Metrics 的采集。美团技术团队 2018 年发布的博文《美团点评统一监控平台建设》披露,其在 CAT 基础上构建了一个覆盖数万台机器、每日处理百亿级指标、千亿级日志的统一监控平台,核心组件包括自研 Metrics 网关、基于 HBase 的长存、基于 ES 的日志平台,以及基于自研协议的 APM。后续美团也在公开分享中提到向 OTel 和 ClickHouse 迁移的路径。

7.3 字节跳动:ByteMetrics 与大规模指标平台

字节跳动技术团队在 2021 年公开的《字节跳动指标平台的建设与实践》中披露,ByteMetrics 平台峰值处理 10 亿级 Series,PB 级数据规模。其架构关键点包括:分布式写入层采用一致性哈希分片;存储层自研时序引擎,兼容 Prometheus Remote Read/Write;查询层支持 PromQL 子集并做了大量查询优化;告警层支持动态阈值与异常检测。字节还在 GitHub 开源了部分组件,并参与了 OpenTelemetry 社区。

7.4 阿里巴巴:ARMS 与鹰眼

阿里内部最早的链路追踪系统是鹰眼(EagleEye),2013 年前后开始生产使用。商业化的阿里云 ARMS(Application Real-Time Monitoring Service)脱胎于此,是国内公有云中覆盖 Metrics/Traces/Logs 最齐全、用户基数最大的 APM 产品之一。ARMS 的特点是与阿里云其他产品(SLS、Prometheus Service、EDAS、ACK)深度集成,支持 OpenTelemetry 协议入数据,在国内金融、零售行业有大量落地案例。

7.5 B 站:Prometheus 联邦与大规模实践

B 站技术团队 2020 年前后公开分享过其监控演进。其早期是 Zabbix + ELK,2018 年起全面向 Prometheus + Grafana 迁移。由于单集群 Prometheus 在 500 万 Series 后会显著劣化,B 站采用了分层联邦:业务侧多个 Prometheus 实例各自采集一部分,顶层 Prometheus 从下层 /federate 端点拉取聚合数据,长期存储使用 Thanos 或 VictoriaMetrics。在此之上,B 站自研了告警规则管理平台、Dashboard 模板库、SLO 平台。

7.6 国内特有挑战

相较海外,国内可观测性建设面临几个结构性差异的挑战:

八、术语与缩写速查

下表汇总本文及后续系列文章会频繁出现的缩写,便于快速对照。

缩写 全称 中文 一句话解释
OTel OpenTelemetry 开放遥测 CNCF 托管的可观测性数据规范与 SDK 集合
OTLP OpenTelemetry Protocol 开放遥测协议 OTel 的数据传输协议,基于 gRPC/HTTP + protobuf
APM Application Performance Monitoring 应用性能监控 聚焦应用层的 Metrics + Traces + Logs 一体化产品类目
RUM Real User Monitoring 真实用户监控 采集浏览器/移动端真实用户性能数据
SLO Service Level Objective 服务级别目标 对服务质量的量化目标,如”99.9% 请求 < 200ms”
SLI Service Level Indicator 服务级别指标 衡量 SLO 达成程度的具体指标
SLA Service Level Agreement 服务级别协议 对外商业合同层面的服务承诺
RED Rate / Errors / Duration 请求率/错误/延迟 Tom Wilkie 提出的服务层三大黄金指标
USE Utilization / Saturation / Errors 利用率/饱和度/错误 Brendan Gregg 提出的资源层三大指标
TSDB Time Series Database 时序数据库 针对时间序列数据优化的数据库,如 Prometheus
MTTD Mean Time To Detect 平均检测时间 从故障发生到被系统检测的平均时间
MTTR Mean Time To Recover 平均恢复时间 从故障被检测到完全恢复的平均时间
eBPF Extended Berkeley Packet Filter 扩展 BPF Linux 内核可编程钩子,用于零侵入采集
W3C TC W3C Trace Context W3C 追踪上下文 traceparent/tracestate HTTP 头的标准
CRD Custom Resource Definition 自定义资源定义 Kubernetes 扩展机制,许多可观测性组件以 CRD 形式配置
HPA Horizontal Pod Autoscaler 水平 Pod 自动伸缩 K8s 基于指标自动扩缩容,可观测数据是其输入
PII Personally Identifiable Information 个人可识别信息 可观测性脱敏的重点对象

九、工程坑点

9.1 坑点一:支柱间数据孤岛

这是最常见也是最令人头疼的一类问题。Metrics 的时间戳精度是 1 秒(Prometheus 默认),Logs 一般到毫秒,Traces 到纳秒。如果三者的时钟源不同、时区没统一、NTP 同步不准,会出现 Trace 开始于 10:15:32.481,而该时段的 Logs 里 trace_id 对应的错误日志时间戳却是 10:15:33,肉眼关联都做不到。

更隐蔽的孤岛是标签命名不一致。Metrics 里服务名叫 service,Logs 里叫 app,Traces 里叫 service.name,Profiles 里叫 process.name。Grafana 的 Correlations 跳转只能按字面匹配,不会自动对齐。

治理建议:以 OTel 的 Resource Semantic Conventions 为事实标准,强制所有信号在 Collector 层面归一到 service.nameservice.versiondeployment.environmenthost.namek8s.pod.name 等统一字段。所有部署 NTP(或更精确的 PTP),时钟同步偏差 < 1ms。

9.2 坑点二:语义不一致导致”同一服务三张皮”

真实案例:某公司订单服务,Metrics 叫 order-service,日志里打印 OrderService,Traces 注册的 service.nameorder_service。排障时三套系统互不相识,需要在 Grafana 变量层手工写正则映射。这是典型的”没有统一 Resource Schema”。

治理建议:用一套硬性的命名规范(通常是小写短横线,对齐 K8s 约定),并把它写进 CI:PR 构建时如果 OTel Resource Attributes 与预期不符,直接 fail。统一的服务目录(Service Catalog)是长期解决方案,每个服务登记一次,所有信号都引用它。

9.3 坑点三:换 SDK 版本字段飘移

OTel 的 Semantic Conventions 自身也在演进。早期版本里 HTTP 字段是 http.method,v1.21 之后演化为 http.request.method,再到 http.request.method(稳定化)。如果后端存储没做别名、兼容层,升级 SDK 的那一天,所有历史 Dashboard 都会瞎。

治理建议:在 Collector 的 transformattributesprocessor 里集中做一次字段映射,对外保持存储层字段稳定。定期升级 SDK 并一次性做迁移,不要让不同微服务使用差异过大的 SDK 版本。

9.4 坑点四:过度采集,全量 Trace 把存储打爆

一个常见错误是”先让 Trace 跑起来再说”,不做采样。对一个日均 1 亿请求的服务,全量 Trace 按每条 10KB 估算就是 1TB/天,一个月就是 30TB。Tempo/Jaeger 的对象存储账单会让财务找上门。

治理建议:出生即采样。推荐策略:错误 100%、慢(> P99 阈值)100%、基线 1% 以下。尾部采样必须在 Collector 层做,尽量不要在 SDK 做”随机丢弃”(会丢失有价值的错误尾部)。

9.5 坑点五:告警疲劳

一次真实事故,Metrics 配置了 5xx 比例告警、日志配置了 ERROR 关键字告警、APM 配置了 P99 超阈值告警——同一次下游抖动导致三个系统同时告警,on-call 工程师半夜收到 50 条重复通知,第二天直接关了通知开关,结果漏掉真正的 P0。

治理建议:告警的”漏斗”应当在 Alertmanager(或同等层)集中做。按 cluster + namespace + service 分组抑制,按时间窗口去重,按告警严重度分层(Page vs Ticket vs Log)。长期目标是”一个事件一条告警”,而不是”一个信号一条告警”。

9.6 坑点六:生产和测试共用 Collector,环境标签没隔离

早期创业团队图省事常把生产和测试的 OTel Collector 合一部署,结果测试环境某次压测把生产的指标 Series 基数顶穿,Prometheus OOM。问题根因是 Collector 没在 processors.resource 层强制 deployment.environment 标签。

治理建议:每个环境独立部署 Collector,并在 receiver 和 exporter 两端都强制校验 deployment.environment。生产 Collector 的 exporter 配置连到生产后端,测试 Collector 连到测试后端,物理隔离。

9.7 坑点七:日志里记录 PII

这是合规红线。曾有团队把用户手机号、身份证号、地址直接打进日志,并同步到海外 SaaS,触发数据跨境审计问题。

治理建议:在 Collector 的 attributesprocessor 或 Vector 的 remap 步骤中强制脱敏。典型手段:正则替换手机号(1\d{10}1**********)、身份证号、银行卡号、Authorization 头。定期用红队工具扫描日志样本验证脱敏有效。

9.8 坑点八:Dashboard 复制粘贴导致”变量污染”

Grafana Dashboard 一个复制给另一个团队,大家各改一点,渐渐所有 Dashboard 的 $service 变量查询语句都不一样:有的查 label_values(up, job),有的查 label_values(kube_pod_info, pod),跨 Dashboard 无法一致跳转。

治理建议:建立统一的 Dashboard 模板库(可用 Grafonnet / grafana-dashboards-as-code 生成),所有业务 Dashboard 从模板派生,关键变量定义不允许修改。

9.9 坑点九:Histogram bucket 设计不合理

Prometheus 的 Histogram 需要预先定义 bucket 边界。很多团队用默认的 [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10],但实际服务 P99 在 300ms 左右,结果 P99 的 histogram_quantile 永远落在 [0.25, 0.5] 这个区间,精度只有 250ms 粒度,完全无法支持 SLO 告警。

治理建议:对每个核心接口根据实际延迟分布定制 bucket,使 P50/P95/P99 至少落在相邻两个 bucket 中。或直接改用原生 Histogram(Prometheus 2.40+)/Exponential Histogram(OTel),自动分桶。

9.10 坑点十:Trace 上下文在异步/队列边界丢失

消息队列、定时任务、线程池这些”异步边界”是 Trace 断链的高发地。一条订单的 Trace 在写入 Kafka 后,消费方重新起了一条新的 Trace,上下游断开。排障时只能拼凑。

治理建议:在消息 Header 中透传 W3C traceparent,消费端用 Extract API 恢复上下文。对线程池,使用 OTel 的 Context.wrap 包装 Runnable;对 Go,显式传递 context.Context;对 Kotlin 协程,使用 MDCContext + OTel ContextElement

9.11 坑点十一:Cardinality 爆炸

前文提过 high-cardinality 是 Prometheus 的毒药。但生产上常见的失控不是人为,而是”无意中引入”。典型来源:把用户 ID 写进 label、把 URL path 里的动态段(/api/orders/123)作为 label 没做聚合、把 Kubernetes 的 Pod name(带哈希后缀)作为主键。

治理建议:Prometheus / VictoriaMetrics 都支持 relabel 规则丢弃高基数标签。在 Collector 的 metricstransformfilter 里限制标签值的最大基数。定期跑基数体检(topk(20, count by (__name__) ({__name__=~".+"})))。

9.12 坑点十二:采样决策在错误的位置做

一条典型的错误链路:SDK 端按 10% 概率采样上报 → Collector 又按 10% 采样下采 → 最终真正入库的是 1%。中间经过两次独立随机,导致实际保存的 Trace 结构在统计上严重偏差(尤其错误 Trace 稀疏性被放大)。

治理建议:采样只在一层做,通常是 Collector 的 tail_sampling。SDK 端保持 100%(或按父采样 parentbased_always_on)。如果 SDK 端一定要做采样(因为带宽敏感),就关掉 Collector 侧的概率采样,只做”错误/慢请求补全”。

十、选型建议

10.1 决策维度

选型建议不应当落在”推荐某某产品”上,而应回到三个基本维度:规模(数据量、SKU 数)、预算(年投入)、团队能力(是否有 SRE)。下表是一个粗略的决策树。

团队规模 数据量(每日) 建议栈 关键理由
< 50 人 < 100GB Prometheus + Grafana + Loki + 简单 Jaeger 学习曲线低、OTel 兼容、零许可证成本
< 50 人,云上 任意 云厂商 SaaS(ARMS / Datadog) 省人力,3 个月到位
50-500 人 100GB-10TB OTel Collector + VictoriaMetrics + Loki/ClickHouse + Tempo + Grafana 自建成熟,可扩展性足够
50-500 人,合规重 任意 自建 + 商业发行版(Grafana Enterprise / Elastic) 补足 RBAC、审计、企业支持
> 500 人,大规模 > 10TB 自建一体化平台(基于 OTel + ClickHouse + 自研查询层) 省存储成本,深度定制需求强
> 500 人,国企/金融 任意 私有化商业(观测云 / DeepFlow / SkyWalking)+ 自研 合规与国产化要求

10.2 小团队(<50 人)

建议尽量上云或使用 SaaS。自建的边际收益小、人力成本高。如果必须自建,优先 Prometheus + Loki + Grafana 三件套。埋点一开始就用 OTel SDK 的 auto-instrumentation,不要自研。

10.3 中团队(50-500 人)

这是自建收益最明显的区间。建议以 OTel 为埋点规范,Collector 为数据中枢,VictoriaMetrics 或 Mimir 做 Metrics,Loki 或 ClickHouse 做 Logs,Tempo 做 Traces,Grafana 做可视化。这一阶段要开始搭建 SLO 平台、变更事件打点、Service Catalog。

10.4 大团队(>500 人)

大团队几乎一定会走向”自建一体化平台”。原因是:开源组件的查询层与元数据层往往无法支撑数百团队的租户隔离、配额管理、成本分摊、审计合规。头部公司(美团、字节、阿里、B 站、京东、腾讯)大多都有自研的数据层(一般以 ClickHouse 为底座)+ 自研的查询网关 + 基于 Grafana 定制的前端。OTel 在这里主要承担”数据入口标准”的角色。

10.5 国内方案 vs 国际方案的取舍

国际方案(Datadog、New Relic、Splunk)的优势是”一站式 + 稳定 + 文档完备”,劣势是价格高、数据主权问题、国内网络延迟高、对本土框架(Dubbo、Nacos、TDengine 等)支持差。国内方案(观测云、DeepFlow、SkyWalking、夜莺)的优势是本土生态契合、合规就绪、响应速度快,劣势是国际化支持与跨国协作场景较弱。

最务实的做法是”以 OTel 为入口,以 ClickHouse / VictoriaMetrics / Tempo 等开源存储为核心,以 Grafana 为门面,前后端都可替换”。这样无论选择任何一家厂商,都能保留迁移自由。

10.6 一份可执行的 90 天路线图

无论团队规模如何,如果你正从零开始搭建可观测性体系,下面这份 90 天路线图可以作为参考。它把工作拆成三个 30 天的阶段,每个阶段都有明确的交付物和退出标准。

第一个 30 天:打地基。目标是”让所有关键服务有最基础的 Metrics + Logs,并能在一张 Dashboard 上看到”。

具体动作: 1. 选定一套核心栈(推荐 OTel Collector + Prometheus/VM + Loki + Grafana),在一个 K8s 集群里跑通。 2. 为 Top 10 核心服务部署 OTel auto-instrumentation,打通 RED 指标。 3. 编写 5 张基础 Dashboard:总览、服务详情、主机、Kubernetes 资源、告警看板。 4. 把现有 Zabbix / 自研脚本的核心告警迁移到 Alertmanager。

退出标准:任何一个核心服务的 QPS、错误率、P99 延迟,能在 Grafana 上 30 秒内找到。

第二个 30 天:打通链路。目标是”让 Metrics、Logs、Traces 三者能互相跳转”。

具体动作: 1. 在所有核心服务里统一注入 W3C Trace Context,验证跨服务传播。 2. 日志里强制打印 trace_idspan_idservice.name,并在 Loki 中建索引。 3. Prometheus 开启 Exemplars,在 Grafana 面板配 “Exemplars → Tempo” 跳转。 4. 部署 Tempo 或 Jaeger,使用 Collector 的 tail_sampling 控制存储量。 5. 在 Grafana 中配置 Correlations / Data Links,实现”点击延迟曲线 → 跳错误日志 → 跳慢 Trace”。

退出标准:发生一次故障时,SRE 能在 3 次点击以内从告警跳到具体的慢 Trace 和错误日志。

第三个 30 天:补全”最后一公里”。目标是”让团队能回答未预见的问题”。

具体动作: 1. 部署 Pyroscope 或 Parca,为核心服务开启持续 Profile。 2. 把 CI/CD 的发布事件、Kubernetes 的关键 Events(OOM、调度失败、探针失败)接入 Grafana Annotations。 3. 建立 Service Catalog,把所有服务的 service.name、owner、repo、runbook 登记到一个可查询的源。 4. 编写第一版 SLO 定义(从 Top 3 服务开始),并配置燃烧率告警。 5. 做一次”故障演练”:人为注入一个慢依赖,验证整个排障链路是否贯通。

退出标准:一个从未见过这套系统的新人,能在半天内通过 Dashboard + Explore 面板自主定位一个故障的根因。

三个月结束时,你不会拥有一个”完美”的可观测性平台,但你会拥有一个能用、能演进、能规模化的起点。剩下的工作,是在后续系列文章里会逐步展开的:SLO 的体系化、eBPF 的渐进引入、成本治理、AI 辅助根因分析,等等。

10.7 结语

可观测性工程不是一次性的建设项目,而是一项与业务规模、架构演进、团队结构持续共振的长期事业。从控制论到云原生,这六十年里工程界围绕”如何理解一个复杂系统”积累的所有经验,最终落在了 Metrics、Logs、Traces、Profiles、Events 这五根柱子上。五支柱既是数据形态的划分,也是认知维度的划分——趋势、事件、因果、热点、变更,恰好覆盖了一个工程师在面对故障时需要反复切换的五种视角。

本系列后续的文章,会逐个深入每一根柱子、每一层组件、每一个国内外典型案例。希望读者能把这篇作为一张地图:地图不能替代路,但至少能告诉你自己身在何处、远方是何处、下一步往哪里走。

最后,再次重申本文反复出现的几条工程原则,作为对整篇内容的压缩:

一,可观测性是属性,不是工具。再昂贵的 SaaS 也买不来一个原本没有被设计成可观测的系统。 二,共享 Resource 是一切关联的前提service.name 不统一,再多的跳转按钮也无用。 三,采样只在一处做,最好在 Collector 层。多层独立采样会把稀有样本稀释到不可用。 四,Dashboard 已死,Explore 当立。预设的图表只能回答预设的问题,真正的价值在自由查询。 五,SLO 驱动告警,而不是阈值驱动告警。错误预算与燃烧率是降噪的根本手段。 六,合规和成本是落地的两堵墙。在做方案时提前考虑 PII 脱敏与采样策略,远比上线后补救便宜。

参考资料

  1. Cindy Sridharan, “Monitoring and Observability”, Medium, 2017
  2. Brendan Burns et al., Site Reliability Engineering, Google, 2016
  3. CNCF OpenTelemetry Specification, https://opentelemetry.io/docs/specs/otel/
  4. Charity Majors et al., Observability Engineering, O’Reilly, 2022
  5. 美团技术团队,《美团点评统一监控平台建设》,美团技术博客,2018
  6. 字节跳动技术博客,《字节跳动指标平台的建设与实践》,2021
  7. CloudEvents Specification v1.0, https://cloudevents.io/
  8. Polar Signals/Parca, https://www.parca.dev/
  9. 夜莺(Nightingale)开源项目,https://github.com/ccfos/nightingale
  10. B 站技术博客,《B 站基于 Prometheus 的监控实践》,2020

上一篇返回可观测性工程

下一篇可观测性 vs 监控:从 Zabbix/Nagios 到 OpenTelemetry 的二十年

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-04-22 · architecture / observability

可观测性工程

从 Metrics、Logs、Traces 到 Profiling、eBPF、OpenTelemetry 与 SLO 治理,面向中国工程团队的可观测性系统化手册。


By .