报警规则不是 if else:从阈值到故障信号

第一篇讲的是监控报警体系的建设。这一篇把视角收窄到一条规则:它怎么从一堆时间序列,变成一个值得打断人的故障信号。

很多低质量报警的根源,是把规则写成了简单的 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 和表达式语言,把这些设计落成可运行的告警规则。

参考资料