跳到内容

元数据卡

  • 前置知识:第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_total

USE 更适合基础设施层面的诊断(节点、磁盘、网络接口)。它问你:每个资源的"容量还有多少"。

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)来区分时间序列。每个不同的标签值组合产生一条新的时间序列。

promql
# 以下是一个高基数查询——小心!
# 如果 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 进阶

你已经熟悉了基础的 ratesumhistogram_quantile。生产环境还需要以下技能:

子查询(Subquery)

promql
# 计算过去 1 小时内,每 5 分钟的错误率最大值
max_over_time(
  rate(http_requests_total{status=~"5.."}[5m])
  [1h:1m]  # 过去 1 小时,步长 1 分钟
)

Recording Rules

预计算复杂的 PromQL 表达式,减少查询时的计算开销:

yaml
# 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,不需要每次都执行复杂计算。

多值聚合

promql
# 计算每个服务的 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 的抑制规则可以把级联告警压缩成一条:

yaml
# 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

告警路由分发

yaml
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)

用变量让一个面板切换维度,而不是为每个维度创建独立面板:

yaml
# Grafana 变量定义示例
变量: $service
查询类型: PromQL
查询: label_values(http_requests_total, service)

# 然后在面板中使用
rate(http_requests_total{service="$service"}[5m])

这样你只需要一个面板,在顶部下拉选择不同的服务。


Observability as Code

"可观测性即代码"——把你的告警规则、Grafana 面板、通知路由作为代码管理。林将军不会允许每个哨站随意更改战报图表的样式——所有仪表盘都应该像军规一样版本控制:

yaml
# 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(告警规则)也是代码:

yaml
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 埋入浏览器端:

javascript
// 前端 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)

合成监控是用脚本模拟用户行为,定期从外部探测你的服务。

yaml
# 用 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 → 收集真实用户的多维数据,发现浏览器/地域/设备相关问题

常见陷阱

  1. 指标基数失控。 每多一个标签值可变的维度,时间序列呈乘法级数增长。在添加新标签前问:这个字段是否适合做链路追踪的属性而不是 Prometheus 的标签?高基数数据放在 trace 或 log 里。

  2. 告警用同一套阈值。 白天和晚上的流量模式不同,工作日和周末的负载不同。单一条阈值会导致白天误报晚上漏报。考虑多级告警或基于历史分布的动态基线(如果有商业方案)。

  3. 面板太多没人看。 30 多个面板等于没有面板。维护一个"关键 6 面板",外加一个"我还想看更多"的 drill-down 路径。不要试图在一个页面上展示所有信息。

  4. 忽视客户端监控。 服务器端 Metrics 无法发现 CDN 故障、DNS 解析错误、前端 JS 异常、用户浏览器兼容性问题。RUM 和合成监控补上这些盲区。

  5. 告警规则的 for 参数太短。 如果 for: 30s,网络抖动就会触发告警。对于大部分场景,for: 5m 是合理的——系统在 5 分钟内恢复,可能不需要告警。


通关挑战

  • 热身:列出你负责的系统中,"适合做 Prometheus 标签"和"不适合做 Prometheus 标签"的字段各 5 个。检查现在的采集配置中是否有高基数标签风险。

  • 挑战:设计一个 Grafana 三层面板架构。第 1 层是 6 个概述面板,第 2 层是每个服务的 RED 矩阵,第 3 层是某个服务的深度排查面板。用 Observability as Code 的方式(ConfigMap + Argo CD)部署它们。

bash
# 用 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 配置抑制规则,让网络设备级别的告警抑制上层的业务告警。

yaml
# 抑制规则配置
inhibit_rules:
- source_match:
    severity: 'critical'
    alertname: 'NodeNetworkDown'
  target_match:
    severity: 'warning'
  equal: ['instance', 'region']

验收标准

  • 能区分四大黄金信号、USE 和 RED 方法论及其适用场景
  • 能识别并处理 Prometheus 的高基数问题
  • 能使用 PromQL 的子查询和 recording rules
  • 能设计多层告警路由和去重策略
  • 能设计三层 Grafana 面板架构
  • 理解 RUM 和合成监控的定位和实现方式

常见卡点

  1. PromQL 查询太慢怎么办? 使用 recording rules 预聚合。检查查询的时间范围——跨 30 天的 unaggregated 数据大概率会慢。拆成短时间窗口 + recording rule。

  2. Grafana 面板加载太慢? 减少面板数量,减少每个面板的查询数量,使用 recording rules。检查 Prometheus 实例的硬件资源是否充足。考虑使用 Mimir/Thanos 做长期存储和查询加速。

  3. RUM 数据量太大怎么办? 采样。不是每个用户每次操作都需要采集。按用户比例采样(10% 或 1%),错误路径 100% 采样。采样策略比存储所有数据更有工程价值。

  4. 合成监控的脚本维护成本高? 只编写最重要的用户路径(登录、核心查询、下单——根据你的业务场景)。扩展到 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 的安全边境——你想要继续远征,就必须学会保护你的系统和数据。那是下一个战场。

Built with VitePress | Software Systems Atlas