分布式系统监控是保障服务稳定性、可用性和故障响应效率的关键能力。某些海外大型互联网公司的 SRE 团队在构建可靠的监控与告警系统方面积累了一些基本原则和最佳实践。本章将介绍分布式系统监控的核心概念、告警设计原则、黑盒监控与白盒监控的区别,以及延迟、流量、错误和饱和度这四个黄金信号,并说明哪些问题应当通过寻呼告警通知值班人员,哪些问题则更适合通过仪表盘、工单或后续分析处理。

分布式系统监控的核心定义
围绕监控这一主题,目前尚不存在一套完全统一的通用术语。即使在同一家大型技术组织内部,下列术语的使用也可能存在差异。这里采用的是较为常见的解释。
监控
监控是指收集、处理、汇总并展示系统的实时定量数据,例如查询数量与类型、错误数量与类型、处理耗时,以及服务器生命周期状态等。
白盒监控
白盒监控是指基于系统内部暴露出来的指标进行监控。这些指标可以来自日志、运行时分析接口,或输出内部统计信息的 HTTP 处理程序等。
黑盒监控
黑盒监控是指从系统外部测试用户可见的行为。
仪表盘
仪表盘通常是一个基于 Web 的应用程序,用于概览展示服务的核心指标。仪表盘可以包含过滤器、选择器等功能,但其默认目标是向用户展示最重要的指标。仪表盘也可以展示团队相关信息,例如工单队列长度、高优先级缺陷列表、某个职责范围内的当前值班工程师,或最近一次发布记录。
告警
告警是设计给人阅读的通知,并会被推送到某个系统中,例如缺陷或工单队列、电子邮件组,或寻呼系统。按照通知方式不同,告警可分为工单告警、邮件告警和寻呼告警。
根本原因
根本原因是软件系统或人为流程中的缺陷。如果修复了它,就能增强我们对“同类事件不会以相同方式再次发生”的信心。一个具体事件可能有多个根本原因。例如,某次事故可能同时源于流程自动化不足、软件在处理错误输入时崩溃,以及用于生成配置的脚本缺少测试。这些因素中的每一个都可能单独构成根本原因,并且都应当被修复。
节点和机器
这两个词可以互换使用,指代物理服务器、虚拟机或容器中正在运行的单个内核实例。一台机器上可能运行多个值得监控的服务。这些服务可能是:
- 彼此相关的服务,例如缓存服务器和 Web 服务器。
- 共享同一硬件但彼此无关的服务,例如代码仓库服务,以及常见配置管理系统的主服务器。
发布
发布是指对服务正在运行的软件或配置所做的任何变更。
为什么分布式系统需要监控?
监控分布式系统有很多原因,包括:
分析长期趋势
我的数据库有多大?增长速度如何?日活跃用户数的增长速度如何?
比较不同时间段或实验组
与某数据库版本相比,使用另一种存储方案后查询速度是否更快?增加一个节点后,缓存命中率能提高多少?我的网站是否比上周更慢了?
告警
系统已经出现故障,需要立刻有人修复;或者系统可能很快发生故障,需要有人尽快检查。
构建仪表盘
仪表盘应当能够回答关于服务的基本问题,通常也应包含某种形式的“四个黄金信号”,即后文将讨论的延迟、流量、错误和饱和度。
进行临时的回顾性分析,即调试
我们的网络延迟突然飙升;与此同时还发生了什么其他事情?
系统监控也有助于为业务分析提供原始数据,并辅助安全漏洞分析。由于本章重点关注 SRE 具备特殊专业能力的工程领域,因此这里不讨论监控在这些方面的应用。
监控和告警机制使系统能够在发生故障时发出警报,或者在故障即将发生前给出预警。当系统无法自动修复时,我们就需要人工介入:调查告警、判断是否存在实际问题、采取措施缓解问题,并最终找出问题的根本原因。除非你正在对系统中的某个非常具体的组件进行安全审计,否则绝不应仅仅因为“感觉有点不对劲”就触发告警。
呼叫值班人员会消耗大量人力成本。如果员工正在工作,告警会打断他们的工作流;如果员工已经下班回家,告警会打断他们的私人时间,甚至影响睡眠。当告警过于频繁时,员工会开始迟疑、草草浏览,甚至忽略收到的告警;有时,真正重要的告警还会被大量噪声掩盖。由于噪声会妨碍快速诊断和修复,故障可能持续更长时间。有效的告警系统应当具备良好的信号强度,并将噪声降到极低。
在实际落地中,监控不应孤立存在,而应与研发流程、发布流程和知识沉淀形成闭环。例如,借助 PingCode 这类智能化研发管理工具,团队可以将目标、客户反馈、需求、开发、测试、发布和 Wiki 知识积累串联起来,让监控中发现的问题更顺畅地回流到研发管理流程中,形成持续改进机制。
如何设定合理的监控预期?
监控一个复杂应用程序本身就是一项重要的工程任务。即使已经部署了相当完善的数据采集、展示和告警基础设施,在某些海外大型技术团队中,一个由 10 到 12 名成员组成的 SRE 团队通常也会有一到两名成员主要负责构建和维护其服务的监控系统。随着通用监控基础设施的普及和集中化,这一人数有所下降,但每个 SRE 团队通常至少仍会有一名“监控负责人”。也就是说,虽然查看流量图仪表盘之类的工具可能很有趣,但 SRE 团队会谨慎避免任何需要有人“盯着屏幕找问题”的做法。
总体而言,某些大型技术团队的趋势是采用更简单、更快速的监控系统,并配备更完善的事后分析工具。他们会避免使用那些试图学习阈值或自动推断因果关系的“神奇”系统。一个例外是用于检测终端用户请求速率异常变化的规则:虽然这些规则仍会尽可能保持简单,但它们能够非常快速地发现一些简单、具体且严重的异常情况。监控数据的其他用途,例如容量规划和流量预测,可以容忍更高的脆弱性,因此也可以接受更高的复杂度。在很长的时间跨度内,例如数月或数年,以较低采样率,例如数小时或数天,进行观察性实验,通常也能容忍更高的脆弱性,因为偶尔缺失样本并不会掩盖长期趋势。
某些 SRE 团队在处理复杂依赖关系层级方面收效有限。他们很少使用类似这样的规则:“如果我知道数据库很慢,就发出数据库慢的告警;否则,发出整个网站变慢的告警。”依赖关系规则通常只适用于系统中非常稳定的部分,例如用于将用户流量从某个数据中心排空的系统。例如,“如果数据中心流量已经排空,就不要发出该数据中心的延迟告警”就是一条常见的数据中心告警规则。很少有团队会维护复杂的依赖关系层级,因为基础设施通常一直在持续重构。
本章描述的一些理念仍然带有理想化色彩。尤其是在持续变化的系统中,我们始终有提升空间,使团队能够更快地从症状定位到根本原因。因此,尽管本章提出了若干监控系统目标以及实现这些目标的方法,但至关重要的是,监控系统,特别是从生产问题出现、通过寻呼告警通知值班人员、进行基础故障排查,到深入调试这一关键路径,必须保持简单清晰,使团队中的每个人都能理解。
同样,为了降低噪声、提高信号强度,监控系统中负责向寻呼系统发送信号的组件必须非常简单,并且稳定可靠。通知人工介入的规则应当易于理解,并且能够清楚表明故障类型。
监控中的症状与原因
你的监控系统应当回答两个问题:哪里出了问题?为什么会出问题?
“哪里出了问题”指的是症状;“为什么会出问题”指的是原因,可能是中间原因,也可能是根本原因。表 6-1 列出了一些假设的症状及其对应原因。
表 6-1:症状与原因示例
| 症状 | 原因 |
|---|---|
| 系统返回 HTTP 500 或 404 错误。 | 数据库服务器拒绝连接。 |
| 系统响应很慢。 | CPU 过载,可能是低效排序算法导致的;也可能是以太网线被机架压住,导致部分数据包丢失。 |
| 南极洲的用户收不到猫咪动态 GIF 图。 | 你的内容分发网络不喜欢科学家和猫,因此把部分客户 IP 地址加入了黑名单。 |
| 私密内容对所有人可见。 | 一次新的软件发布破坏了访问控制列表,并错误地允许了所有请求。 |
在编写优秀的监控报告时,区分“是什么”和“为什么”至关重要,因为这决定了报告能否最大限度提高信号强度,并最大限度降低噪声。
黑盒监控与白盒监控
我们大量使用白盒监控,同时适度但关键地使用黑盒监控。区分黑盒监控和白盒监控最简单的方法是:黑盒监控面向症状,反映的是已经发生的问题,而不是预测中的问题。换句话说,它告诉我们:“系统现在没有正常工作。”白盒监控则依赖于检查系统内部状态的能力,例如通过日志或 HTTP 端点进行插桩。因此,白盒监控能够检测到即将发生的问题,也能够发现那些被重试机制掩盖的故障等。
需要注意的是,在多层系统中,一个团队看到的症状,可能是另一个团队看到的原因。例如,假设数据库性能缓慢。对于检测到这一问题的数据库 SRE 来说,缓慢的数据库读取就是症状;但对于观察到网站变慢的前端 SRE 来说,同样的缓慢数据库读取却是原因。因此,白盒监控有时侧重症状,有时侧重原因,这取决于白盒监控所提供的信息粒度。
在收集遥测数据用于调试时,白盒监控至关重要。如果 Web 服务器在处理数据库密集型请求时变慢,你需要同时了解 Web 服务器感知到的数据库速度,以及数据库自身感知到的速度。否则,你就无法区分问题究竟是数据库服务器本身变慢,还是 Web 服务器与数据库之间的网络出现了问题。
对于寻呼告警而言,黑盒监控的关键优势在于,它能迫使相关团队只在问题已经发生并导致真实症状出现时,才呼叫人工处理。另一方面,对于尚未发生但迫在眉睫的问题,黑盒监控几乎无能为力。
四个黄金信号:延迟、流量、错误和饱和度
监控的四个黄金信号是延迟、流量、错误和饱和度。如果只能衡量面向用户系统的四个指标,就应重点关注这四个。
延迟
延迟是处理请求所需的时间。区分成功请求的延迟和失败请求的延迟至关重要。例如,由于与数据库或其他关键后端连接中断而触发的 HTTP 500 错误,可能很快就能返回;然而,因为 HTTP 500 表示请求失败,如果把 500 错误计入整体延迟,就可能得到误导性的计算结果。另一方面,缓慢的错误处理甚至比快速的错误处理更糟糕。因此,跟踪错误请求的延迟,比简单地把错误请求过滤掉更重要。
流量
流量用于衡量系统承受的需求量,通常以系统特定的高层指标表示。对于 Web 服务,这一指标通常是每秒 HTTP 请求数,并且可能按照请求类型细分,例如静态内容请求与动态内容请求。对于音频流媒体系统,流量指标可能更关注网络 I/O 速率或并发会话数。对于键值存储系统,流量指标可能是每秒事务数和检索次数。
错误
错误指请求失败的速率。失败可以分为显式失败,例如 HTTP 500 错误;隐式失败,例如返回 HTTP 200 成功响应,但内容错误;以及策略性失败,例如“如果承诺一秒内响应,则任何超过一秒的请求都视为错误”。当协议响应码不足以表达所有失败情况时,可能需要使用辅助的内部协议来跟踪部分失败模式。监控这些失败的方式可能截然不同:在负载均衡器上捕获 HTTP 500 错误,可以很好地覆盖所有完全失败的请求;而只有端到端系统测试才能检测到是否返回了错误内容。
饱和度
饱和度衡量的是服务“有多满”,也就是系统资源的利用程度。它重点关注最受限制的资源。例如,在内存受限的系统中,应展示内存使用情况;在 I/O 受限的系统中,应展示 I/O 使用情况。需要注意的是,许多系统在达到 100% 利用率之前性能就已经开始下降,因此设定合理的利用率目标非常重要。
在复杂系统中,可以用更高层次的负载测量来补充饱和度指标:你的服务能否妥善处理两倍流量?只能处理额外 10% 的流量?甚至连当前流量都难以承受?对于非常简单的服务,如果没有任何参数会改变请求复杂度,例如“给我一个随机数”或“我需要一个全局唯一且单调递增的整数”,并且配置很少变化,那么基于负载测试得到的静态值可能就足够了。然而,正如上一段所述,大多数服务需要使用间接信号,例如 CPU 利用率或网络带宽,而这些信号都有已知上限。延迟升高通常是饱和的先行指标。在较小的时间窗口内,例如一分钟,测量第 99 百分位响应时间,可以非常早地发出饱和信号。
最后,饱和度还与预测即将到来的资源耗尽有关,例如:“你的数据库看起来将在 4 小时内写满硬盘。”
如果你测量了这四个黄金信号,并在其中某个信号出现问题时,或者在饱和度接近问题阈值时,通知人工处理,那么你的服务至少已经具备了相当不错的监控基础。
关注长尾延迟:插桩与性能
从零开始构建监控系统时,人们很容易倾向于围绕某些指标的平均值来设计系统,例如平均延迟、节点平均 CPU 使用率,或数据库平均填充率。后两种情况的风险显而易见:CPU 和数据库的利用率很容易出现严重不均衡。延迟也是如此。假设一个 Web 服务的平均延迟为 100 毫秒,每秒处理 1000 个请求,那么其中 1% 的请求完全可能耗时 5 秒。[23] 如果用户依赖多个这样的 Web 服务来渲染页面,那么某个后端服务的第 99 百分位响应时间,很容易变成前端服务响应时间的中位数。
区分“请求整体平均较慢”和“请求长尾非常慢”的最简单方法,是按延迟区间收集请求数量,而不是收集每个请求的实际延迟值。这样的数据也适合绘制直方图。例如:我处理了多少个延迟在 0 到 10 毫秒之间的请求?多少个在 10 到 30 毫秒之间?多少个在 30 到 100 毫秒之间?多少个在 100 到 300 毫秒之间?以此类推。将直方图边界设计为近似指数分布,例如每档约为上一档的 3 倍,通常是一种便于观察请求分布的方式。
监控数据如何选择合适的测量分辨率?
系统的不同方面应采用不同粒度进行测量。例如:
- 只观察一分钟内的 CPU 负载,可能完全发现不了那些持续时间较短、但会导致高尾延迟的尖峰。
- 另一方面,对于一年累计停机时间不超过 9 小时,也就是年可用性为 99.9% 的 Web 服务来说,每分钟探测 HTTP 200 成功状态超过一两次,可能过于频繁。
- 同样,对于目标可用性为 99.9% 的服务来说,每 1 到 2 分钟检查一次硬盘空间是否已满,可能并无必要。
在设计测量粒度时要谨慎。虽然每秒收集一次 CPU 负载数据可能会产生有趣的结果,但如此高频的测量在采集、存储和分析方面都可能非常昂贵。如果你的监控目标需要高分辨率,但对延迟要求不高,可以通过在服务器上执行内部采样,再由外部系统定期收集并聚合某段时间内或跨多台服务器的分布情况,从而降低成本。你可以这样做:
- 每秒记录一次当前 CPU 利用率。
- 使用 5% 粒度的桶,每秒递增对应的 CPU 利用率桶。
- 每分钟汇总这些值。
这种策略使你能够观察到短暂的 CPU 热点,而不会因为数据采集和保留而产生极高成本。
监控系统设计:尽可能简单,但不要过度简化
如果把所有这些需求叠加在一起,监控系统可能会变得非常复杂。你的系统最终可能包含:
- 针对不同延迟阈值、不同百分位数,以及各种不同指标的告警。
- 用于检测和揭示可能原因的额外代码。
- 面向每一种可能原因的相关仪表盘。
潜在的复杂性来源是无穷无尽的。与所有软件系统一样,监控系统也可能变得过于复杂,从而变得脆弱、难以变更,并带来沉重的维护负担。
因此,设计监控系统时应当注重简洁性。在选择监控内容时,请牢记以下准则:
- 最常用于捕捉真实事件的规则,应尽可能简单、可预测且可靠。
- 很少使用的数据采集、聚合和告警配置应被移除。例如,某些 SRE 团队每季度使用不到一次的配置就应考虑删除。
- 已经被采集,但既没有出现在任何预设仪表盘中,也没有被任何告警使用的信号,应予以删除。
根据某些大型技术团队的经验,基础指标采集与聚合,再加上告警和仪表盘,作为一个相对独立的系统运行效果良好。实际上,这类监控系统可能被拆分为多个二进制程序,但通常团队成员会理解这些程序的整体工作方式。人们很容易想把监控与其他复杂的系统检查方法结合起来,例如详细的系统性能分析、单进程调试、异常或崩溃详情追踪、负载测试、日志采集与分析,或流量检查。虽然这些方法大多与基础监控有相通之处,但把太多方法混杂在一起,会让系统变得过于复杂和脆弱。正如软件工程中的许多其他方面一样,更好的策略是维护彼此独立的系统,并通过清晰、简洁、松耦合的集成点连接它们。例如,使用 Web API 以一种能够长期保持稳定的格式提取汇总数据。
如何建立有效的监控和告警规则?
本章讨论的原则可以整合为一套监控与告警理念。这套理念在某些海外 SRE 团队中得到了广泛认可和遵循。虽然它略带理想化色彩,但无论你的组织规模大小、服务或系统复杂程度如何,它都是编写或审核新告警时很好的起点,也能帮助组织提出正确的问题。
在创建监控和告警规则时,询问以下问题可以帮助你避免误报和寻呼疲劳:[24]
- 这条规则是否检测到了其他方式无法检测到的、紧急的、可操作的,并且用户当前已经看到或即将看到的状况?[25]
- 我是否知道这个告警是良性的,因此可以忽略?我在何时、为何可以忽略这个告警?又该如何避免这种情况?
- 这个告警是否确凿地表明用户受到了负面影响?是否存在一些用户未受影响的可检测情况,例如流量已经排空或正在进行测试部署,而这些情况应被过滤掉?
- 我能否针对这个告警采取行动?该行动是否紧急,还是可以等到明天早上?该行动能否安全地自动化?该行动是长期解决方案,还是仅仅是短期权宜之计?
- 是否还有其他人已经因为同一问题收到通知,因此至少有一次寻呼告警是不必要的?
这些问题反映了关于工单与寻呼告警的基本理念:
- 每次寻呼器响起,我都应该能够立即采取紧急行动。但我一天只能承受几次这样的紧急响应,之后就会感到疲惫。
- 每一次寻呼告警都应该是可操作的。
- 每一次寻呼告警响应都应该体现人的判断力。如果某个告警只需要机械式回复,那么它就不应该存在。
- 每一次寻呼告警都应该围绕一个新的问题,或一个此前未见过的事件。
这种观点弱化了某些区别:如果一次寻呼告警满足前四点,那么它究竟是由白盒监控还是黑盒监控触发的,就不那么重要了。与此同时,这种观点也强化了另一些区别:与其费力寻找原因,不如把更多精力放在发现症状上;至于原因,只应关注那些非常明确、非常迫在眉睫的原因。
长期监控与告警治理
在现代生产系统中,监控系统需要跟踪一个不断演进的系统,而这个系统的软件架构、负载特征和性能目标都在持续变化。一个当前极其罕见、且难以自动化处理的告警,未来可能会变得频繁,甚至可能需要编写临时脚本来处理。到了这一步,就应当有人找出并消除问题的根本原因;如果做不到,那么告警响应就应该被完全自动化。
制定监控决策时,必须着眼于长期目标。今天发生的每一次寻呼告警,都会分散人员精力,影响他们未来改进系统的效率。因此,为了提升系统的长期表现,有时需要牺牲一部分短期可用性或性能。下面的两个案例可以说明这种权衡。
某分布式存储服务 SRE:过度告警的故事
某些海外大型互联网公司的内部基础设施通常会根据服务级别目标,即 SLO,来提供服务并衡量性能。许多年前,某分布式存储服务的 SLO 是基于模拟的、行为良好的客户端的平均性能制定的。由于该服务及其底层存储栈存在问题,平均性能受到长尾性能影响:性能最差的 5% 请求通常比其他请求慢得多。
当服务级别目标接近被违反时,系统会触发邮件告警;当 SLO 被违反时,系统会触发寻呼告警。这两类告警都大量触发,耗费了大量工程时间。团队花了大量时间对告警进行分类,只为找出真正需要处理的少数告警;与此同时,我们又常常忽略那些真正影响用户的问题,因为真正影响用户的问题数量相对较少。许多寻呼告警并不紧急,因为它们源于基础设施中一些众所周知的问题;这些告警要么只需要例行式回应,要么根本没有得到回应。
为了解决这一问题,团队采取了三管齐下的策略:在努力改善该分布式存储服务性能的同时,我们暂时降低了服务级别目标,将请求延迟目标设定在第 75 百分位。此外,我们还禁用了邮件告警,因为告警数量过多,逐一排查已经不现实。
这一策略为团队争取了喘息空间,使我们能够真正着手解决该分布式存储服务及底层存储栈中的长期问题,而不是疲于应对各种突发告警。值班工程师终于可以腾出时间完成实际工作,而不再不断被寻呼告警打断。最终,暂时降低告警频率使我们能够更快迈向更好的服务。
某邮件服务:可预测、可脚本化的人工响应
某邮件服务的早期版本基于一个改造版分布式进程管理系统构建。该系统最初是为批量处理搜索索引而创建的,后来被改造为适用于长时间运行的进程,并被应用于该邮件服务。但调度器中一些相对晦涩的代码存在难以修复的漏洞。
当时,该邮件服务的监控机制是:当调度系统取消调度某个单独任务时,就触发告警。这一设置并不理想,因为即使在当时,该邮件服务也已经拥有成千上万个任务,而每个任务影响的用户量都不到总用户数的 1%。团队非常重视为用户提供良好的使用体验,但这样的告警机制难以维护。
为了解决这个问题,SRE 团队开发了一个工具,可以以恰到好处的方式“调整”调度器,从而最大限度减少对用户的影响。团队曾多次讨论,是否应在找到更好的长期解决方案之前,把从检测问题到调整调度器的整个流程自动化。但有人担心,这种权宜之计会延误真正的修复。
这种紧张关系在团队内部很常见,通常反映出团队成员对自身自律能力存在潜在不信任:一些团队成员希望先采取权宜之计,为彻底修复争取时间;另一些成员则担心权宜之计会被遗忘,或者真正的修复会被无限期搁置。这种担忧并非没有道理,因为相比真正修复问题,通过补丁掩盖问题更容易,也更容易积累难以维护的技术债务。管理者和技术负责人在推动真正的长期修复方面发挥着关键作用。即使最初的“痛点”已经减弱,他们也应该支持并优先推进那些可能耗时较长的长期修复。
如果某类寻呼告警的响应总是千篇一律、像算法一样固定,那就应该引起警惕。团队不愿将这类寻呼告警自动化,意味着他们对清理技术债务缺乏信心。这是一个需要上报的重要问题。
分布式系统监控的长远规划
上面两个例子都体现了一个共同主题:短期可用性与长期可用性之间的矛盾。通常,强力投入可以帮助一个摇摇欲坠的系统维持高可用性,但这条路往往不可持续,容易导致人员精疲力竭,并过度依赖少数“英雄式”的团队成员。为了系统的长期稳定性,采取可控的短期可用性下降虽然痛苦,但却具有战略意义。
重要的是,不要把每一次寻呼告警都视为孤立事件,而应考虑整体寻呼量是否能够支持系统健康、合适的可用性水平、健康而高效的团队,以及长期良好的发展前景。某些 SRE 团队会在季度报告中与管理层一起审查寻呼频率统计数据,通常以每个值班班次的事件数来表示;一个事件可能包含几个相关的寻呼告警。这样可以确保决策者及时了解寻呼负载和团队整体健康状况。如果团队更侧重跨部门任务推进和日常协作,也可以通过 Worktile 这类通用项目协作系统承接告警后的任务分派、文档沉淀、日历排期和审批流程,避免问题只停留在临时沟通中。
结论:构建健康的分布式系统监控与告警流程
健康的监控和告警流程应当简单且易于理解。它主要关注用于触发寻呼告警的症状,并把面向原因的启发式方法作为调试问题的辅助手段。监控层级越高,症状通常越容易被监控;但对于数据库等子系统的饱和度和性能,通常必须直接在子系统自身上进行监控。
邮件告警的价值非常有限,并且很容易被大量噪声淹没。相反,你应该使用仪表盘来监控所有正在发生的次要问题,获取那些通常会出现在邮件告警中的信息。仪表盘还可以与日志结合使用,以便分析历史相关性。
从长远来看,要成功维持值班轮转和产品运行,就必须选择针对症状或即将发生的真实问题发出告警,将目标调整到实际可实现的水平,并确保监控能够支持快速诊断。
文章包含AI辅助创作,作者:liu,如若转载,请注明出处:https://docs.pingcode.com/baike/5244191