可观测性全景: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) = n,n
是状态维度)。换言之,如果我们把一个数据中心看成一个庞大的动态系统,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 可观测性的核心命题
综合上述来源,我们可以把可观测性的核心命题写成如下更工程化的版本:
- 外部可测、内部可推。所有系统状态的变化都应能通过外部信号反映出来。
- 高基数、高维度。系统必须能记录每一条请求的上下文(用户 ID、版本号、区域、实验分组),并允许按任意维度下钻查询。
- 关联性。Metrics、Logs、Traces、Profiles、Events
必须能通过共同的关联键(如
trace_id、service.name、host.name)互相跳转。 - 低成本探索。在不重新发布、不修改代码的前提下,能对生产环境发起新的查询。
这四条原则,后面几节会反复出现。它们是评判一个可观测性栈是否”成熟”的最终标准。
二、三支柱到五支柱的演进
2.1 2017 年”三支柱”的确立
“Three Pillars of Observability”这个说法的流行,同样发生在 2017 年前后。Peter Bourgon 在其个人博客中系统地把可观测性切分为三种数据类型:Metrics、Logs、Traces。这种切法之所以能迅速被接受,是因为它精确地对应了当时三类已经独立发展多年的开源生态:
- Metrics 对应 Prometheus(2012 年开源)、Graphite、StatsD、InfluxDB 等时序数据库生态。
- Logs 对应 ELK(Elasticsearch、Logstash、Kibana)、Graylog、Fluentd 等日志采集/存储生态。
- Traces 对应 Zipkin(2012 年 Twitter 开源)、Jaeger(2016 年 Uber 开源),以及背后 Google Dapper 论文催生的整个分布式追踪生态。
三支柱模型最大的贡献,是给工程师提供了一个清晰的采购清单:要做可观测性,至少要有一套指标系统、一套日志系统、一套追踪系统。后来 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 为什么需要五支柱而不是三支柱
三支柱回答的是”发生了什么”与”哪条调用链慢了”的问题,但在真实故障排查中,还有两个维度的问题三支柱答不上来:
- “为什么这段代码慢?”——需要 Profiles 把 CPU/内存/锁等待归因到具体函数、具体行号。
- “是什么改动触发的?”——需要 Events 把发布、配置、调度决策以机器可读的形式摆出来。
这两个问题正是所谓根因定位的”最后一公里”。在一个成熟的可观测性平台中,典型的排障路径是:
- Metrics 告警触发:某服务 P99 延迟突增。
- 跳转 Traces:通过 exemplar 跳到一条慢调用的具体 Span。
- 跳转 Logs:通过
trace_id关联到该请求期间的错误日志。 - 跳转 Profiles:用 Span 的起止时间在 Continuous Profiling 中检索对应时段的 CPU 热点。
- 叠加 Events:发现问题发生前 15 分钟有一次灰度发布。
五支柱缺一不可。
2.5 五支柱关联图
上面流程的可视化,就是本文开头的
landscape-pillars.svg。五根柱子通过三类”桥”互联:
exemplars:Prometheus 2.26 起支持的特性,允许一个 Histogram bucket 携带若干trace_id,实现 Metrics → Traces 的跳转。trace_id/span_id:以 W3C Trace Context(traceparentHTTP 头)为标准,横贯 Logs 与 Traces。pprof.labels/ OTel Resource Attributes:把 Profiles 与 Traces、服务名绑定,使得可以按service.name或更精细的operation维度筛选火焰图。
一套合格的可观测性平台,衡量的标准不是”我有几个支柱”,而是”我的支柱之间能不能一键跳转,跳转后时间对齐、维度对齐、上下文对齐”。这一点我们在第九节的坑点部分会反复强调。
三、五种信号的形态、成本与用途
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: versionPrometheus 的暴露端点上会看到类似这样的数据:
# 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_id、request_id 或
trace_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 的数据形态最灵活,关键是
type、source、subject、time
四元组。一次 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”说起。借用这对概念:
- Known unknowns:我们事先知道可能出问题的地方,并为其设定了阈值、告警和 Dashboard。例如”CPU 打满”、“磁盘满”、“5xx 超 1%”、“队列堆积超过 1000”。这是监控的主场。
- Unknown unknowns:我们事前根本没想到会出的问题。例如”某地区某型号手机的某个 Android 版本在某次灰度后下单成功率下降 3%“、”某客户因为他们的 HTTP/2 栈与我们的 TLS 1.3 握手顺序兼容问题而报 400”。这类问题没有预设的告警,必须能在事后任意下钻。可观测性的主场。
监控关心”系统是否按我预设的方式运行”,可观测性关心”系统当前是什么状态,以及为什么是这个状态”。这是两个完全不同的认知模式。
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 驱动的告警。简化模型:
- 定义 SLI:可量化的服务质量指标,如”请求成功率”。
- 定义 SLO:SLI 在时间窗口上的目标,如”过去 30 天成功率 ≥ 99.9%“。
- 计算错误预算(Error
Budget):
1 - SLO,上例即 0.1%。 - 基于”燃烧率”(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.4 与 1 是 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.name、http.method、db.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:CNCF 的官方”可观测性瑞士军刀”,提供 receivers(接收各种协议)、processors(采样/脱敏/聚合)、exporters(投递到各种后端)的流水线模型。
- Kafka:用来承接超大规模日志/Trace 流,消费端再做异步写入。美团、字节、B 站等厂内部几乎都有 Kafka 作为日志/Trace 总线。
- Remote Write + VMAgent:VictoriaMetrics 生态中,VMAgent 作为 Prometheus 数据的转发 Agent,支持压缩、限流、持久化队列。
典型的 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 数据存储层
按支柱划分,主流存储选择如下:
- Metrics:Prometheus 单机(适合每秒写入 < 100 万样本);VictoriaMetrics/Thanos/Mimir(单集群横向扩展到每秒千万级样本);M3DB(Uber 开源,已不活跃);InfluxDB(v2 OSS 已弱化,推 Cloud)。
- Logs:Elasticsearch(重型倒排索引);Loki(轻索引、对象存储友好);ClickHouse(列存,阿里、字节、B 站广泛采用);OpenSearch(ES 分叉)。
- Traces:Jaeger(Cassandra/ES 后端);Tempo(对象存储,重查询,轻写入);SkyWalking(国产,APM 一体化);Zipkin(老牌,已少见于新项目)。
- Profiles:Pyroscope(Grafana Labs)、Parca(Polar Signals)、商业 Polar Signals Cloud。
- Events:一般没有独立存储,而是复用 Kafka/Loki/ClickHouse,或以 annotation 形式存入 Grafana。
选型的核心矛盾是写入速率 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-service 和
payment-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 几个值得单独说明的项目
- OpenTelemetry:严格来说它不是产品,是规范 + SDK + Collector 的三合一事实标准。所有新项目都应以 OTel 作为起点。
- Prometheus:社区依然是 Metrics 领域的中心,但生产规模大的公司几乎都要上 Thanos、VictoriaMetrics 或 Mimir。
- Datadog:国际市场商业化最成功的可观测性公司,2023 年营收超 20 亿美元。其”All-in-One”体验是国内产品对标的范本。
- 夜莺(Nightingale):由国内 Flashcat 团队发起、CCFOS 社区托管的开源监控项目,2023 年进入 CCF 开源发展委员会。它在 Prometheus 上增加了告警流水线、机器分组、可视化等运维友好特性,是国内 Zabbix 的自然替代者之一。
- DeepFlow:云杉网络开源的 eBPF 可观测性平台,主打自动服务拓扑、网络黄金信号、零侵入追踪,已进入 CNCF Sandbox。
- SkyWalking:吴晟发起、Apache 顶级项目,APM 一体化方案,在国内金融、电力、运营商行业装机量极大。
6.3 选型维度
从实际落地看,选型至少要考虑以下五个维度:
- TCO(总拥有成本):不仅算许可证费,也要算人力成本。一个 10 人 SRE 团队自建 Prometheus+Loki+Tempo 至少要 3-6 个月才能稳定。
- 数据主权与合规:金融、政务、医疗行业几乎没得选,只能自建或私有化。SaaS 方案会卡在等保、数据出境、客户审计这些关口。
- 定制能力:自建生态可以无限魔改,商业 SaaS 的扩展点通常只到 API 和告警 Webhook。
- 学习曲线:Prometheus/PromQL 易学,Grafana 上手快;但 ClickHouse 运维、Kafka 调优、eBPF 排障都是硬核活。
- 厂商锁定风险:Datadog 的私有数据格式、非 OTel 兼容的 Agent 是典型锁定点。相比之下,以 OTel 为输入的方案更易迁出。
七、中国实际:国内互联网公司三大支柱落地现状
7.1 典型演进路径
观察过去十年国内主流互联网公司的可观测性建设,可以提炼出一条大致清晰的演进路径:
阶段一(2010-2015):Zabbix + Nagios + 自研
Dashboard。这一阶段以主机级监控为主,告警靠短信/邮件,日志分散在各台物理机上,用
grep 和 ssh 排障。
阶段二(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 国内特有挑战
相较海外,国内可观测性建设面临几个结构性差异的挑战:
- 数据合规(等保 2.0 与数据安全法):日志、追踪中携带的用户 ID、手机号、身份证、IP 等信息在传输、存储、查询各环节都要做脱敏和审计。这对 Collector 的 processor 链路提出了硬性要求。
- 多云/混合云部署:头部公司普遍是”两地三中心 + 多云”,单一云厂商 SaaS 方案常常不兼容,必须自建或做多云网关。
- 超大规模下的采样策略:日活亿级的 C 端业务,全量 Trace 每日产生 PB 级数据,尾部采样必须精心设计,否则存储成本会吃掉全部预算。典型方案是”错误 100% + 慢 100% + 基线 0.1%-1%“。
- 跨团队数据孤岛:大公司常见”中台团队管平台、业务团队管埋点、SRE 管告警”,三方口径不一致,导致”同一个 service.name 在三张表里拼不起来”(详见第九节坑点)。
- 开源社区的本地化:大量海外文档与工具链的中文材料滞后、版本不同步,使得国内团队在选型时更偏向”厂内自研”或”国内开源项目”,这也是 SkyWalking、Nightingale、DeepFlow 等能做起来的土壤。
八、术语与缩写速查
下表汇总本文及后续系列文章会频繁出现的缩写,便于快速对照。
| 缩写 | 全称 | 中文 | 一句话解释 |
|---|---|---|---|
| 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.name、service.version、deployment.environment、host.name、k8s.pod.name
等统一字段。所有部署 NTP(或更精确的 PTP),时钟同步偏差
< 1ms。
9.2 坑点二:语义不一致导致”同一服务三张皮”
真实案例:某公司订单服务,Metrics 叫
order-service,日志里打印
OrderService,Traces 注册的
service.name 是
order_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 的
transform 或 attributesprocessor
里集中做一次字段映射,对外保持存储层字段稳定。定期升级 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
的 metricstransform 或 filter
里限制标签值的最大基数。定期跑基数体检(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_id、span_id、service.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 脱敏与采样策略,远比上线后补救便宜。
参考资料
- Cindy Sridharan, “Monitoring and Observability”, Medium, 2017
- Brendan Burns et al., Site Reliability Engineering, Google, 2016
- CNCF OpenTelemetry Specification, https://opentelemetry.io/docs/specs/otel/
- Charity Majors et al., Observability Engineering, O’Reilly, 2022
- 美团技术团队,《美团点评统一监控平台建设》,美团技术博客,2018
- 字节跳动技术博客,《字节跳动指标平台的建设与实践》,2021
- CloudEvents Specification v1.0, https://cloudevents.io/
- Polar Signals/Parca, https://www.parca.dev/
- 夜莺(Nightingale)开源项目,https://github.com/ccfos/nightingale
- B 站技术博客,《B 站基于 Prometheus 的监控实践》,2020
上一篇:返回可观测性工程
下一篇:可观测性 vs 监控:从 Zabbix/Nagios 到 OpenTelemetry 的二十年
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
可观测性工程
从 Metrics、Logs、Traces 到 Profiling、eBPF、OpenTelemetry 与 SLO 治理,面向中国工程团队的可观测性系统化手册。
【可观测性工程】可观测性 vs 监控:从 Zabbix/Nagios 到 OpenTelemetry 的二十年
监控与可观测性不是新旧迭代,而是认知模型的根本转换。本文梳理从 1999 年 Nagios 到 2019 年 OpenTelemetry 的二十年演进时间线,对比 push/pull 模型、数据模型差异,以及国内从 Zabbix 到 Prometheus 再到 OTel 的典型迁移路径与工程坑点。
【可观测性工程】Metrics:Prometheus、VictoriaMetrics、Thanos、Mimir、M3
从 Prometheus 架构与数据模型出发,系统梳理 Remote Write、PromQL 进阶、Thanos 全局聚合、Mimir 多租户、VictoriaMetrics 性能、M3DB 原理,以及五者在大规模生产场景下的对比矩阵与迁移实践。
【可观测性工程】OpenTelemetry 深入:SDK、Collector、语义约定与版本演进
从 OpenTracing 与 OpenCensus 合并到今天的 OTel v1 稳定版,梳理 SDK 生命周期、Collector 流水线、OTLP 协议与 Semantic Conventions 的工程意义,并结合阿里 ARMS、观测云、夜莺等国内实践,给出多租户与尾采样的落地建议。