第一篇讲的是监控报警体系的建设。这一篇把视角收窄到一条规则:它怎么从一堆时间序列,变成一个值得打断人的故障信号。
很多低质量报警的根源,是把规则写成了简单的 if else:CPU 大于 80 就报警,错误数大于 10 就报警,延迟大于 500ms 就报警。这个写法看起来直观,但在生产环境里很容易误报。因为真实系统里的信号有流量周期、采样噪声、局部故障、发布扰动、依赖抖动和数据缺失。
所以报警规则设计的目标不是「发现所有异常」,而是把异常压缩成可靠、可行动、低噪声的故障信号。
一、规则的输入输出
一条规则的输入通常是时间序列:某个指标在一段时间窗口内的值。输出不是一个数字,而是一个判断:正常、警告、严重、未知,以及这次判断对应的分组。
| 概念 | 问题 | 例子 |
|---|---|---|
| 指标 | 看什么? | 请求数、错误数、延迟、队列长度、CPU、磁盘 |
| 窗口 | 看多久? | 最近 5 分钟、最近 1 小时、过去 7 天同一时间 |
| 聚合 | 按什么维度看? | 单实例、服务、机房、业务线、全局 |
| 条件 | 什么算异常? | 错误率 > 1%、P99 > 800ms、积压增长持续 15 分钟 |
| 动作 | 异常后谁处理? | 值班电话、IM、工单、升级到服务 owner |
如果一条规则只定义了条件,没有定义窗口、聚合和动作,它通常是不完整的。
二、窗口设计:不要被一个点骗了
时间窗口解决的是抖动问题。单个采样点可能是网络抖动、采集延迟、GC、发布瞬间或某个实例的短暂抖动。报警规则应该尽量对一段时间内的行为做判断。
- 短窗口:发现快,噪声高。适合强故障信号,比如服务完全不可用、流量归零。
- 长窗口:发现慢,稳定性高。适合容量、积压、慢性错误率上升。
- 多窗口:同时看短窗口和长窗口。短窗口防止漏掉快速故障,长窗口防止瞬时误报。
# 伪代码:短窗口和长窗口同时异常才打严重报警
short_error_rate = error_rate(last_5m)
long_error_rate = error_rate(last_30m)
crit = short_error_rate > 5% and long_error_rate > 2%
这个模式背后的直觉是:真正的故障通常不会只在一个采样点出现;如果短窗口尖刺很高但长窗口正常,可能只是局部抖动;如果长窗口异常但短窗口已经恢复,可能适合进入工单而不是电话。
三、阈值类型:固定、动态、相对变化
阈值不是只能写一个固定数字。不同业务形态适合不同阈值。
| 阈值类型 | 适合场景 | 风险 |
|---|---|---|
| 固定阈值 | 容量、水位、明确 SLA,例如磁盘 < 10% | 不适合强周期业务 |
| 业务阈值 | 订单量、支付量、成功率,有业务底线 | 需要产品和业务共同确认 |
| 同比/环比 | 强周期流量,例如每天同一时段对比 | 节假日、活动、发布会导致误判 |
| 动态基线 | 模式稳定但绝对值变化大 | 解释性差,容易让值班人不信任 |
| 错误预算消耗 | 有明确 SLO 的服务 | 需要先建立 SLI/SLO 和预算窗口 |
固定阈值最容易写,也最容易滥用。比如 QPS 小于 100 是否异常,取决于服务类型、时间段、业务活动和上下游状态。相比之下,成功率、错误率、延迟分位数通常更稳定,因为它们更接近用户体验。
四、聚合维度:按实例报警,还是按服务报警
同一个指标,按不同维度聚合,会得到完全不同的告警语义。
| 维度 | 看到的问题 | 适合动作 |
|---|---|---|
| instance | 单机异常、单容器异常 | 摘流、重启、机器排障 |
| cluster / AZ | 机房、集群、可用区异常 | 流量切换、容量调整 |
| service | 服务整体不可用或质量下降 | 服务 owner 介入 |
| business | 业务链路异常 | 跨团队协同、事故响应 |
经验上,直接打断人的报警应该更偏服务或业务维度,而不是单实例维度。单实例异常如果有自动摘流和重建能力,应该先交给自动化系统处理;只有当实例异常比例超过阈值、影响服务容量时,再升级为人工报警。
# 伪代码:不要因为一个实例 5xx 高就电话报警
instance_error = error_rate_by_instance(last_5m)
service_error = error_rate_by_service(last_5m)
bad_instance_ratio = count(instance_error > 5%) / count(instance_error)
warn = bad_instance_ratio > 10%
crit = service_error > 5% and bad_instance_ratio > 20%
五、缺失数据:没有数据也是一种信号
报警系统里最容易被低估的问题是「没有数据」。没有数据可能意味着业务没流量,也可能意味着采集挂了、服务挂了、指标名变了、标签变了、上报链路堵了。
处理缺失数据时,要先区分指标语义:
- 持续流量服务:核心请求数突然为 0,通常是严重信号。
- 低频任务:没有数据可能正常,应该用任务完成时间或调度状态判断。
- 日志类指标:没有错误日志通常是正常,不应该进入 Unknown。
- 采集心跳:没有数据就是采集失败,应单独报警给平台 owner。
Bosun 里 Unknown 是一等状态:当表达式因为数据缺失无法评估时,告警会进入 Unknown。Prometheus 里也有 absent、up 这类处理方式。无论工具是什么,核心原则都是:不要让缺失数据悄悄变成正常,也不要让本来正常的稀疏指标一直 Unknown。
六、依赖抑制:不要让一个故障变成一百条报警
一个数据库故障可能让十几个服务错误率升高;一个机房网络问题可能让成百上千台机器超时。如果每个下游都单独报警,值班群会被淹没,反而看不到根因。
依赖抑制解决的是「故障风暴」问题。常见策略有三类:
- 拓扑依赖:下游依赖已报警时,上游症状降级或合并。
- 故障域合并:同一机房、同一集群、同一依赖引起的报警聚合成一条主告警。
- 变更抑制:发布、扩容、演练期间,部分预期异常进入观察模式,但不能屏蔽用户影响类告警。
抑制不是静默一切。用户体验层的报警应该尽量保留,因为它代表真实影响;被抑制的通常是原因层、资源层和重复症状。
七、常见规则模板
下面这些模板比固定阈值更适合在生产系统里复用。
1. 错误率规则
total = requests(status=*) over 5m
error = requests(status=5xx) over 5m
rate = error / total
warn = total > min_traffic and rate > 1%
crit = total > min_traffic and rate > 5%
注意一定要加最小流量保护。低流量服务里一次失败就可能得到 100% 错误率,但这不一定值得报警。
2. 延迟规则
p95 = latency_percentile(95, last_10m)
p99 = latency_percentile(99, last_10m)
warn = p95 > 300ms and traffic > min_traffic
crit = p99 > 1000ms and traffic > min_traffic
延迟规则要区分成功请求和失败请求。失败请求可能很快返回 500,把它混进整体延迟会掩盖真实慢请求。
3. 流量骤降规则
current = qps(last_5m)
baseline = qps(same_time_yesterday_5m)
crit = baseline > 100 and current < baseline * 0.3
流量骤降适合入口服务、订单链路和任务入口。但它强依赖业务周期,节假日、大促、运营活动都需要额外处理。
4. 容量耗尽规则
free_ratio = disk_free / disk_total
growth_rate = disk_used_growth_per_hour
hours_left = disk_free / growth_rate
warn = free_ratio < 15% or hours_left < 24
crit = free_ratio < 8% or hours_left < 6
容量类报警要尽量报警在「还来得及处理」的时候。磁盘 99% 才报警,技术上准确,工程上已经太晚。
八、上线前检查表
- 这条报警是否对应真实用户影响或明确工程行动?
- 有没有最小流量保护,避免低流量误报?
- 窗口是否足够过滤瞬时抖动?
- 聚合维度是否匹配处理动作?
- 缺失数据时应该 Normal、Unknown 还是 Critical?
- 是否有 owner、通知渠道和升级策略?
- 报警内容里是否包含影响范围、当前值、阈值、最近变更、排障入口?
- 过去 7 天或 30 天回放会触发几次?每次是否值得处理?
结语
报警规则不是 if else,而是把时间、维度、阈值、缺失数据和人类行动组合在一起的工程判断。好的规则让值班人少被打扰,但在真正出事时更早知道、更快定位、更敢行动。
下一篇进入具体工具:用 Bosun 的 RuleConf 和表达式语言,把这些设计落成可运行的告警规则。