概述
系列定位与前言
本文是"高并发与稳定性工程"系列的第 7 篇。在前 6 篇文章中,我们由底向上,从单点的限流算法、熔断降级、服务隔离,到全链路的容量规划、混沌工程,乃至百万级秒杀架构的实战落地,逐步构建了一套强悍的防御工事。然而,这一切精密的设计,若缺乏一套敏锐的"神经系统"进行实时感知与反馈,都无异于在黑暗中航行的巨轮。监控与告警,正是稳定性体系拼图的最后一块,它将"被动防御"升级为"主动感知",将"事后修复"进化为"数据驱动"。
总结性引言
设想一下,双十一零点的流量洪峰如期而至。你的订单服务 P99 延迟从日常的 150ms 悄然攀升至 2s,错误率从 0.01% 跳变到 1%。如果你没有提前定义好"什么是正常的"------即服务等级指标(SLI)和服务等级目标(SLO),你可能根本意识不到这已是严重故障,直到用户因无法下单而投诉。如果你仅配置了"错误率 > 5%"的静态阈值告警,面对这种缓慢恶化的延迟,你的告警系统将全程静默,因为错误率"尚未达标"。这就是传统监控的"盲区"。
监控与告警体系,就是为微服务安装的"心率监护仪"。SLI 是心跳频率,SLO 是正常心率范围,Error Budget 是安全余量,Burn Rate 是危险信号,而 Alertmanager 则是最终发出的呼救信号。 本文将带你从零开始,为电商订单系统建立一套完整的 SLO 监控体系:从定义订单创建的四个核心 SLI 开始,设定 99.9% 请求 < 200ms 的 SLO,通过 Micrometer + Prometheus 采集指标,在 Grafana 中搭建 Burn Rate / Error Budget / RED 三板斧可视化面板,并通过 Alertmanager 配置 P0/P1/P2 三级告警路由,最终在模拟的延迟故障中验证全链路告警是否按预期触发。
核心要点
- SLI/SLO/SLA 方法论:掌握四类黄金指标的选型、基于历史数据与用户容忍度的 SLO 设定、Error Budget 的计算与应用,理解 SLI → SLO → SLA 的金字塔式依赖关系。
- Prometheus 指标模型与 PromQL 深度 :精通 Counter/Gauge/Histogram/Summary 的内部原理与生产选型,掌握
histogram_quantile计算 P99 的机制,辨析rate与increase的陷阱,利用 Recording Rules 进行预聚合。 - Grafana 的 SLO 可视化面板设计:构建 Burn Rate 多窗口告警面板,实现 Error Budget 消耗仪表盘,组合 RED 面板的 PromQL,让系统状态一目了然。
- Alertmanager 的多级告警路由与降噪:设计 P0/P1/P2 三级路由树,通过分组、抑制和静默机制,确保在告警风暴中,将正确的信息在正确的时间送达正确的人。
- 混沌验证:通过主动注入故障,验证从指标采集到告警通知的全链路闭环有效性。
- 电商贯穿案例:所有知识点都将服务于一个核心案例------为电商订单系统建立 SLO 监控体系。
文章组织架构图
架构图说明
- 总览说明:全文遵循一条清晰的"理论 → 工具 → 实践 → 验证"认知路径。文章首先建立 SLO 方法论这一理论基石,随后深入 Prometheus、Grafana 和 Alertmanager 三大核心工具,再通过混沌工程验证告警体系的有效性,最后用贯穿案例和面试专题巩固所有知识点。
- 逐模块说明 :
- 模块 1:建立监控的"世界观",定义什么是好服务,什么是坏服务。
- 模块 2:解决"如何精确测量"的问题,深入指标模型的底层。
- 模块 3:将冰冷的测量数据转化为直观的视觉洞察,让问题无所遁形。
- 模块 4:将洞察转化为行动,构建一个智能、降噪的告警响应系统。
- 模块 5:通过主动攻防,验证行动链路的可靠性。
- 模块 6:用一个生产级案例将所有模块的知识点进行串联和实战。
- 模块 7:缝合整个系列的知识网络,体现监控作为"神经系统"的核心地位。
- 模块 8:拔高视角,应对高频面试挑战。
- 关键结论 :监控体系的终极目标不是"采集更多指标",而是 "当 SLO 面临风险时,让对的人在对的时间看到对的信息"。Burn Rate 多窗口告警是 SRE 监控的最佳实践------它既能快速感知对 SLO 构成威胁的故障,又能通过双窗口机制有效防止误报。理解 PromQL 的本质,不是为了炫技,而是为了在 Grafana 里写出精确表征用户痛感的 SLO 查询。
1. SLI/SLO/SLA 方法论与工程落地
没有度量,就没有管理。在稳定性领域,Google SRE 的 SLI/SLO/SLA 方法论为我们提供了一套严谨、可量化的服务管理框架。它要求我们用精确的数学语言来定义和衡量系统的可靠性。
1.1 SLI:服务等级指标------如何定义"好坏"
SLI(Service Level Indicator,服务等级指标)是对服务某一特定属性进行度量的量化指标。它是整个体系的基石,回答"我们测量什么"的问题。SRE 将海量的监控指标归纳为四类黄金指标,覆盖了从用户体验到系统健康的方方面面。
在电商订单系统中,我们的 SLI 定义如下:
-
延迟(Latency):
- 定义:衡量处理一个请求的快慢。需要注意的是,指标是"对用户有意义的响应时间",而非服务端的内部耗时。例如,订单创建接口从网关接收到请求到返回响应体的完整耗时。
- 选型 :必须使用分位值(Percentile),而非平均值。平均值会掩盖长尾问题。常用 P50(中位数)、P90、P99。我们将 P99 延迟 作为核心 SLI。
- 黄金指标采集类型 :
Histogram。 - 我们的 SLI :
order_create_p99_latency_seconds。
-
流量(Traffic):
- 定义:衡量系统的负载压力。对订单服务而言,就是每秒处理的请求数量。
- 选型:QPS(Queries Per Second)或 TPM(Transactions Per Minute)。
- 黄金指标采集类型 :
Counter。 - 我们的 SLI :
rate(order_create_total[1m]),即每秒下单请求量。
-
错误(Errors):
- 定义:衡量失败请求的比例。注意区分显式失败(如 HTTP 5xx)和隐式失败(如 HTTP 200 OK 但 body 里包含业务错误码)。
- 选型:错误率 = 失败请求数 / 总请求数。
- 黄金指标采集类型 :
Counter。 - 我们的 SLI :
order_create_error_rate,定义为rate(order_create_errors_total[5m]) / rate(order_create_total[5m])。
-
饱和度(Saturation):
- 定义:衡量服务有多"满",即资源消耗逼近容量的程度。它通常是系统最脆弱的环节的指标。
- 选型:CPU 利用率、内存使用量、线程池活跃线程数、消息队列积压量、数据库连接池使用数。
- 黄金指标采集类型 :
Gauge。 - 我们的 SLI :Tomcat 请求处理线程池的
active_threads和 DB 连接池的active_connections。
1.2 SLO:服务等级目标------如何定义"足够好"
SLO(Service Level Objective,服务等级目标)是基于 SLI 设定的、服务可靠性的量化目标。它回答"我们有多可靠"的问题。SLO 是内部目标,用于指导开发和运维决策。
-
设定原则:
- 基于历史数据 :分析过去 28 天的
order_create_p99_latency_seconds,发现峰值约 150ms。 - 基于用户容忍度:根据 UX 研究,用户对电商下单的延迟容忍上限在 200ms 左右,超过此值用户体验急剧下降。
- 不要追求 100%:100% 可靠性的成本是指数级的,而且也不可能达到。留出错误预算,为创新和失败留出空间。
- 目标定义形式 :
99.9% 的请求在 200ms 内完成。
- 基于历史数据 :分析过去 28 天的
-
我们的 SLO:
- 延迟 SLO :99.9% 的订单创建请求延迟 < 200ms(即 P99 ≤ 200ms,且通过
histogram_quantile计算的分位值在统计周期内满足此目标)。 - 错误率 SLO:99.99% 的请求成功(即错误率 < 0.01%)。
- 延迟 SLO :99.9% 的订单创建请求延迟 < 200ms(即 P99 ≤ 200ms,且通过
1.3 Error Budget(错误预算)
Error Budget(错误预算)是 SLO 的副产品,是 1 减去 SLO。它明确了在一定时间窗口内,系统被"允许"出现故障的次数。错误预算 = (1 - SLO) × 总请求数。
- 例:设 SLO 为 99.9%(延迟 < 200ms)。在过去 28 天,订单服务总请求为 100 亿次。那么,错误预算就是允许 100 亿次请求中有 0.1%,即 1000 万次请求延迟超过 200ms。
- 工程意义 :
- 发布决策:当剩余错误预算充足时,可以大胆地进行功能发布和架构变更。
- 冻结发布:当错误预算消耗殆尽(或即将耗尽),应立即停止所有非紧急的变更,将全部工程力量投入到修复可靠性问题上。这实现了业务与运维在决策上的客观统一。
1.4 SLA:服务等级协议------商业承诺
SLA(Service Level Agreement,服务等级协议)是服务提供商与客户之间的法律或商业合同。它回答"如果服务不可靠会发生什么"的问题。
- 与 SLO 的关系:SLA 基于 SLO,但通常比 SLO 更宽松。例如,SLO 可能是 99.9%(内部目标),而对外的 SLA 是 99.5%(商业承诺)。这中间的 0.4% 的差异,是一个缓冲地带,用于在触发 SLA 赔偿条款前,内部先进行问题修复。
- 内容:包含若不满足 SLA 时的赔偿条款(如赔偿代金券)和问题升级路径。
SLI/SLO/SLA 关系金字塔图
商业合同
如: 99.5%可用性, 含赔偿条款"] end subgraph mid_slo ["中层 SLO"] SLO["SLO 服务等级目标
内部工程目标
如: 99.9%请求延迟<200ms"] end subgraph bot_sli ["底层 SLI"] SLI["SLI 服务等级指标
量化度量
如: P99延迟, 错误率, QPS"] end SLA -- "基于更宽松的" --> SLO SLO -- "依赖于" --> SLI ErrorBudget["Error Budget 错误预算
(1 - SLO) x 总请求
发布决策的燃料"] SLO -- "衍生出" --> ErrorBudget
图表说明:
- 图表主旨概括:该金字塔图清晰地展示了 SLI、SLO、SLA 三者之间的层次依赖和价值递进关系,从底层的技术度量到顶层的商业承诺。
- 逐层/逐元素分解 :
- 底层 (SLI):是纯粹的工程指标,客观、中立。
- 中层 (SLO):是基于 SLI 设定的内部目标,是团队对服务可靠性的自我要求,直接衍生出 Error Budget 这一核心决策依据。
- 顶层 (SLA):是面向客户、带有法律或商业后果的外部承诺。
- 设计原理映射:这种分层设计实现了关注点分离。开发人员关心 SLI 和 SLO,法务和商务关心 SLA。SLO 为开发团队提供了一个清晰、可量化的可靠性目标,而 Error Budget 则将这个目标转化为了一个可用于权衡"创新速度 vs. 系统稳定"的决策框架。
- 工程联系与关键结论 :在生产环境中,我们的所有监控、告警和面板设计,都应围绕 SLO 展开,而非简单地监控 CPU 或内存。 监控系统的核心问题应是"我们是否正走在实现本月 SLO 的正确轨道上?",而 Burn Rate 告警正是回答这个问题的关键。
2. Prometheus 指标模型与 PromQL 深度
Prometheus 是整个监控体系的核心采集与查询引擎。理解其指标模型和 PromQL,是构建高效监控体系的基础。
2.1 四种核心指标类型精讲
Micrometer 作为指标门面,将 Spring Boot 应用的内部状态翻译为 Prometheus 可以理解的格式。我们需要在代码中正确地选择指标类型。
-
Counter(计数器)
-
原理:只增不减的累计值(除非进程重启)。完美对应 QPS、请求总数、错误次数等。
-
选型:当你想知道"发生了多少次"时使用。
-
示例 :
java// Micrometer 埋点 MeterRegistry registry = ...; Counter orderCounter = Counter.builder("order_create_total") .description("Total number of order creations") .tag("env", "prod") .register(registry); // 业务代码中 orderCounter.increment(); -
PromQL 查询 :
rate(order_create_total[5m])计算过去 5 分钟内每秒的平均增长率。
-
-
Gauge(仪表盘)
-
原理:可增可减的瞬时值,代表一个状态快照,如 CPU 温度、内存使用、线程池活跃数。
-
选型:当你想知道"当前的值是多少"时使用。
-
示例 :
java// 获取 Tomcat 线程池的当前活跃线程数 AtomicInteger activeThreads = ...; Gauge.builder("tomcat_threads_active", activeThreads, AtomicInteger::get) .description("Active threads in Tomcat") .register(registry); -
PromQL 查询 :
tomcat_threads_active直接查询瞬时值。
-
-
Histogram(直方图)
-
原理 :在客户端预先定义一系列桶(bucket),对观测值进行采样并落入对应桶中。这是计算 P99 延迟的基石。
-
选型:当你想知道"值的分布情况",且需要跨实例聚合时,强烈推荐。它可以计算平均值、任意分位值,并可聚合。
-
工作机制 :
Histogram会创建一组指标:<basename>_bucket{le="<upper_bound>"}:累计计数,表示观测值 ≤upper_bound的次数。<basename>_sum:所有观测值的总和。<basename>_count:观测值的总次数。
-
示例 :
javaTimer timer = Timer.builder("order_create_duration") .description("Order creation duration") .serviceLevelObjectives( Duration.ofMillis(10), Duration.ofMillis(50), Duration.ofMillis(100), Duration.ofMillis(200), Duration.ofMillis(500), Duration.ofMillis(1000)) .publishPercentileHistogram() // 自动生成一组合理的桶 .register(registry); // 业务代码中记录耗时 timer.record(() -> { /* 创建订单逻辑 */ }); -
核心查询 :
histogram_quantile(0.99, rate(order_create_duration_bucket[5m]))。PromQL 先通过rate计算每个桶在 5 分钟内的平均增长速度,然后histogram_quantile函数利用这些桶的分布,线性插值估算出第 99 分位的值。
-
-
Summary(摘要)
- 原理:在客户端本地直接计算特定分位值(如 P50, P90, P99),并输出为单独的指标。它不需要服务端计算。
- 选型 :仅当你无法使用
Histogram(如无法预定义桶),或需要在客户端精确计算分位值且不需要跨实例聚合时使用。 - 核心缺陷 :客户端 A 的 P99 不能与客户端 B 的 P99 在服务端通过
avg等方式进行聚合。在微服务架构中,我们几乎总是需要跨实例聚合,因此Histogram是默认首选。
2.2 PromQL 深度解析与生产陷阱
-
ratevsincrease- 共同点 :两者都作用于
Counter类型,用于处理单调递增序列,并能自动处理计数器重置(进程重启)。 rate(v range-vector):计算范围向量中时间序列的每秒平均增长率。适合绘制趋势图(QPS)。它通过外推将结果规约到秒。increase(v range-vector):计算范围向量中时间序列的总增量。例如,increase(order_create_total[1h])返回过去一小时的请求总量。- 误用陷阱 :
- 对 Gauge 使用
rate:完全无意义。Gauge 不是单调的,rate的计算结果不可预测。 - 用
_sum/_count计算平均值代替分位值 :rate(order_create_duration_sum[5m]) / rate(order_create_duration_count[5m])只能得到平均延迟,完全抹平了长尾,是监控系统的天敌。 rate的时间窗口过短 :rate(...[1m])图形锯齿感重,易受抖动影响,也容易被尖刺触发误报。生产环境通常用[5m]或[10m]作为窗口。
- 对 Gauge 使用
- 共同点 :两者都作用于
-
Recording Rules(预聚合)
-
问题 :
histogram_quantile(0.99, rate(order_create_duration_bucket[5m]))在流量大、实例多时,每次查询都需要扫描海量原始数据,Grafana 面板加载缓慢。 -
方案:通过 Recording Rules 预先计算并存储高消耗的表达式结果。
-
配置示例 (
rules.yml) :yamlgroups: - name: order_service_rules interval: 60s # 每分钟评估一次 rules: - record: job:order_create_duration_bucket:rate5m expr: rate(order_create_duration_bucket[5m]) - record: job:order_create_p99:5m expr: histogram_quantile(0.99, sum by(le) (job:order_create_duration_bucket:rate5m)) -
工程效益 :Grafana 直接查询
job:order_create_p99:5m,性能提升数十倍。这也使得你的 SLO 计算更快、更高效。
-
Prometheus 指标采集与 PromQL 查询架构图
Micrometer 暴露 /actuator/prometheus] --> P A2[订单服务 Pod 2
Micrometer 暴露 /actuator/prometheus] --> P A3[库存服务 Pod
Micrometer 暴露指标] --> P end subgraph 采集与存储 P[Prometheus Server
Service Discovery & Scrape] P -- 存储 --> TSDB[(Prometheus TSDB
本地时序数据库)] end subgraph 查询与计算 TSDB -- 原始数据查询 --> PQ[PromQL Engine] RR[Recording Rules
预聚合规则] -- 生成预计算指标 --> PQ end subgraph 消费端 PQ -- 即时/范围查询 --> G[Grafana
可视化面板] PQ -- 告警规则评估 --> AM[Alertmanager
告警与路由] end
图表说明:
- 图表主旨概括:该图描述了从业务服务暴露指标到最终消费的完整数据链路,突出了 Prometheus 的中心地位。
- 逐层/逐元素分解 :
- 数据源 :Spring Boot 应用通过 Micrometer 在
/actuator/prometheus端点暴露文本格式的指标。 - 采集与存储:Prometheus Server 通过 K8s Service Discovery 发现抓取目标,定期 Pull 指标数据并存入本地 TSDB。
- 查询与计算:PromQL 引擎处理来自 Grafana 的查询请求,并能利用 Recording Rules 生成的预聚合数据加速查询。
- 消费端:Grafana 用于可视化,Alertmanager 用于处理由告警规则生成的告警。
- 数据源 :Spring Boot 应用通过 Micrometer 在
- 设计原理映射:Pull 模型保证了监控系统的健壮性(被监控对象崩溃不影响 Prometheus),TSDB 为海量时序数据提供了高效读写能力,Recording Rules 体现了空间换时间的思想。
- 工程联系与关键结论 :PromQL 是连接原始指标和业务洞察的桥梁。 花时间编写高质量的 Recording Rules 是对后期运维体验的极佳投资。永远不要直接在高流量面板上查询原始 Histogram 的 P99。
3. Grafana 的 SLO 可视化面板设计
Grafana 将 PromQL 的查询结果转化为直观的图形。我们的目标是设计一套以 SLO 为核心的仪表盘,让任何人一眼就能看出服务的健康状态及其对 SLO 的影响。
3.1 Burn Rate 多窗口告警面板
这是整个 SLO 监控的核心创新,直接解决了传统阈值告警的痛点。
-
设计原理:
- Burn Rate(燃烧速率):衡量我们消耗 Error Budget 的速度有多快。
- 公式 :
Burn Rate = 时间窗口内错误率 / 错误预算率。 - 例:SLO 为 99.9%,则错误预算率为 0.1%。如果过去 1 小时内,错误率飙升到 1.44%,那么 Burn Rate = 1.44 / 0.1 = 14.4。这意味着我们正以 14.4 倍于"正常"的速度消耗我们的错误预算。
- 多窗口检测 :
- 短窗口(1h):用于快速检测。当 1h 的 Burn Rate 超过阈值(如 14.4)时,说明出现了严重、紧迫的问题。
- 长窗口(5m):用于验证持续性问题。当 5m 的 Burn Rate 也超过阈值时,才触发告警。这样可以防止由于短时、轻微的抖动(1h 窗口偶然飙升,但很快恢复)导致的误报。
- 双重确认 :只有当
1h Burn Rate > 14.4且5m Burn Rate > 14.4时,才判定 SLO 处于高风险状态。
-
核心 PromQL: 假设我们的 SLO 目标是 99.9% 的请求延迟 < 200ms。我们定义一个"好"的事件为请求延迟 ≤ 200ms 桶中的计数。
promql# 1. 查询 5 分钟内的总请求速率 total_rate = sum(rate(order_create_duration_count[5m])) # 2. 查询 5 分钟内"好"的事件速率 (即请求延迟 <= 0.2s 的桶的速率) good_rate = sum(rate(order_create_duration_bucket{le="0.2"}[5m])) # 3. 计算当前错误率 error_rate = 1 - (good_rate / total_rate) # 4. 计算 1 小时的 Burn Rate (错误率 / 错误预算率) # 错误预算率 = 1 - 0.999 = 0.001 burn_rate_1h = (1 - (sum(rate(order_create_duration_bucket{le="0.2"}[1h])) / sum(rate(order_create_duration_count[1h])))) / 0.001 -
面板设计:
- 展示两条线:一条是
burn_rate_1h,一条是burn_rate_5m。 - 设定一条红色警戒线,值为 14.4。
- 当两条线均跨过警戒线时,面板变红高亮,一目了然。
- 展示两条线:一条是
3.2 Error Budget 消耗仪表盘
这个面板直观地展示我们当月剩下的"犯错额度"。
-
设计:一个进度条或仪表盘。
-
核心 PromQL :
promql# 计算过去 28 天的"坏"事件总数 bad_events_28d = sum(increase(order_create_duration_count{le="+Inf"}[28d])) - sum(increase(order_create_duration_bucket{le="0.2"}[28d])) # 计算错误预算消耗百分比 budget_consumed_percent = (bad_events_28d / (sum(increase(order_create_duration_count[28d])) * (1 - 0.999))) * 100 -
告警规则联动 :
- 消耗 > 50%:黄色预警,提醒团队需要谨慎发布。
- 消耗 > 80%:红色告警,应立刻停止新功能上线,开启全员稳定性整治。
3.3 RED 面板与 USE 面板
-
RED 面板 (Rate, Errors, Duration):
- 面向每个服务。这是诊断单个服务问题的第一站。
- Rate :
sum(rate(order_create_total[5m]))。 - Errors :
sum(rate(order_create_total{status="5.."}[5m])) / sum(rate(order_create_total[5m]))。 - Duration :
histogram_quantile(0.99, sum(rate(order_create_duration_bucket[5m])) by (le))。 - 联动:当 Burn Rate 面板告警时,直接跳转到对应服务的 RED 面板,可以快速判断是流量、错误还是延迟的变化导致了 SLO 风险。
-
USE 面板 (Utilization, Saturation, Errors):
- 面向基础设施(Node, K8s Pod)。补充 RED 面板的微观视角。
- 例如,Tomcat 线程池饱和度(
tomcat_threads_active / tomcat_threads_max)、数据库连接池饱和度等。
Grafana SLO 仪表盘的多面板布局图
剩余预算: 85% | 消耗率: 15%
PromQL: 计算过去28天坏事件数/总预算"] end subgraph second_row ["第二行 核心SLO风险"] B["🔥 Burn Rate 多窗口告警面板 (P0)"] C["订单创建 P99 延迟 SLO"] end subgraph third_row ["第三行 服务级诊断"] D["订单服务 RED 面板
Rate / Errors / Duration"] E["库存服务 RED 面板
Rate / Errors / Duration"] end subgraph bottom_row ["底行 基础设施"] F["K8s 集群 USE 面板
CPU/Mem/Disk/Net"] G["Tomcat & DB 连接池
线程/连接饱和度"] end A --- B --- C B --- D --- E D --- F --- G classDef row1 fill:#f0f4ff,stroke:#4f6ef6,stroke-width:2px,color:#1e3a8a classDef row2 fill:#fff0e6,stroke:#c2410c,stroke-width:2px,color:#7c2d12 classDef row3 fill:#e6f7f2,stroke:#059669,stroke-width:2px,color:#064e3b classDef row4 fill:#f1f5f9,stroke:#334155,stroke-width:2px,color:#0f172a class top_row row1 class second_row row2 class third_row row3 class bottom_row row4
图表说明:
- 图表主旨概括:这是一个典型的"由总到分"的监控仪表盘布局,从一个全局的 Error Budget 开始,逐层下钻到具体的 SLO、服务乃至基础设施。
- 逐层/逐元素分解 :
- 顶行:决策者视图,直接回答"我们现在安全吗?"
- 第二行:SRE 核心视图,Burn Rate 面板是火警探测器,延迟面板展示历史趋势。
- 第三行:开发人员视图,RED 面板用于快速定位是哪个服务出现问题。
- 底行:基础设施视图,用于排查是否是资源问题导致的延迟或错误。
- 设计原理映射:这个布局遵循了 5-Whys 和逐层下钻的故障排除逻辑。当顶层面板的数值变红时,可以立刻向下看,找到问题的具体源头。
- 工程联系与关键结论 :一个设计优良的监控面板,应该能在 30 秒内让一个有经验的运维工程师判断出故障的影响范围和可能的原因方向。 将所有关于 SLO 的信息聚合在同一个地方,是实现这一目标的最佳实践。
4. Alertmanager 的多级告警路由与降噪
告警的本质是"把人叫醒"。而 Alertmanager 的艺术在于,在正确的时间,用正确的方式,把正确的人叫醒,并且只叫一次。
4.1 告警分级策略 (P0/P1/P2)
| 级别 | 定义 | 通知渠道 | 响应时间 | 例子 |
|---|---|---|---|---|
| P0(紧急) | SLO 面临立即、严重的风险。服务完全或部分不可用。 | 电话 + 企微/钉钉群 | 5分钟内 | Error Budget 消耗 > 80%;Burn Rate (1h & 5m) > 14.4 |
| P1(严重) | 服务降级,但未完全中断。SLO 有一定风险。 | 企微/钉钉群 | 15分钟内 | P99 延迟 > 3倍 SLO 值;错误率 > 5% |
| P2(一般) | 需要关注,但无即时风险。通常是饱和度类指标。 | 邮件 | 1小时内 | CPU > 80%;连接池使用率 > 80% |
4.2 路由树与分组配置
Alertmanager 的核心配置是 route。它是一个树状结构,决定了告警的流向。
yaml
# alertmanager.yml
route:
receiver: 'default-receiver' # 默认接收器
group_by: ['alertname', 'severity'] # 分组标签
group_wait: 10s # 第一次告警到来时,等待10秒,以便同一组的其他告警到来后一起发送
group_interval: 5m # 同组后续告警的发送间隔
repeat_interval: 4h # 如果告警未恢复,重复发送的间隔
routes:
# P0 告警路由
- match:
severity: p0
receiver: p0-receiver
continue: false # 匹配后不再向下匹配
# P1 告警路由
- match:
severity: p1
receiver: p1-receiver
continue: false
# 接收器定义
receivers:
- name: 'p0-receiver'
webhook_configs:
- url: 'http://your-incident-manager/p0' # 触发紧急工单
wechat_configs:
- corp_id: 'xxx'
to_user: '@all'
agent_id: 'yyy'
api_secret: 'zzz'
- name: 'p1-receiver'
wechat_configs:
- corp_id: 'xxx'
to_user: '@all'
agent_id: 'yyy'
api_secret: 'zzz'
# ...
group_by:将同一"症状"的告警打包。 例如,一个后端服务出问题,可能导致 50 个 API 的延迟告警。通过group_by: ['alertname', 'severity'],这 50 条告警会合并成一条"您的订单服务有多个P1延迟告警"的通知,而不是 50 次电话轰炸。group_intervalvsrepeat_interval:group_interval控制的是有新告警加入现有组 时的发送频率。repeat_interval控制的是没有任何变化的告警的提醒频率。
4.3 抑制与静默
-
抑制 (Inhibition) :高级别告警触发后,自动屏蔽由其引发的低级别告警,防止告警风暴。
yamlinhibit_rules: - source_match: severity: 'p0' alertname: 'HighErrorRate' target_match: severity: 'p1' alertname: 'HighLatency' # 含义:当订单服务因高错误率触发P0告警时,由该错误引发的P1高延迟告警将被抑制。 -
静默 (Silence):在计划内维护窗口,通过 Alertmanager UI 或 API 创建静默规则,避免产生无效告警。这是运维纪律的体现。
Alertmanager 多级告警路由树状图
Prometheus Alert Rules] --> B(Alertmanager) subgraph B [Alertmanager 核心] direction TB C[路由树 Root Route] C -- match severity=p0 --> D[P0 接收器
电话+企微] C -- match severity=p1 --> E[P1 接收器
企微群] C -- match severity=p2 --> F[P2 接收器
邮件] D -.-> G((抑制规则
P0抑制同服务P1)) E -.-> G C -.-> H[分组 Grouping
group_by: alertname, severity] D & E & F -.-> H end D & E & F --> I(运维团队)
图表说明:
- 图表主旨概括:此图展示了告警流经 Alertmanager 时的关键处理环节:路由、分组和抑制,最终通知到人。
- 逐层/逐元素分解 :
- 入口:Prometheus 生成的告警。
- 路由 :
route作为根节点,根据match条件将告警分发到不同级别的子路由。 - 抑制 :
inhibit_rules作为一个独立的逻辑模块,关联了源告警和目标告警。 - 分组 :
group_by作用于所有被接收的告警,将它们打包。
- 设计原理映射:责任链模式。告警在路由树中传递,直到被某个接收器处理。抑制规则是典型的"断路器"模式在告警域的应用。
- 工程联系与关键结论 :"告警是呼救,不是噪音。" 每次误报都会消耗 On-Call 工程师的精力,降低其对真正告警的敏感度。精心配置分组、抑制和静默,是对团队最宝贵资源------注意力------的保护。
5. 监控与告警的混沌验证
仅凭信任,无法保证告警体系的正确性。我们必须使用混沌工程的方法,主动注入故障,验证从 Prometheus 采集到 Alertmanager 通知的全链路是否畅通且准确。
5.1 验证场景:模拟网络延迟
目标:验证订单服务调用库存服务的延迟增加,导致订单服务 P99 延迟违反 SLO 时,Burn Rate 告警能否按预期触发。
步骤:
-
定义告警规则 (
rules.yml):yamlgroups: - name: order_service_slo_alerts rules: - alert: HighOrderCreationLatencyBurnRate expr: | ( 1 - ( sum(rate(order_create_duration_bucket{le="0.2"}[1h])) / sum(rate(order_create_duration_count[1h])) ) ) / (1 - 0.999) > 14.4 and ( 1 - ( sum(rate(order_create_duration_bucket{le="0.2"}[5m])) / sum(rate(order_create_duration_count[5m])) ) ) / (1 - 0.999) > 14.4 for: 2m # 持续2分钟 labels: severity: p0 annotations: summary: "订单创建 SLO Burn Rate 过高 (>14.4)" description: "订单服务 SLO 面临严重风险。1h Burn Rate 为 {{ $value | humanize }}。" -
注入故障:对库存服务注入 500ms 的网络延迟。
bash# 使用 ChaosBlade blade create network delay --time 500 --interface eth0 --destination-ip <库存服务IP> -
观察与验证:
- Grafana 面板 :观察 Burn Rate 面板,在 2-3 分钟内,
burn_rate_1h和burn_rate_5m两条线应先后突破 14.4 的阈值。Error Budget 消耗曲线斜率变陡。 - Alertmanager 告警 :在
for: 2m持续后,Alertmanager 应产生一条HighOrderCreationLatencyBurnRate告警。检查其severity标签为p0。 - 通知 :等待
group_wait时间后,验证企微群是否收到高亮、合并后的告警通知。确认 P1 级别的延迟告警是否被正确抑制。 - 恢复:停止故障注入后,验证告警是否在预期时间内自动恢复。
- Grafana 面板 :观察 Burn Rate 面板,在 2-3 分钟内,
关键结论 :未经测试的告警,等同于没有告警。 将告警规则的验证作为混沌实验的标准环节,是确保整个稳定性体系闭环的最后一公里。这个过程反复锤炼了团队对故障的反应速度和处理流程。
6. 贯穿案例:电商订单系统大促 SLO 监控体系
现在,我们将所有知识片断串联,展示一个完整的、从零到一的 SLO 监控体系建设过程。
6.1 指标定义与埋点
在 Spring Boot 应用中,我们首先引入依赖:
xml
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<version>1.9.x</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
配置暴露端点 application.yml:
yaml
management:
endpoints:
web:
exposure:
include: "health,info,prometheus"
metrics:
tags:
application: "order-service"
自定义埋点代码:
java
@RestController
@RequestMapping("/orders")
public class OrderController {
private final MeterRegistry meterRegistry;
private final OrderService orderService;
public OrderController(MeterRegistry meterRegistry, OrderService orderService) {
this.meterRegistry = meterRegistry;
this.orderService = orderService;
}
@PostMapping
@Timed(value = "order_create_duration", percentiles = {0.5, 0.9, 0.99}) // 自动添加Histogram
public ResponseEntity<Order> createOrder(@RequestBody OrderRequest request) {
// 业务逻辑
Order order = orderService.create(request);
// 流量计数
Counter.builder("order_create_total")
.tag("status", "success")
.register(meterRegistry)
.increment();
return ResponseEntity.ok(order);
}
}
对于饱和度 SLI,我们通过 Spring Boot Actuator 自带的 Metrics 或自定义 Gauge 采集:
java
@Bean
public MeterBinder tomcatThreads(ServerProperties serverProperties) {
return registry -> Gauge.builder("tomcat.threads.active", () ->
TomcatThreads.getActiveCount())
.register(registry);
}
6.2 配置 Prometheus 采集
prometheus.yml 部分配置:
yaml
scrape_configs:
- job_name: 'order-service'
metrics_path: '/actuator/prometheus'
kubernetes_sd_configs:
- role: pod
relabel_configs:
- source_labels: [__meta_kubernetes_pod_label_app]
regex: 'order-service'
action: keep
6.3 告警规则与 Recording Rules
在前文基础上,我们再增加一个 Error Budget 消耗告警:
yaml
- alert: ErrorBudgetExhausted
expr: |
(
sum(increase(order_create_duration_count[28d] offset 0s)) -
sum(increase(order_create_duration_bucket{le="0.2"}[28d] offset 0s))
) / (
sum(increase(order_create_duration_count[28d] offset 0s)) * 0.001
) > 0.8
for: 5m
labels:
severity: p0
annotations:
summary: "错误预算消耗超过 80%"
6.4 Grafana 面板 JSON Model 示例(核心 PromQL 集合)
我们为 Burn Rate 双窗口面板提供两个查询(Grafana 中可设置多个 query):
- Query A (1h Burn Rate):
promql
1 - (
sum(rate(order_create_duration_bucket{le="0.2"}[1h]))
/
sum(rate(order_create_duration_count[1h]))
) / 0.001
- Query B (5m Burn Rate):
promql
1 - (
sum(rate(order_create_duration_bucket{le="0.2"}[5m]))
/
sum(rate(order_create_duration_count[5m]))
) / 0.001
设置阈值线 14.4,并且设置条件告警表达式(Grafana 10 支持 Alert 功能)。我们将面板类型选为 "Time series",开启 "Alert" 选项卡。
6.5 大促时的故障排查时序
下面用一张时序图展示大促期间库存服务延迟导致订单 SLO 告警的完整交互:
说明:
- 图中展示了从用户请求 → 业务调用 → 指标采集 → 查询评估 → 告警通知 → 人工介入的完整链条。
- Prometheus 的抓取间隔和规则评估间隔决定了告警的时效性,生产环境建议抓取间隔 15-30s,规则评估间隔 30-60s。
- Grafana 面板上的 Burn Rate 计算与 Prometheus 的告警规则是独立的,分别用于可视化和触发通知,两者结合确保了双重保障。
6.6 完整数据流图
电商订单系统 SLO 监控体系的完整数据流图
Micrometer @Timed, Counter, Gauge"] B["库存服务
Micrometer Counter, Timer"] C["支付服务
Micrometer Gauge"] end subgraph "监控引擎" D["Prometheus
指标拉取与存储"] E["PromQL 查询引擎"] F["告警规则评估
Burn Rate > 14.4, Error Budget > 80%"] end subgraph "可视化与告警" G["Grafana SLO 仪表盘
Burn Rate/Error Budget/RED/USE"] H["Alertmanager
路由/分组/抑制"] end subgraph "响应与通知" I["On-Call 工程师"] J["企微/钉钉群"] K["电话通知"] end A & B & C -- "/actuator/prometheus" --> D D -- "存储与计算" --> E E -- "提供数据" --> G E -- "评估条件" --> F F -- "发送告警" --> H H -- "路由分派" --> I & J & K G -- "手动触发或查看" --> I
图表说明:
- 图表主旨概括:此图呈现了一个从应用埋点开始,经由采集、计算、可视化、告警,直至人员响应的端到端闭环流程。
- 逐层/逐元素分解:流程清晰地分为四层:数据采集层负责生产标准格式的指标,监控引擎层负责聚合和判断,可视化与告警层负责呈现和分发,响应与通知层是最终的人机接口。
- 设计原理映射:这是一个事件驱动架构在监控领域的体现。指标的变化是整个系统的"事件",经过多层复杂事件处理(CEP),最终转化为人的行动。
- 工程联系与关键结论 :自动化是手段,不是目的。 整个数据流的最终目的是为了赋能工程师,让他们能更快、更准地做出决策和行动。这套体系将系统内部的不可见状态,转化为可观测、可理解、可行动的外部信号。
7. 与前后系列的衔接
本文作为系列的第 7 篇,是整个"高并发与稳定性工程"知识体系的"眼睛"和"大脑"。
- 监控 SLI 的数据来源 :
- 限流(第 1 篇) :Sentinel 的
pass_qps和block_qps可作为流量 SLI,其限流规则本身也是饱和度的一种体现。 - 熔断降级(第 2 篇) :Resilience4j 的熔断器状态 (
state) 和拒绝调用数 (not_permitted_calls_total) 是极佳的错误 和饱和度 SLI。 - 服务隔离(第 3 篇) :线程池的活跃线程数、队列大小等指标,直接构成了本文中的饱和度 SLI。
- 容量规划(第 4 篇) :全链路压测得出的系统容量极限,为我们的饱和度 SLI 提供了科学的阈值设置依据。
- 限流(第 1 篇) :Sentinel 的
- 混沌验证(第 5 篇):前文讲述如何制造故障,本文讲述如何发现和响应这些故障。二者互为表里,共同构成了完整的"攻防演练"闭环。
- 秒杀架构(第 6 篇):在秒杀这类极限场景下,我们可以针对性地建立专项监控面板和告警规则,例如将 P99 阈值调到更高,但将 QPS 和商品库存预减的成功率作为更核心的 SLO。
至此,我们完成了从'代码防御(限流、熔断、隔离)'、'架构防御(秒杀架构)'、'验证攻击(混沌工程)'到'监控告警'的稳定性全景图拼图。监控体系是前序所有工作的价值放大器,它让防御不再是静态的围墙,而是一个动态感知、实时反应的活体系统。
8. 面试高频专题
以下精选 16 道面试题,覆盖从基础概念到系统设计的全方位考察,严格遵循四段式结构:核心回答、详细解释、多角度追问、加分回答。
8.1 概念与方法论
Q1: 什么是 SLI、SLO、SLA?它们之间是什么关系?
- 核心回答:SLI 是具体的度量指标,SLO 是基于 SLI 设定的内部可靠性目标,SLA 是基于 SLO 的对外商业承诺。
- 详细解释:SLI (Service Level Indicator) 回答"我们测量什么",如 P99 延迟、错误率。SLO (Service Level Objective) 回答"我们有多可靠",是团队对 SLI 设定的量化目标,如"99.9% 的请求延迟 < 200ms"。SLA (Service Level Agreement) 则是与服务消费者签订的契约,通常比 SLO 更宽松,包含赔偿条款。三者呈金字塔关系:SLI 是基础,SLO 是内部指南,SLA 是外部约束。
- 多角度追问 :
- 如果 SLO 未达标,是否意味着 SLA 也一定违约? 不一定,因为 SLA 通常留有缓冲(比如 SLO 99.9%,SLA 99.5%)。但如果 SLO 严重未达标,可能很快耗尽缓冲并触发 SLA。
- Error Budget 究竟是什么? 错误预算 = (1 - SLO) × 总请求数,表示系统允许的"坏事件"数量。它是发布决策的核心:预算充足时可大胆变更,预算耗尽则冻结发布。
- SLO 为什么不能设置为 100%? 追求 100% 的成本是指数级的,且不切实际。留出错误预算可以为创新和失败提供空间,也符合"风险共担"原则。
- 加分回答:Google SRE 原著指出,SLO 应基于"用户痛感"设定,而非单纯技术指标。例如,用户能感知的是"端到端响应时间"而非服务器处理时间。此外,SLO 的测量周期通常与业务周期(如月度)对齐,以便于决策。
Q2: 什么是四类黄金指标?请举例说明如何为订单服务选择 SLI。
- 核心回答:延迟、流量、错误、饱和度。订单服务的 SLI 可选择:创建订单的 P99 延迟、下单 QPS、下单成功率、线程池/连接池饱和度。
- 详细解释:延迟(Latency)关注请求耗时,必须使用分位值;流量(Traffic)反映系统负载,如 QPS;错误(Error)衡量失败比例,需区分显式错误(5xx)和隐式错误(业务错误码);饱和度(Saturation)表示资源消耗逼近极限的程度,是最脆弱的先行指标。选择时需确保指标与用户体验直接相关。
- 多角度追问 :
- 为什么不用平均延迟而用 P99? 平均值掩盖长尾,比如 100 个请求中 99 个 10ms,1 个 10s,平均约 109ms,看起来不错,但已有一个用户严重超时。P99 能暴露长尾问题。
- 流量指标可以用什么采集类型? Counter,通过
rate()计算 QPS。 - 饱和度的常见表现有哪些? CPU 高、线程池队列满、数据库连接池耗尽、消息积压。这些指标是即将崩溃的先兆。
- 加分回答:Brendan Gregg 的 USE 方法(Utilization, Saturation, Errors)是专门针对资源监控的黄金指标扩展,可以与 RED 方法互补。
8.2 Prometheus 指标模型
Q3: Prometheus 的 Histogram 和 Summary 有什么区别?为什么推荐使用 Histogram?
- 核心回答:Histogram 在服务端通过分桶估算分位值,可跨实例聚合;Summary 在客户端直接计算精确分位值,但不可聚合。微服务中我们几乎总是需要聚合,因此推荐 Histogram。
- 详细解释 :Histogram 将观测值落入预定义的桶中,只暴露桶计数和总数,分位数由 PromQL 的
histogram_quantile服务端计算。多个实例的 Histogram 桶可以安全相加,从而计算整体 P99。Summary 则直接在客户端维护滑动窗口并计算分位值,暴露_quantile指标,但这些分位值来自不同实例,无法算术平均。此外,Summary 消耗更多客户端资源。 - 多角度追问 :
- Histogram 的桶分布如何影响精度? 桶分布决定了分位值估算的误差。桶越密,误差越小。应在关注的 SLO 阈值附近加密桶。
- 如果我不需要聚合,可以选择 Summary 吗? 可以,单体应用或已知永远不会水平扩展的服务,Summary 可以给出精确分位值且无需服务端计算。
histogram_quantile是如何工作的? 它使用桶的累计直方图,对目标分位数所在区间进行线性插值。假设 P99 落在 100ms 和 200ms 桶之间,就会根据两个桶的计数比例估算 P99 值。
- 加分回答 :Prometheus 官方文档明确表示 Histogram 是首选,因为其服务端计算允许灵活调整时间窗口和聚合维度,而 Summary 的分位值是固定的。另外,使用
publishPercentileHistogram可以自动生成大量桶,但会增加时间序列基数,生产需权衡。
Q4: 解释 rate 和 increase 的区别,以及常见的误用场景。
- 核心回答 :
rate计算每秒平均增长率,increase计算时间窗口内的总增量。对 Gauge 使用它们无意义;用_sum/_count计算平均值会掩盖长尾;窗口太短导致抖动。 - 详细解释 :
rate(counter[5m])会处理计数器重置,返回每秒速率,适合趋势图。increase(counter[5m])返回该时段增量,适合累计统计。常见误用:①rate(gauge[5m])会得到错误波动;②rate(http_duration_sum[5m]) / rate(http_duration_count[5m])只是平均延迟,无法体现分位值;③rate(...[1m])图形毛刺多,易产生误报。窗口推荐至少 3-5 个 scrape 间隔。 - 多角度追问 :
- 为什么
rate会自动处理计数器重置? 因为它检测到序列值下降时,就认为是进程重启,将差值补偿上去。 increase外推机制可能导致什么陷阱? 如果时间窗口与抓取间隔不对齐,外推可能产生小数增量或轻微不准确,但在大窗口下可忽略。rate和irate有何不同?irate只看窗口内最后两个样本点,反应更灵敏,但图形更尖锐,适合告警表达式;rate更平滑,适合面板。
- 为什么
- 加分回答 :Prometheus 设计者 Björn Rabenstein 在《Monitoring Distributed Systems》中指出,
rate通过外推将结果归一化到每秒,便于不同窗口间对比,但也会在短时间内引入微小失真,这是为了使用便利性的权衡。
8.3 SLO 与 Burn Rate 告警
Q5: 什么是 Burn Rate?为什么使用双窗口(1h+5m)告警?
- 核心回答:Burn Rate 是错误预算消耗速率的倍数。双窗口检测兼顾了快速性与可靠性:短窗口(如 1h)快速感知,长窗口(如 5m)防止短时抖动导致的误报。
- 详细解释:Burn Rate = (当前错误率) / (错误预算率)。若 SLO 为 99.9%,错误预算率 0.1%,当前 1h 错误率为 1.44%,则 Burn Rate = 14.4。双窗口告警要求 1h 和 5m 的 Burn Rate 都超过阈值才触发,因为紧急故障会同时影响两个窗口,而瞬时毛刺可能只让短窗口超标,长窗口起到确认作用。这样可以有效降低误报率。
- 多角度追问 :
- 阈值为 14.4 是怎么得出的? Google SRE 建议:对于月度错误预算,1h 内消耗 2% 的总预算即触发告警。0.1% 的预算率,1h 消耗 2% 对应 Burn Rate 为 14.4。这样能在数小时内提醒,避免预算瞬间耗尽。
- 双窗口的时长可以调整吗? 可以,根据业务容忍度。一般短窗口 5m-1h,长窗口 6h-1d。更长的窗口可检测缓慢消耗。
- Burn Rate 告警和传统阈值告警的根本区别是什么? 传统告警基于绝对阈值(如错误率 >5%),与 SLO 无关,可能漏掉缓慢恶化的 SLO 违反。Burn Rate 直接关联 SLO,更精准。
- 加分回答:在《Site Reliability Engineering》第 6 章中,作者提供了一张 Burn Rate 阈值表,对应不同的告警紧急度。例如 "Critical" 对应 14.4 阈值,"Warning" 对应 1 阈值。该表是生产设计的直接参考。
8.4 Alertmanager 配置
Q6: Alertmanager 的 grouping、inhibition、silence 分别解决什么问题?如何配置?
- 核心回答:Grouping 将同类型告警打包,防止消息轰炸;Inhibition 在高级别告警触发时抑制由此引起的低级别告警;Silence 用于计划内维护静默。
- 详细解释 :
- Grouping :通过
group_by定义分组键,group_wait等待新告警入组,group_interval控制后续更新发送间隔,repeat_interval控制重复提醒。例如数据库宕机导致 100 个 API 错误,分组后只发一条"数据库不可用"。 - Inhibition :配置
inhibit_rules,当 source 告警存在时,target 告警不发送。如 P0 高错误率告警抑制 P1 高延迟告警。 - Silence:通过 Web UI 或 API 创建,可基于标签匹配,在维护窗口内不发送任何告警。
- Grouping :通过
- 多角度追问 :
group_wait设太大会有什么问题? 会延迟第一条告警的送达,可能延误响应。- 如何在 Prometheus 告警规则中设置抑制所需的标签? 在规则的
labels中定义severity: p0等,Alertmanager 根据标签匹配。 - Silence 和 Inhibition 的区别? Silence 是手动创建,用于计划内事件;Inhibition 是自动的,基于规则。
- 加分回答 :高级用法:使用
continue: true让告警匹配多个子路由,实现通知到多个渠道;利用matchers实现更复杂的条件抑制。
8.5 实践与故障排查
Q7: 在大促期间,Error Budget 消耗突然加速,但还没触发告警,你该如何应对?
- 核心回答:应立即查看 Grafana SLO 面板,定位是哪个服务或依赖的延迟/错误导致消耗加速,评估剩余预算能否支撑到大促结束,必要时启动预案(降级、限流、扩容)。
- 详细解释:首先看 RED 面板,若延迟上升但错误率不高,可能是依赖变慢;查看 USE 面板,确认是否有资源瓶颈。如果是某下游服务导致,可启用熔断或限流保护自身。同时计算当前 Burn Rate 预估剩余预算耗尽时间,决定是否暂停发布或降级非核心功能。关键是数据驱动决策,而非凭直觉。
- 多角度追问 :
- 如果找不到根因,你怎么办? 启用混沌实验(如果已有),或查看链路追踪(如 Jaeger),同时通知依赖方协同排查。
- 如何在短时间内恢复 SLO? 可临时放宽 SLO 窗口(需谨慎),但更推荐通过服务降级或自动扩容恢复。
- 如何避免预算消耗加速? 提前进行容量规划与全链路压测,确保资源冗余。
- 加分回答:Google 建议为 Error Budget 消耗率设置多级告警:50% 消耗时发邮件提醒,80% 时发紧急告警并冻结变更。这是一种风险递进管理。
8.6 系统设计题
Q8:(系统设计)某电商系统订单服务需建立 SLO 监控体系。请设计: (1) 定义四个 SLI 及其采集方式; (2) 设定 SLO 并给出 Error Budget 公式; (3) 设计 Burn Rate 多窗口告警规则(含 PromQL); (4) 设计 Alertmanager 路由树(P0/P1/P2); (5) 设计 Grafana SLO 面板布局与核心 PromQL。
-
核心回答:建立"定义 SLI → 埋点采集 → Prometheus 存储 → Grafana 可视化 + Alertmanager 告警"的完整闭环。订单服务选取延迟(P99)、流量(QPS)、错误(失败率)、饱和度(线程池/连接池)作为四类 SLI,设定 SLO 为"99.9% 请求延迟 < 200ms"和"错误率 < 0.01%",基于 Error Budget 决策发布,通过 Burn Rate 双窗口告警实现精准通知,并设计多面板 Grafana 仪表盘。
-
详细解释与系统设计:
1. 总体架构
监控体系的总体架构如下图所示,展示了从订单服务埋点到 Prometheus 采集、Grafana 可视化、Alertmanager 告警路由的完整数据流。
Micrometer] OS2[订单服务 Pod 2
Micrometer] OS3[订单服务 Pod N
Micrometer] end subgraph 依赖服务 INV[库存服务
Micrometer] PAY[支付服务
Micrometer] end subgraph 监控采集层 PROME[Prometheus Server
K8s服务发现
拉取指标] TSDB[(本地TSDB)] end subgraph 可视化层 GRAF[Grafana
SLO仪表盘
Burn Rate/RED/USE面板] end subgraph 告警层 RULES[Prometheus 告警规则
Burn Rate / Error Budget] ALERTM[Alertmanager
分组/抑制/路由] NOTIFY[通知渠道
电话/企微/邮件] end subgraph 运维人员 SRE[On-Call 工程师] end OS1 & OS2 & OS3 -->|/actuator/prometheus| PROME INV & PAY --> PROME PROME --> TSDB PROME --> RULES TSDB --> GRAF GRAF --> SRE RULES --> ALERTM ALERTM --> NOTIFY NOTIFY --> SRE
图表说明(四层):
- 图表主旨概括:展示 SLO 监控体系的全栈架构,从业务服务、采集存储、可视化到告警响应的分层设计。
- 逐层分解 :
- 业务层 :订单服务及依赖服务通过 Micrometer 暴露
/actuator/prometheus端点。 - 采集层:Prometheus Server 通过 K8s 服务发现自动发现 Pod 并 Pull 指标,存储于 TSDB。
- 可视化层:Grafana 从 Prometheus 查询数据,渲染 SLO 仪表盘,供工程师查看。
- 告警层:Prometheus 评估预配置的告警规则,将触发告警发送至 Alertmanager,经分组、抑制后推送到电话/企微/邮件。
- 业务层 :订单服务及依赖服务通过 Micrometer 暴露
- 设计原理映射:分层解耦,每个组件职责单一。Pull 模型保证监控与业务隔离,TSDB 高效存储时序数据,Grafana 与 Alertmanager 分别处理可视化与通知,遵循 Unix 哲学。
- 工程联系与关键结论 :每一层都应进行 Meta-monitoring,确保监控系统自身可靠。实际部署时 Prometheus 和 Alertmanager 至少双副本,Grafana 使用数据库存储仪表盘配置。
2. SLI 定义与埋点采集
(1)延迟 SLI
-
指标名称 :
order_create_duration_seconds -
类型:Histogram
-
桶分布 :
[0.01, 0.05, 0.1, 0.2, 0.5, 1.0, 2.0],重点关注 200ms 附近的精度。 -
Micrometer 代码 :
java@Timed(value = "order.create.duration", percentiles = {0.5, 0.9, 0.99}) @PostMapping("/orders") public Order createOrder(@RequestBody OrderRequest req) { ... }或使用
Timer.builder自定义桶。
(2)流量 SLI
-
指标名称 :
order_create_total -
类型 :Counter(标签
status=success|failure) -
Micrometer 代码 :
javaCounter.builder("order.create.total") .tag("status", "success") .register(meterRegistry) .increment();
(3)错误 SLI
-
直接复用
order_create_total{status="failure"}与总请求的比值。 -
错误率 PromQL :
inisum(rate(order_create_total{status="failure"}[5m])) / sum(rate(order_create_total[5m]))
(4)饱和度 SLI
- Tomcat 线程池 :
tomcat_threads_active,Gauge 类型,取值于ThreadPoolExecutor.getActiveCount()。 - DB 连接池(HikariCP) :
hikaricp_connections_active,Gauge 类型,使用 Micrometer 自带HikariCPMetricsBinder自动暴露。
3. SLO 与 Error Budget 设定
基于历史 28 天数据:订单创建 P99 延迟峰值 150ms,平均 QPS 约 2 万。用户可容忍下单延迟上限为 200ms。
- 延迟 SLO:99.9% 的请求在 200ms 内完成。
- 错误率 SLO:错误率 < 0.01%(即 99.99% 成功率)。
- 计算窗口:以 28 天(约 2,419,200 秒)为滚动窗口。
- Error Budget 公式 :
- 月度总请求数 ≈
avg(QPS) * 28*24*3600。 - 延迟错误预算 :
(1 - 0.999) * total_requests,允许 0.1% 的请求超过 200ms。 - 错误率错误预算 :
(1 - 0.9999) * total_requests,允许 0.01% 的请求失败。
- 月度总请求数 ≈
Error Budget 消耗监控 PromQL(以延迟 SLO 为例):
promql
# 过去28天"坏"事件总数 (延迟 >= 200ms)
bad_events = sum(increase(order_create_duration_seconds_count[28d]))
- sum(increase(order_create_duration_seconds_bucket{le="0.2"}[28d]))
# 总请求数
total_events = sum(increase(order_create_duration_seconds_count[28d]))
# 错误预算消耗比例
budget_consumed_ratio = bad_events / (total_events * (1 - 0.999))
4. Burn Rate 多窗口告警规则设计
设计原理:
- Burn Rate = 当前错误率 / 错误预算率。
- 错误预算率 =
1 - 0.999 = 0.001。 - 依据 Google SRE 推荐,当 1 小时内错误预算消耗 2% 时,Burn Rate =
0.02 * total_budget / (1h * error_budget_rate)≈0.02 / (1h_rate * 0.001),简化后阈值取 14.4。 - 双窗口:1h(短窗口)与 5m(长窗口,实际常用 6h,按题目要求 5m 为长窗口示例),两者同时超阈值触发告警,避免毛刺误报。
Prometheus 告警规则 (rules.yml):
yaml
groups:
- name: order_slo_alerts
interval: 30s
rules:
- alert: HighOrderLatencyBurnRate
expr: |
(
1 - (
sum(rate(order_create_duration_seconds_bucket{le="0.2"}[1h]))
/
sum(rate(order_create_duration_seconds_count[1h]))
)
) / 0.001 > 14.4
and
(
1 - (
sum(rate(order_create_duration_seconds_bucket{le="0.2"}[5m]))
/
sum(rate(order_create_duration_seconds_count[5m]))
)
) / 0.001 > 14.4
for: 2m
labels:
severity: p0
service: order-service
annotations:
summary: "订单服务延迟 SLO Burn Rate 过高"
description: "1h Burn Rate 为 {{ $value | humanize }},错误预算正快速消耗。"
- alert: ErrorBudgetExhausted
expr: |
(
sum(increase(order_create_duration_seconds_count[28d]))
- sum(increase(order_create_duration_seconds_bucket{le="0.2"}[28d]))
)
/
(
sum(increase(order_create_duration_seconds_count[28d])) * 0.001
) > 0.8
for: 5m
labels:
severity: p0
service: order-service
annotations:
summary: "订单服务错误预算消耗超过 80%"
5. Alertmanager 路由树设计
group_by: alertname, severity] ROUTE -->|match severity=p0| P0_R[P0接收器: 电话+企微群] ROUTE -->|match severity=p1| P1_R[P1接收器: 企微群] ROUTE -->|match severity=p2| P2_R[P2接收器: 邮件] INHIBIT{抑制规则
P0抑制同服务P1} P0_R -.-> INHIBIT P1_R -.-> INHIBIT
图表说明 :树状路由根据 severity 标签分流,P0 直接电话 + 群通知,P1 群通知,P2 邮件。抑制规则确保当 P0 触发时,同一服务的 P1 告警被抑制,减少噪音。
Alertmanager 配置片段:
yaml
route:
receiver: 'default'
group_by: ['alertname', 'severity', 'service']
group_wait: 10s
group_interval: 5m
repeat_interval: 4h
routes:
- match:
severity: p0
receiver: p0-receiver
continue: false
- match:
severity: p1
receiver: p1-receiver
continue: false
- match:
severity: p2
receiver: p2-receiver
inhibit_rules:
- source_match:
severity: p0
alertname: HighOrderLatencyBurnRate
target_match:
severity: p1
alertname: HighLatencyWarning
6. Grafana SLO 面板布局与核心 PromQL
面板布局设计为四行:
第一行:Error Budget 消耗仪表盘
-
类型:Gauge
-
核心 PromQL:
less100 - ( ( sum(increase(order_create_duration_seconds_count[28d])) - sum(increase(order_create_duration_seconds_bucket{le="0.2"}[28d])) ) / (sum(increase(order_create_duration_seconds_count[28d])) * 0.001) ) * 100显示剩余错误预算百分比,阈值 50% 变黄,80% 变红。
第二行:Burn Rate 多窗口面板
-
类型:Time series
-
Query A (1h Burn Rate):
less(1 - (sum(rate(order_create_duration_seconds_bucket{le="0.2"}[1h])) / sum(rate(order_create_duration_seconds_count[1h])))) / 0.001 -
Query B (5m Burn Rate):
less(1 - (sum(rate(order_create_duration_seconds_bucket{le="0.2"}[5m])) / sum(rate(order_create_duration_seconds_count[5m])))) / 0.001 -
添加固定阈值线 14.4(红色虚线)。
第三行:订单服务 RED 面板
- Rate:
sum(rate(order_create_total[5m])) - Errors:
sum(rate(order_create_total{status="failure"}[5m])) / sum(rate(order_create_total[5m])) - Duration (P99):
histogram_quantile(0.99, sum(rate(order_create_duration_seconds_bucket[5m])) by (le))
第四行:基础设施 USE 面板
- Tomcat 线程池利用率:
tomcat_threads_active / tomcat_threads_max - HikariCP 连接池利用率:
hikaricp_connections_active / hikaricp_connections_max - CPU/Memory 等通过 Node Exporter 指标补充。
7. 业务流程:从故障注入到告警响应
下面用时序图描述一次完整的故障告警流程,模拟库存服务延迟导致订单服务 SLO 风险。
图表说明:该时序图详细展示了"用户请求 → 延迟写入 → Prometheus 规则评估 → Alertmanager 通知 → 人工响应"的端到端链路,验证了监控体系在真实故障场景下的反应能力。
-
多角度追问:
- 如何设计 Histogram 的桶以达到最优精度? 在 SLO 阈值(200ms)附近采用细密桶,如 100, 150, 180, 200, 220, 250,远离阈值处可放宽以减少基数。
- 若订单服务实例数动态扩缩,Histogram 聚合方式是否需要调整? 不需要,
sum by(le)将所有实例桶累加再计算分位值,天然支持动态伸缩。 - Error Budget 告警是否应与部署流水线集成? 强烈建议。可通过 API 查询剩余预算,当低于 50% 时 CI/CD 自动转为"黄灯"需审批,低于 80% 则冻结部署。
-
加分回答:Google SRE 进一步提出了"Multi-Window, Multi-Burn-Rate"告警框架,不仅包括一对窗口,还可设置多组(如 1h/5m,6h/1d),用于不同紧急级别的告警。在生产中,可将这些告警与 K8s HPA 或事件驱动自动扩容联动,实现从"告警"到"自愈"的闭环。例如,当 Burn Rate 超过 14.4 时,不仅通知 On-Call,同时自动触发扩容预案(需谨慎设计安全边界)。
8.7 指标设计与分位值选择
Q9: 如何为延迟 SLO 设定合理的分位值(如 P99)?P99、P999 和平均值各有什么适用场景?
-
核心回答:P99 是延迟 SLO 的黄金标准,因为它能捕捉长尾问题而不引入过多噪音;平均值掩盖长尾,P999 适用于极高可靠性要求的核心金融链路。
-
详细解释:
- 平均值 :将所有请求延迟求平均,完全抹平长尾。例如 100 个请求中 99 个耗时 10ms,1 个耗时 10s,平均值为 109.9ms,看起来毫无问题,但有一个用户经历了不可接受的 10s 等待。平均值唯一的用途是容量规划中的宏观趋势观察,绝不能用于 SLO。
- P99:99% 的请求延迟低于此值。这意味着只有 1% 的用户会经历超过此值的延迟。对于电商订单创建这类核心业务,P99 能很好地平衡"捕捉长尾"和"容忍偶发抖动"之间的关系。如果 P99 设置为 200ms,意味着每 100 个用户中有 99 个能在 200ms 内完成下单。
- P999:99.9% 的请求延迟低于此值。适用于对延迟极度敏感的场景,如金融交易核心链路、支付网关。P999 能捕捉到 P99 可能遗漏的更极端的长尾,但代价是对系统抖动的敏感度更高,可能导致更频繁的告警。
- 选择原则:首先确定"用户开始感到痛苦"的延迟阈值(通过 UX 研究或 A/B 测试),然后选择分位值。对于大多数互联网服务,P95 或 P99 已足够;对于核心支付或交易,考虑 P999。
-
多角度追问:
- 为什么 SLO 通常不直接说"P99 < 200ms",而是说"99.9% 的请求 < 200ms"? 前者用分位值描述,后者用比例描述,两者等价。但"99.9% 的请求 < 200ms"直接给出了错误预算的计算基础,更容易被非工程人员理解。
- 如果我的服务流量很小(如每天 1000 个请求),P99 还有意义吗? 流量过小时,分位值的统计意义会下降。每天 1000 个请求,P99 就是第 990 个最慢的请求。样本太少时,单次超时就会大幅波动。此时建议使用更长的时间窗口(如 7 天滚动窗口)或降低分位值到 P95。
- Histogram 的桶分布如何影响 P99 计算精度? 如果 SLO 是 200ms,而你最大的桶是 500ms,且中间没有桶,P99 可能被估算为 500ms,与实际偏差很大。应在 SLO 阈值附近加密桶,例如在 100ms、150ms、200ms、250ms、300ms 处都设置桶。
-
加分回答:Google SRE 原著建议,SLO 的选择应遵循"用户痛感"原则------不要为所有服务设定相同的 SLO,区分关键路径和非关键路径。例如,用户浏览商品列表的延迟容忍度远高于下单支付。此外,错误预算的消耗模式决定了告警的灵敏度,P99 未达标未必意味着错误预算耗尽,需要结合 Burn Rate 来综合判断。
8.8 Prometheus 架构与模型
Q10: Prometheus 的 Pull 模型相比 Push 模型有何优缺点?在什么场景下需要 Pushgateway?
-
核心回答:Pull 模型简化了被监控端、利于健康检查、集中控制采集节奏;Push 模型适合短生命周期的 Job 和网络隔离环境。Prometheus 默认 Pull,但在批处理任务和防火墙场景下需要 Pushgateway。
-
详细解释:
- Pull 模型优势 :
- 客户端简单:被监控端只需暴露 HTTP 端点,无需知道 Prometheus 地址。
- 健康检查天然集成 :如果拉取失败,本身就是问题信号(
up指标为 0)。 - 集中控制采集频率:在 Prometheus Server 端统一管理抓取间隔,方便调优和限流。
- 易于调试 :工程师可以直接 curl
/actuator/prometheus查看暴露的指标,不需要依赖中间代理。
- Pull 模型劣势 :
- 服务发现依赖:需要 Prometheus 能发现所有目标,在动态环境中需要 K8s SD 或 Consul 等机制。
- 网络连通性要求:Prometheus 必须能直接访问被监控端,这在跨 VPC 或防火墙场景下可能有困难。
- Push 模型适用场景 :
- 短生命周期 Job:如定时任务、批处理程序,运行几秒就退出,Pull 来不及抓取。此时应将指标 Push 到 Pushgateway,Prometheus 再从 Pushgateway Pull。
- 网络隔离:被监控端在防火墙后,Prometheus 无法主动访问,可以通过 Push 到 Pushgateway 跨越网络边界。
- Pushgateway 使用注意:Pushgateway 是一个"指标缓存",而不是聚合器。多个 Job 实例 Push 同名指标会相互覆盖,因此每个实例应有唯一标识符。此外,Pushgateway 中的指标不会自动过期,需要在 Job 结束时主动清理。
- Pull 模型优势 :
-
多角度追问:
- 为什么 Prometheus 官方不推荐滥用 Pushgateway? 因为它破坏了 Prometheus 的"实例健康检查"能力------Pull 模式下
up指标直接反映目标存活状态;Pushgateway 模式下,目标崩溃不会导致 Pushgateway 中的指标消失。 - 如果必须使用 Pushgateway,如何保证指标的唯一性? 在指标标签中添加
instance或job_id,确保每次 Push 使用的是唯一标识符,而不是覆盖。 - Prometheus Agent 模式是什么? Prometheus 2.32 引入的 Agent 模式,专门用于远程写入场景。它只采集和转发数据,不做本地长期存储和查询,适合边缘集群或资源受限环境。
- 为什么 Prometheus 官方不推荐滥用 Pushgateway? 因为它破坏了 Prometheus 的"实例健康检查"能力------Pull 模式下
-
加分回答:Prometheus 设计者 Björn Rabenstein 在《Monitoring Distributed Systems》中详细阐述了 Pull 模型的设计哲学:它遵循 Unix 的"工具小而精"原则,Prometheus Server 是主动方,被监控端只需被动暴露,符合"简单负责"的系统设计原则。OpenTelemetry 协议的默认模型也是 Pull,进一步验证了这一设计方向的行业认同。
Q11: Recording Rules 应该记录哪些指标?有哪些生产最佳实践?
-
核心回答:Recording Rules 应预聚合高消耗的 PromQL 查询(如 Histogram 的 P99)、频繁使用的跨服务聚合查询、以及需要长期存储的派生指标。核心实践包括命名规范、评估间隔调优、避免过度聚合。
-
详细解释:
- 应该记录的指标类型 :
- Histogram 分位值 :
histogram_quantile需要扫描大量桶数据,每次 Grafana 刷新都实时计算会严重拖慢 Prometheus。预计算job:http_request_duration:p99:5m可以大幅加速面板加载。 - 跨服务聚合查询 :如
sum(rate(http_requests_total[5m])) by (service),多个团队都在 Grafana 中查询时,预计算能避免重复计算。 - 错误预算消耗速率 :
1 - (sum(rate(good_events[28d])) / sum(rate(total_events[28d]))),需要 28 天的大窗口数据,实时计算极慢,必须预聚合。 - SLI 本身 :如果 SLO 定义为"99.9% < 200ms",可以直接 Recording 一个
sli:order_create:latency_compliance:rate5m,供 Burn Rate 告警使用。
- Histogram 分位值 :
- 生产最佳实践 :
- 命名规范 :遵循
level:metric_name:operation模式。例如job:http_requests_total:rate5m(job 级别,5m 速率)、instance:node_cpu_utilization:avg5m(instance 级别,5m 平均)。 - 评估间隔 :
interval通常设置为采集间隔的 2-4 倍。例如采集间隔 30s,interval: 60s是合理选择。太短加重 Prometheus 负担,太长数据滞后。 - 标签裁剪 :Recording Rules 结果中只保留必要的标签。例如
sum by(job)可以去掉instance标签,减少时间序列基数。 - 避免过度聚合:不是所有查询都需要 Recording Rules。只对高频、高消耗查询做预聚合。低频查询直接查询原始数据即可。
- 命名规范 :遵循
- 应该记录的指标类型 :
-
多角度追问:
- Recording Rules 和 Grafana 的内置聚合有什么区别? Recording Rules 在 Prometheus 端预计算并存储为新的时间序列,查询时直接读取。Grafana 聚合是在查询时实时计算。前者用存储换时间,后者用时间换存储。
- Recording Rules 会占用多少额外存储? 每条 Recording Rule 生成一条新的时间序列。如果原始指标有 1000 条序列,Recording 后有 100 条,额外存储约为原始数据的 10%。需要权衡。
- 如何监控 Recording Rules 本身的健康? Prometheus 暴露
prometheus_rule_evaluation_failures_total和prometheus_rule_group_last_duration_seconds指标,可用于监控规则评估是否正常。
-
加分回答 :Prometheus 官方文档推荐 Recording Rules 的另一用途是"为联邦集群提供数据源"------下级 Prometheus 通过 Recording Rules 预聚合关键指标,上级 Prometheus 从
/federate端点拉取这些预聚合数据,而不是全量原始数据。这在多集群监控架构中至关重要,可以显著减少跨集群网络传输和上级 Prometheus 的存储压力。
8.9 Grafana 面板与告警
Q12: 如何为 Grafana 面板设置告警?与 Prometheus 告警规则相比有何优劣?
-
核心回答:Grafana 10 支持在面板上直接定义告警,优势是可视化配置简单、与面板数据源无缝集成;Prometheus 告警规则更灵活、支持复杂表达式和多条件组合。生产环境建议核心 SLO 告警使用 Prometheus 规则,辅助面板告警使用 Grafana Alerting。
-
详细解释:
- Grafana Alerting 优势 :
- 所见即所得:在面板上看到的数据曲线,就是告警评估的数据源。不需要在 YAML 文件中盲写 PromQL。
- 多数据源支持:可以基于 Prometheus、Loki、Elasticsearch、PostgreSQL 等多种数据源创建告警,而 Prometheus 规则只能查询 Prometheus。
- 统一的告警管理:Grafana 10 内置告警状态管理器,可以管理告警的静默、抑制和通知策略,与 Alertmanager 功能有重叠但不完全相同。
- 条件表达式直观 :通过 GUI 设置
IS ABOVE 14.4、WHEN (A) AND (B)等组合条件,降低门槛。
- Grafana Alerting 劣势 :
- 性能依赖 Grafana:Grafana 本身是可视化层,处理告警评估会增加其负载,高频率评估可能影响面板渲染。
- 多租户隔离弱:Grafana 的告警评估和 Prometheus 规则评估的隔离性不同。
- 表达式灵活度受限 :复杂的多窗口 Burn Rate 计算,在 PromQL 中直接写
(A and B) or (C and D)更灵活。
- 推荐策略 :核心 SLO 相关的 P0/P1 告警使用 Prometheus 规则(
rules.yml),因为它是离数据最近的评估层,延迟最低。辅助性告警(如面板上观察到的临时异常趋势)使用 Grafana Alerting,方便快速调整阈值。
- Grafana Alerting 优势 :
-
多角度追问:
- Grafana Alerting 和 Alertmanager 如何协同? Grafana Alerting 支持将告警发送到 Alertmanager(作为 receiver),利用 Alertmanager 的分组、抑制和路由能力。
- Grafana 面板告警中的
Evaluate every和For如何设置?Evaluate every控制评估频率,通常设为 1m;For是持续时长,避免瞬时抖动,通常设为 2-5m。 - 如果 Grafana 宕机,面板告警还会触发吗? 不会。这是 Grafana Alerting 的致命弱点。Prometheus 规则评估独立于 Grafana,不受 Grafana 宕机影响。
-
加分回答:Grafana 9 重新设计了 Alerting 子系统,引入了统一告警规则存储和告警状态管理。Grafana 10 进一步增强了与 Prometheus Alertmanager 的兼容性。对于有"统一可观测性平台"愿景的团队,Grafana Alerting 可以作为唯一告警入口,管理来自不同数据源的所有告警。但 SRE 团队应始终确保最关键的告警在 Prometheus 层有直接规则,形成双重保障。
8.10 Alertmanager 高级配置
Q13: 在 Alertmanager 中,如果只想在夜间(如 22:00-08:00)抑制非 P0 告警,应如何配置?
-
核心回答 :Alertmanager 原生不支持基于时间的抑制规则,需要通过外部工具(如 Alertmanager 的 API + CronJob)动态创建和删除
silence,或者使用 Grafana 的"通知策略"按时间路由。 -
详细解释:
-
方案一:基于 Alertmanager API 的动态 Silence : 编写一个脚本(Python/Go),在夜间开始时(如 22:00)调用 Alertmanager 的
/api/v2/silencesAPI,创建一个匹配severity!=p0的静默规则,设置startsAt和endsAt。在次日 08:00 静默自动过期,非 P0 告警恢复正常。bashcurl -X POST http://alertmanager:9093/api/v2/silences \ -d '{ "matchers": [{"name":"severity","value":"p1","isRegex":false}], "startsAt":"2026-05-20T22:00:00Z", "endsAt":"2026-05-21T08:00:00Z", "createdBy":"cronjob", "comment":"夜间抑制 P1 告警" }' -
方案二:Grafana 通知策略的时间路由 : Grafana 10 的"通知策略"支持基于时间的路由规则。可以设置两条策略:一条匹配
severity=p0全天生效;另一条匹配severity!=p0且在 22:00-08:00 时匹配到一个"空接收器"或静默接收器。这样非 P0 告警在夜间不会被发送。 -
方案三:双 Alertmanager + 路由层: 部署两个 Alertmanager 实例,一个处理全天告警(P0),另一个处理非 P0 告警。在上游(如负载均衡或 Prometheus 规则)通过时间策略将告警路由到不同实例。
-
注意 :抑制(inhibition)和静默(silence)不同。抑制是基于告警内容自动触发的,无法按时间条件。如果一定要用抑制,可以在 P0 告警中增加一个标签
night_suppress=true,夜间由脚本为所有 P0 告警添加此标签,但这非常规做法。
-
-
多角度追问:
- 为什么不直接在 Prometheus 告警规则中加时间条件? Prometheus 告警规则不支持时间判断(如
hour() > 22)。PromQL 本身没有时间条件函数。 - 如果使用 Silence,如何保证脚本的高可用? 使用 K8s CronJob 或外部调度系统(如 Airflow),并增加重试和监控,确保静默规则一定能被创建和删除。
- 如何验证静默规则已生效? 可以在夜间发送测试告警,验证是否被静默。Alertmanager 的
/api/v2/alerts接口可以查看当前活跃的静默规则。
- 为什么不直接在 Prometheus 告警规则中加时间条件? Prometheus 告警规则不支持时间判断(如
-
加分回答:一些企业使用自研的告警网关(如阿里云的告警中心)在 Alertmanager 之上增加一层,支持更复杂的路由策略,包括时间窗口、升级规则、值班表集成等。这是"告警中台"的常见设计模式。此外,SRE 应警惕"夜间静默"带来的风险------夜间是用户少但可能是定时任务和批处理运行的高峰,非 P0 告警也可能预示严重问题。
8.11 监控系统的自身可观测性
Q14: 如何监控监控系统自身(Meta-monitoring)?Prometheus 自身暴露哪些关键指标?
-
核心回答:Meta-monitoring 是监控系统自举的关键实践。Prometheus 自身暴露了 TSDB 存储、抓取状态、规则评估、告警发送等全维度指标,需建立独立的监控实例来监控生产 Prometheus,防止"监控系统宕机却无人知晓"。
-
详细解释:
- 为什么需要 Meta-monitoring : 监控系统是稳定性保障的最后一道防线。如果 Prometheus 或 Alertmanager 宕机,所有依赖它的告警都会静默,形成"盲区"。Meta-monitoring 通过部署一个独立的监控实例(通常叫
prometheus-meta),专门监控生产 Prometheus 的健康状态。 - Prometheus 自身关键指标 :
- 存储健康 :
prometheus_tsdb_storage_blocks_bytes(TSDB 块大小)、prometheus_tsdb_head_samples_appended_total(样本摄入速率)。如果摄入速率突然下降或存储块持续增长,说明写入异常。 - 抓取状态 :
up{job="..."}(目标存活状态)、scrape_duration_seconds(抓取耗时)、scrape_samples_scraped(每次抓取样本数)。如果up=0或抓取耗时突增,说明目标异常。 - 规则评估 :
prometheus_rule_evaluation_failures_total(规则评估失败次数)、prometheus_rule_group_last_duration_seconds(规则组评估耗时)。规则评估超时或失败会导致告警漏报。 - 告警发送 :
prometheus_notifications_sent_total(发送通知数)、prometheus_notifications_errors_total(通知失败数)。通知失败数增加说明 Alertmanager 可能不可达。 - 查询性能 :
prometheus_engine_query_duration_seconds(查询耗时),用于发现 Grafana 慢查询。
- 存储健康 :
- Alertmanager Meta-monitoring :
alertmanager_alerts(当前活跃告警数)alertmanager_notifications_total(通知总数)alertmanager_notifications_failed_total(通知失败总数)alertmanager_silences(当前静默数)
- 为什么需要 Meta-monitoring : 监控系统是稳定性保障的最后一道防线。如果 Prometheus 或 Alertmanager 宕机,所有依赖它的告警都会静默,形成"盲区"。Meta-monitoring 通过部署一个独立的监控实例(通常叫
-
多角度追问:
- Meta-monitoring 的 Prometheus 应该部署在哪里? 建议部署在不同的可用区或集群,确保物理隔离。至少不能与生产 Prometheus 共享同一底层基础设施。
- 如果 Meta-Prometheus 也宕机了怎么办? 这是"无限递归"问题。实践中,通常使用外部监控服务(如 Grafana Cloud、Datadog、Prometheus 的 SaaS 版本)监控自建 Prometheus 的
/metrics端点,彻底打破循环。 - 如何设置 Meta-monitoring 的告警阈值?
up{job="prometheus"} == 0触发 P0 电话告警;prometheus_notifications_errors_total速率 > 0 触发 P1 告警;TSDB 摄入速率异常触发 P2。
-
加分回答:《Site Reliability Engineering》第 10 章专门讨论了"监控分布式系统"时,监控系统自身可靠性的重要性。Google 内部使用"Probers"(黑盒探针)来验证监控系统的功能正确性------定期发送已知的指标脉冲,检查是否能在预期时间内收到告警。这是对监控系统端到端能力的终极验证,比单纯监控指标更可靠。
8.12 大规模故障与告警风暴
Q15: 当系统出现大规模连锁故障,Alertmanager 产生数百条告警时,如何应对告警风暴?
-
核心回答:通过 Alertmanager 的分组(Grouping)、抑制(Inhibition)、以及上层的告警聚合网关,将数百条告警压缩为少数几条"根因告警",并结合值班表、升级机制,确保 On-Call 工程师不被淹没。
-
详细解释:
- 告警风暴的典型场景: 一个核心服务(如数据库)宕机,导致所有依赖它的上游服务同时产生"连接超时"、"依赖服务不可用"、"熔断打开"等数百条告警。如果每条告警都单独发送通知,On-Call 工程师的手机将在 1 分钟内被消息刷屏,无法快速定位根因。
- 第一道防线:Alertmanager 分组 : 通过
group_by: ['alertname', 'severity', 'service'],将同一服务的同一类告警合并。例如 100 个 API 的延迟告警合并为一条"订单服务有 100 个 P1 高延迟告警"的通知。 - 第二道防线:Alertmanager 抑制 : 配置
inhibit_rules,当数据库不可用的 P0 告警触发时,抑制所有"依赖服务连接数据库超时"的 P1 告警。这样只有根因告警会送达,下游症状告警被自动屏蔽。 - 第三道防线:告警聚合网关 : 在 Alertmanager 和通知渠道之间插入一层告警聚合网关(如 OpsGenie、PagerDuty、国内的 PIG)。这些工具支持更智能的聚合策略:
- 基于拓扑的压缩:如果同一 K8s 集群的所有 Node 都触发"高 CPU"告警,只发送一条"集群 CPU 整体偏高"的聚合通知。
- 值班表与升级:非工作时间自动升级到二级值班人员,超过 30 分钟未 Ack 自动电话通知主管。
- 事后复盘:告警风暴暴露的是系统耦合度问题。长期优化应解耦依赖,降低连锁故障的半径。
-
多角度追问:
group_by应该选择哪些标签? 至少包括alertname和severity。还可以按service或team分组。注意不要包含高基数标签(如instance、pod),否则分组失效,因为每个实例的标签值不同。- 如果抑制规则配置错误,抑制了根因告警怎么办? 这是"过度抑制"的经典故障。应确保抑制规则有明确的方向:source 是更底层的告警(如基础设施),target 是上层症状告警。定期通过混沌实验验证抑制逻辑。
- 如何测试告警风暴场景下的系统行为? 在混沌实验中,注入数据库断连故障,观察 Alertmanager 日志和通知输出,验证是否只有预期的根因告警被发送。
-
加分回答 :Google SRE 提出了"告警比例"的概念------
notification_rate / alert_rate应尽可能低。一个健康的告警系统,95% 以上的告警应该是被分组或抑制的,只有 5% 会真正触达 On-Call 工程师。如果这个比例倒挂,说明告警设计有严重问题。Meta-monitoring 中应监控alertmanager_notifications_total / alertmanager_alerts_total,作为告警系统的健康指标。
8.13 故障排查实战
Q16: 描述一个完整的故障排查场景:从 Grafana 发现订单服务 RED 面板 P99 延迟飙升,到最终定位到具体代码行。
-
核心回答:故障排查遵循"RED 面板 → USE 面板 → 依赖调用链(Tracing)→ 日志(Logging)→ 代码"的五步下钻法。Grafana 定位现象,Prometheus 锁定时间窗口,Jaeger/Zipkin 追踪慢调用,Loki/ELK 关联日志,最终定位到代码中的慢查询或锁等待。
-
详细解释:
-
场景设定: 双十一大促深夜,Grafana SLO 仪表盘的 Burn Rate 面板变红。订单服务 RED 面板显示 P99 延迟从 120ms 飙升到 2.5s,但错误率正常,QPS 平稳。
-
Step 1: Grafana RED 面板确认现象(30 秒) : 查看订单服务 RED 面板,确认 Duration(P99)飙升,Rate 无异常,Errors 无异常。初步判定:不是流量洪峰导致,不是代码抛出异常,而是某个依赖或内部处理变慢。
-
Step 2: USE 面板排除资源瓶颈(1 分钟): 查看 K8s Node 和 Pod 的 USE 面板。CPU 60%、内存 50%、网络 I/O 正常、Tomcat 线程池活跃数增加但未满。资源不是瓶颈。
-
Step 3: 依赖调用链追踪(2 分钟) : 在 Grafana 中点击"View in Jaeger"(前提是已关联链路追踪数据源)。筛选时间窗口,查看典型慢请求的 Trace。发现 Trace 中
order-service调用inventory-service的checkStock方法耗时 2.3s(正常 20ms)。定位到瓶颈:库存服务响应慢。 -
Step 4: 日志关联(1 分钟) : 在 Jaeger 的 Span 中提取
traceId,跳转到 Loki 中搜索{service="inventory-service"} | traceId="xxx"。发现库存服务日志中有一条:sqlWARN [inventory-service] Slow query detected: 2.3s SELECT * FROM inventory WHERE sku='SKU-123' FOR UPDATE进一步搜索该 SKU 的请求频率,发现大促期间该热门商品被高并发抢购,
FOR UPDATE行锁导致大量请求排队。 -
Step 5: 定位代码(1 分钟) : 根据日志中的类名和方法名(如
InventoryRepository.findBySku),在代码仓库中定位到:java@Query("SELECT i FROM Inventory i WHERE i.sku = :sku FOR UPDATE") Optional<Inventory> findBySkuWithLock(@Param("sku") String sku);根因:扣减库存时使用了悲观锁,高并发下锁等待时间线性增长。
-
应急措施:运维启动库存服务扩容,或临时切换为乐观锁 + 重试模式(通过配置中心动态切换)。
-
永久修复:将库存扣减改为 Redis 原子预减(详见第 6 篇秒杀架构),数据库仅做异步持久化。
-
-
多角度追问:
- 如果没有链路追踪系统,如何定位依赖耗时? 可以在订单服务中对调用库存服务的 RPC 客户端做 AOP 埋点,记录每次调用的耗时,暴露为 Histogram 指标。通过 Grafana 对比订单服务自身耗时和库存调用耗时。
- 如何在 Grafana 中将 RED 面板和 Loki 日志关联? Grafana 10 支持"Logs Panel"和"Trace Panel"的数据链接。在 RED 面板上配置 Drill-down Link,点击时间曲线可以跳转到该时间段的 Loki 日志查询。
- 如何防止类似问题再次发生? 在库存服务的 SLO 中增加"checkStock 延迟 P99 < 50ms"的指标,配置 Burn Rate 告警。在故障出现时就能定位到库存服务,而不是等订单服务的 SLO 被连带影响。
-
加分回答:这一排查流程体现了"可观测性三大支柱"的协同价值------Metrics 告诉你"有问题",Tracing 告诉你"在哪里",Logging 告诉你"为什么"。Google 的"Golden Signals"方法论强调,监控面板应支持从聚合指标到单个请求的逐层下钻。在 Grafana 中,通过统一的数据源关联(Prometheus + Loki + Tempo/Jaeger),可以在一个界面内完成从现象到根因的完整排查,这也是 Grafana 10 主推的"统一可观测性平台"愿景。
本文完整论述了从 SLO 方法论到 Prometheus 指标采集、Grafana 可视化、Alertmanager 告警响应,再到混沌验证的全链路监控体系搭建,并通过电商订单系统贯穿始终。监控体系的建立标志着稳定性保障从被动响应走向数据驱动的科学管理。