元数据卡
- 前置知识:第8章(可观测性基础)、第9章(SRE 基础)
- 预计时间:45 分钟
- 阅读模式:高度专注
- 完成标志:理解四大黄金信号和 USE/RED 方法论,掌握 PromQL 高阶查询和指标基数管理,能设计合理的告警策略和 Grafana 面板,理解 Observability as Code 的概念
你的进度
远征军的可观测性栈已经部署到位。Prometheus 采集着各项指标,Grafana 展示着仪表盘,告警系统在深夜也会把值班的你叫醒。
但两个月下来,新的问题浮出水面:
告警太多。每个新部署的 PrometheusRule 都创建了告警,最初你觉得"多一点告警总比漏掉好"。然后你在一个晚上收到了 47 条告警通知——其中 46 条是同一个 root cause 导致的级联告警。你筛了半天才找到真正的原因是一个数据库连接池耗尽。
Grafana 面板也失控了。你是说"这个面板刚才还看到的"——同事改了一个面板、新增了几个面板、有人把面板的 PromQL 写错导致数据源请求把 Grafana 自己搞挂了。仪表盘管理成了新问题。
林将军说:"你的告警系统警报比战报还多。一个士兵报告了 47 次'发现敌军',但你后来发现这只是一次大规模突袭的碎片信息。"
你的任务
从"能用可观测性工具"走向"用好可观测性工程"。理解四大黄金信号和 USE/RED 方法论的指导思想,掌握 PromQL 的聚合和基数控制,设计告警去重和路由策略,建立 Grafana 面板的设计规范,了解可观测性即代码和 RUM/合成监控的概念。
破局 · 溯源
超越三支柱:Use/RED 方法论
第 8 章提到可观测性的三个数据维度(Metrics、Tracing、Logging)。但你有数据之后,怎么知道看什么指标?
四大黄金信号(Google SRE 经典)
Google SRE 定义了四个最能反映系统健康状况的信号:
| 信号 | 含义 | 典型指标 | 回答的问题 |
|---|---|---|---|
| 延迟(Latency) | 请求处理时间 | P50/P95/P99 | 用户等多久? |
| 流量(Traffic) | 系统负载 | QPS、RPS、活跃连接数 | 系统承受了多大压力? |
| 错误(Errors) | 失败请求 | HTTP 5xx、超时、业务错误码 | 功能正常工作吗? |
| 饱和度(Saturation) | 资源使用程度 | CPU/内存/连接池/队列深度 | 还有盈余吗?什么时候撑不住? |
你不需要在面板上展示所有指标——但这四个信号类型是"必看"的。如果只看一个维度(比如 CPU),你可能会错过延迟飙升而 CPU 正常的场景(锁竞争或 IO 等待)。
USE 方法论(Brendan Gregg)
USE 是面向资源的:利用率(Utilization)、饱和度(Saturation)、错误数(Errors)。
USE 方法论的应用:
对于"每个资源"(CPU、内存、磁盘、网络):
利用率: 资源有多忙? → node_cpu_seconds_total
饱和度: 资源排队了多少工作? → 磁盘 IO 等待队列长度
错误: 资源有故障吗? → disk_read_errors_totalUSE 更适合基础设施层面的诊断(节点、磁盘、网络接口)。它问你:每个资源的"容量还有多少"。
RED 方法论(Tom Wilkie)
RED 是面向服务的:每秒请求数(Rate)、每秒错误数(Errors)、请求耗时(Duration)。
RED 方法论的应用:
对于"每个服务":
Rate: 每秒处理的请求数 → rate(http_requests_total[5m])
Errors: 每秒失败的请求数 → rate(http_requests_total{status=~"5.."}[5m])
Duration: 请求耗时分布 → histogram_quantile(0.99, rate(...))RED 更适合微服务层面的诊断。每个服务有一个 RED 微型面板,你一眼看出:这个服务请求量多少、错误率多高、响应多慢。
USE 看"资源够不够",RED 看"服务好不好"。两个方法论互补——当你用 RED 发现某个服务延迟升高了,再用 USE 排查它的资源是否饱和。
指标基数(Cardinality)和高基数问题
Prometheus 使用标签(label)来区分时间序列。每个不同的标签值组合产生一条新的时间序列。
# 以下是一个高基数查询——小心!
# 如果 http_requests_total 包含 user_id 作为标签
http_requests_total{user_id="~.*"}
# 如果有 10 万用户,这个查询会生成 10 万条时间序列
# Prometheus 的 TSDB 索引会暴增,查询变慢,存储膨胀低基数 vs 高基数的判断:
低基数字段(适合做 Prometheus 标签):
- 服务名(20 个值)
- 状态码(几十个值)
- HTTP 方法(GET/POST/PUT/DELETE — 4 个值)
- 实例 ID(一般来说 10-100 个)
高基数字段(不适合做 Prometheus 标签):
- 用户 ID(百万级别)
- IP 地址(指数级)
- 请求 URL 路径(除非路径枚举有限)
- Session ID(次秒级变化)高基数的危害:
1 个指标 × 3 个低基数字段(10 × 5 × 20 = 1000 条序列)→ 没问题
1 个指标 × 1 个高基数字段(10 万用户)→ 10 万条序列
一次 scrape,10 个这样的指标 → 100 万条序列
Prometheus 的存储和查询性能会显著退化管理基数的方法:
1. 不把高基数字段作为标签——放在日志或 trace 中
2. 使用 recording rules 预聚合:
- 原始指标: http_requests_total{path,method,status,user_id}
- 预聚合: sum by (path, method, status) (http_requests_total)
- 查询时用预聚合指标,不用原始指标
3. 设置标签限制:Prometheus 的 `--storage.tsdb.max-series`PromQL 进阶
你已经熟悉了基础的 rate、sum、histogram_quantile。生产环境还需要以下技能:
子查询(Subquery)
# 计算过去 1 小时内,每 5 分钟的错误率最大值
max_over_time(
rate(http_requests_total{status=~"5.."}[5m])
[1h:1m] # 过去 1 小时,步长 1 分钟
)Recording Rules
预计算复杂的 PromQL 表达式,减少查询时的计算开销:
# prometheus-rules.yaml
groups:
- name: atlas-recording-rules
interval: 30s
rules:
- record: job:http_errors:rate5m
expr: sum(rate(http_requests_total{status=~"5.."}[5m])) by (job)
- record: service:error_budget_remaining:ratio
expr: |
(
1 - (
sum(rate(http_requests_total{status=~"5.."}[30d]))
/
sum(rate(http_requests_total[30d]))
)
) / (1 - 0.999)之后查询直接用 service:error_budget_remaining:ratio,不需要每次都执行复杂计算。
多值聚合
# 计算每个服务的 P99 延迟,只展示超过 500ms 的服务
histogram_quantile(0.99,
sum(rate(http_request_duration_seconds_bucket[5m])) by (le, service)
) > 0.5告警疲劳:去重、静默、路由
告警疲劳是可观测性工程最容易被低估的问题。太多的低价值告警会让值班人员对真正的告警麻木。
告警去重策略
1. 聚合告警(not per-instance):
❌ PrometheusRule 按实例告警: 20 个实例同时告警 → 20 条
✓ 聚合告警: sum by (service) — "outpost 服务 50% 实例不可用"
2. 级联抑制(Alertmanager inhibition):
一个数据库故障引发 10 个服务的告警
→ 只告警数据库故障,抑制所有依赖服务的级联告警一个数据库故障可能引发 10 个服务的告警——但林将军只需要知道根源在哪。Alertmanager 的抑制规则可以把级联告警压缩成一条:
# alertmanager-config.yaml
route:
group_by: ['service', 'severity']
group_wait: 30s # 等待 30s 收集同一组的告警
group_interval: 5m # 同一组告警 5 分钟内不重复发送
repeat_interval: 4h # 已解决的告警 4 小时后才重新通知
inhibit_rules:
- source_match:
severity: 'critical'
target_match:
severity: 'warning'
equal: ['service'] # 如果同一个服务的 critical 告警已存在,抑制 warning告警路由分发
route:
receiver: 'default'
routes:
- match:
severity: 'critical'
receiver: 'pagerduty-critical'
continue: false
- match:
severity: 'warning'
receiver: 'slack-warning'
- match:
alertname: 'ErrorBudgetBurningFast'
receiver: 'slack-sre'
- match_re:
service: '(monitoring|logging)'
receiver: 'slack-infra-team'不同级别的告警路由到不同的接收端:
P0 → PagerDuty 电话(值班人员必须响应)
P1 → Slack 的 #oncall 频道 + 电话(快速响应)
P2 → Slack 的系统告警频道(工作时间内处理)
P3/P4 → Jira Ticket 或低优先级的消息聚合(随工单处理)告警应该满足的条件
在创建每条告警规则之前,自问:
1. 这个告警信号是 actionable 的吗?——收到后需要有人做操作吗?
2. 如果不做操作,后果是什么?——用户会受影响吗?
3. 这个告警有 runbook 吗?——收到告警后第一步做什么?
4. 这个告警是否唯一?——同一个 root cause 是否已经产生了另一个告警?如果以上四个问题中任何一个的答案是"否",这条告警规则需要重新设计或者删除。
Grafana 面板设计原则
三个层级的面板架构:
第 1 层: 概述面板(Executive Summary)
目标: 看一眼就知道系统是否健康
内容: 四大黄金信号的总览、错误预算面板、SLO 达标率
只放最重要的 6-8 个面板
适合放在大屏幕监控墙上
第 2 层: 服务面板(Service Dashboard)
目标: 快速定位哪个服务出了问题
内容: 每个服务的 RED 矩阵
每个服务一行:Rate / Errors / Duration
有 drill-down 链接指向第 3 层面板
第 3 层: 深度面板(Deep Dive Dashboard)
目标: 故障排查和根因分析
内容: 慢查询详情、容器日志、节点资源、事件时间线
有大量细节,只在排查时打开面板设计规范:
1. 颜色使用一致:
绿色 = 正常(指标在 SLO 范围内)
黄色 = 接近阈值(错误预算消耗超过 50%)
红色 = 超出阈值(SLO 被违反)
2. 时间范围默认一致:
所有关联面板用相同的时间范围
关联面板可以用时间选择器的"Global"设置
3. 布局原则:
从上到下:宏观到微观、概述到细节
从左到右:时间顺序、依赖顺序(上游→下游)
监控墙用大字号(20+)
4. 减少面板数量:
每个 Grafana 面板有 15-20 个面板足够
超过 30 个面板应该拆分为子面板
用变量选择器让一个面板展示不同维度的数据变量(Variables)
用变量让一个面板切换维度,而不是为每个维度创建独立面板:
# Grafana 变量定义示例
变量: $service
查询类型: PromQL
查询: label_values(http_requests_total, service)
# 然后在面板中使用
rate(http_requests_total{service="$service"}[5m])这样你只需要一个面板,在顶部下拉选择不同的服务。
Observability as Code
"可观测性即代码"——把你的告警规则、Grafana 面板、通知路由作为代码管理。林将军不会允许每个哨站随意更改战报图表的样式——所有仪表盘都应该像军规一样版本控制:
# grafana-dashboard.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: atlas-frontline-dashboard
labels:
grafana_dashboard: "true"
data:
atlas-frontline-dashboard.json: |
{
"title": "Frontline Service Overview",
"panels": [
{
"title": "QPS by Service",
"type": "timeseries",
"targets": [
{
"expr": "sum(rate(http_requests_total[$__interval])) by (service)",
"legendFormat": "{{ service }}"
}
]
}
]
}然后用 Argo CD 管理这个 ConfigMap——当你在 Git 中修改面板 JSON,Argo CD 自动同步到集群。
类似地,PrometheusRule(告警规则)也是代码:
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: atlas-alert-rules
labels:
prometheus: kube-prometheus
spec:
groups:
- name: atlas-frontline
rules:
- alert: AtlasServiceDown
expr: up{job=~"atlas-.*"} == 0
for: 5m
labels:
severity: critical
annotations:
summary: "远征军 {{ $labels.job }} 服务离线"Observability as Code 带来的好处:
- Git 作为所有配置的单一来源
- 面板修改走 PR review 流程
- 面板和告警规则可以版本回退
- 同样的面板配置可以部署到多个环境(区别只有数据源地址)
RUM(Real User Monitoring)和合成监控
你的 Prometheus 数据来自服务器端。但有些重要信号只能从客户端获得。
RUM:真实用户监控
RUM 收集真实用户在浏览器或 APP 中体验到的性能数据。
RUM 能回答的问题(服务器端 Metrics 不能回答的):
- 用户真正感受到的页面加载时间是多久?
- 用户使用的是哪个浏览器版本?哪些浏览器报错多?
- 不同地理位置的用户延迟差异有多大?
- JavaScript 错误导致了多少用户操作失败?常见的 RUM 方案:
- 开源:OpenTelemetry Browser SDK + Grafana Faro(Grafana 的 RUM SDK)
- 商业:Datadog RUM、New Relic Browser
用 OpenTelemetry 埋入浏览器端:
// 前端 RUM 埋点
import { WebTracerProvider } from '@opentelemetry/sdk-web';
import { ConsoleSpanExporter, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-web';
import { ZoneContextManager } from '@opentelemetry/context-zone';
const provider = new WebTracerProvider();
provider.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter()));
provider.register({
contextManager: new ZoneContextManager()
});
// 自动采集页面加载、资源加载、fetch/XHR 调用
const webTracerProvider = new WebTracerProvider({
// 配置采集哪些指标
});
// 手动记录用户交互
const span = webTracerProvider.getTracer('atlas-frontend').startSpan('page-interaction');
span.setAttribute('component', 'report-list');
span.setAttribute('action', 'filter');
// ... 用户操作
span.end();合成监控(Synthetic Monitoring)
合成监控是用脚本模拟用户行为,定期从外部探测你的服务。
# 用 k6 做合成监控脚本
# k6 run synthetic-check.js
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
vus: 1,
duration: '1m',
};
export default function () {
// 模拟用户登录
const loginRes = http.post('https://api.atlas-frontline.com/login', {
username: 'test_scout',
password: 'test_password',
});
check(loginRes, {
'login succeeded': (r) => r.status === 200,
'login response time < 2s': (r) => r.timings.duration < 2000,
});
// 模拟用户查询战报
const reportRes = http.get(
'https://api.atlas-frontline.com/api/reports',
{ headers: { Authorization: `Bearer ${loginRes.json('token')}` } }
);
check(reportRes, {
'reports loaded': (r) => r.status === 200,
'reports returned results': (r) => r.json('reports').length > 0,
});
sleep(1);
}合成监控的价值:
- 从外部看你的服务(CDN、DNS、SSL 证书、API 网关——哪个环节有问题?)
- 持续验证关键用户路径是否正常工作
- 可以部署在多个地理区域——检测区域性的网络问题
- 在真实用户发现问题之前就收到告警
RUM vs 合成监控的关系:
RUM: 真实数据,统计准确,但被动(只有用户在用才有数据)
合成监控: 模拟数据,持续主动探测,但无法反映所有真实用户的设备多样性
最佳实践:两个都用。
合成监控 → 持续探测核心用户路径,发现基础设施和 API 问题
RUM → 收集真实用户的多维数据,发现浏览器/地域/设备相关问题常见陷阱
指标基数失控。 每多一个标签值可变的维度,时间序列呈乘法级数增长。在添加新标签前问:这个字段是否适合做链路追踪的属性而不是 Prometheus 的标签?高基数数据放在 trace 或 log 里。
告警用同一套阈值。 白天和晚上的流量模式不同,工作日和周末的负载不同。单一条阈值会导致白天误报晚上漏报。考虑多级告警或基于历史分布的动态基线(如果有商业方案)。
面板太多没人看。 30 多个面板等于没有面板。维护一个"关键 6 面板",外加一个"我还想看更多"的 drill-down 路径。不要试图在一个页面上展示所有信息。
忽视客户端监控。 服务器端 Metrics 无法发现 CDN 故障、DNS 解析错误、前端 JS 异常、用户浏览器兼容性问题。RUM 和合成监控补上这些盲区。
告警规则的 for 参数太短。 如果 for: 30s,网络抖动就会触发告警。对于大部分场景,for: 5m 是合理的——系统在 5 分钟内恢复,可能不需要告警。
通关挑战
热身:列出你负责的系统中,"适合做 Prometheus 标签"和"不适合做 Prometheus 标签"的字段各 5 个。检查现在的采集配置中是否有高基数标签风险。
挑战:设计一个 Grafana 三层面板架构。第 1 层是 6 个概述面板,第 2 层是每个服务的 RED 矩阵,第 3 层是某个服务的深度排查面板。用 Observability as Code 的方式(ConfigMap + Argo CD)部署它们。
# 用 Grafana 的 API 导出面板 JSON
# 通过 curl 获取现有面板
curl -s -H "Authorization: Bearer $GRAFANA_API_TOKEN" \
https://grafana.atlas.local/api/dashboards/uid/atlas-frontline-overview \
| jq '.dashboard' > frontline-overview.json
# 放入 ConfigMap
kubectl create configmap frontline-overview-dashboard \
--from-file=frontline-overview.json \
--dry-run=client -o yaml > frontline-dashboard-cm.yaml观察:在 Prometheus 中找出过去一个月内触发次数最多的告警规则。分析这些告警中哪些是有价值的、哪些是噪音。调整告警阈值或删除无价值的告警规则。
排障:你值班时收到了一连串告警——"Postgres 连接池满""订单服务 P99 飙升""上游服务超时"。但实际根因是网络交换机故障。为 Alertmanager 配置抑制规则,让网络设备级别的告警抑制上层的业务告警。
# 抑制规则配置
inhibit_rules:
- source_match:
severity: 'critical'
alertname: 'NodeNetworkDown'
target_match:
severity: 'warning'
equal: ['instance', 'region']验收标准
- 能区分四大黄金信号、USE 和 RED 方法论及其适用场景
- 能识别并处理 Prometheus 的高基数问题
- 能使用 PromQL 的子查询和 recording rules
- 能设计多层告警路由和去重策略
- 能设计三层 Grafana 面板架构
- 理解 RUM 和合成监控的定位和实现方式
常见卡点
PromQL 查询太慢怎么办? 使用 recording rules 预聚合。检查查询的时间范围——跨 30 天的 unaggregated 数据大概率会慢。拆成短时间窗口 + recording rule。
Grafana 面板加载太慢? 减少面板数量,减少每个面板的查询数量,使用 recording rules。检查 Prometheus 实例的硬件资源是否充足。考虑使用 Mimir/Thanos 做长期存储和查询加速。
RUM 数据量太大怎么办? 采样。不是每个用户每次操作都需要采集。按用户比例采样(10% 或 1%),错误路径 100% 采样。采样策略比存储所有数据更有工程价值。
合成监控的脚本维护成本高? 只编写最重要的用户路径(登录、核心查询、下单——根据你的业务场景)。扩展到 5-10 个关键路径足够覆盖 80% 的用户体验。
现在不需要理解
- 分布式追踪的高级采样策略(Tail Sampling、Probabilistic Sampling)——在接入 Tracing 半年、遇到存储瓶颈时再研究
- Mimir / Thanos / VictoriaMetrics 的架构差异——当你需要长期(超过 30 天)存储 Prometheus 数据时再对比选择
- eBPF 级别的可观测性(Pixie、Cilium Hubble)——极为强大的内核级观测手段,但通常需要技术基础设施已经非常成熟
旅人笔记
可观测性是"能用,但容易滥用"的系统。Prometheus 扛不住高基数,Grafana 面板一多就没人看,告警多了就没人信。从三支柱到四大黄金信号,从 USE 到 RED,这些方法论帮你从"把所有数据画成图"升级到"有目的性地观察系统的健康状况"。告警去重、Observability as Code、RUM 和合成监控——这些是你在"可观测性基础"之上增加的工程智慧。你不是在搭建更复杂的监控系统——你是在让系统变得更安静,只在真正需要你注意的时候才说话。
本卷回顾
这是第七卷的终点。从分布式系统的八个基础章节开始,你了解了一条从理论到工程的道路。但现在远征军的战线延伸得更远了——SRE 的错误预算让你能科学地决策可靠性与速度,IaC 和 GitOps 把基础设施变成了工程代码,K8s 生产实践让你能安全地运行有状态服务,可观测性的深化让你不再被告警噪音淹没。
林将军说:"你现在是一个真正的远征军指挥官了——你知道集群怎么跑,也知道系统什么时候该打架、什么时候该休整。但记住,系统永远会有你不能预见的问题。下一个战场的敌人不是技术,是政策。"
下一站预告
第七卷,从分布式系统的基础理论走到生产实践,你经历了一条漫长的山路。你学习了 RPC、共识算法、分布式存储和计算、微服务、容器编排。然后在四章深化中,你学会了用 SRE 的量化方法管理学可靠性、用 IaC 和 GitOps 把资源管理代码化、用生产级 K8s 实践管理复杂工作负载、用深化可观测性来对抗告警疲劳和度量盲区。你的工具箱已经装满。远方是 Vol 8 的安全边境——你想要继续远征,就必须学会保护你的系统和数据。那是下一个战场。