指标异常检测:四种方法实战对比
异常检测,说白了就是帮我们回答一个问题:这个指标到底正不正常?
我在SLA运维的头两年,踩过不少坑。有一次半夜被电话叫醒,说某核心接口的响应时间飙到了500ms。我一看监控,阈值告警确实触发了。但后来复盘才发现——500ms其实只比平时高了20%,只是阈值设得太死。从那以后,我对异常检测的理解就变了:阈值只是底线,真正的异常藏在趋势里。
这一章,我把四种最常用的方法掰开揉碎讲清楚。每种方法都有它的脾气,用对了是利器,用错了是坑。
核心观点:没有万能的方法。阈值告警抓硬伤,同比环比看趋势,3Sigma抓统计异常,移动平均法平滑噪音。组合使用才是王道。
4.1 阈值告警:最基础也最容易翻车
阈值告警,就是给指标画一条红线。超过就告警,没超过就没事。简单粗暴,但有效。
我见过太多团队把阈值设成固定值,比如CPU超过90%就告警。结果呢?白天高峰期天天告警,运维直接麻木了。这叫告警疲劳。
我的经验:阈值要分时段、分场景。比如白天高峰期阈值可以设高一点(95%),凌晨低峰期设低一点(80%)。另外,持续超过阈值才告警,单次突刺可以忽略。
# 伪代码:阈值告警逻辑
def threshold_alert(metric_value, threshold, duration=60):
"""
metric_value: 当前指标值
threshold: 阈值
duration: 持续超过阈值的时间(秒)
"""
if metric_value > threshold:
if sustained_over_threshold(metric_value, threshold, duration):
return True # 触发告警
return False
注意:阈值告警最大的问题是无法感知趋势。指标从50%慢慢爬到89%,阈值告警不会响。等它到90%触发告警时,可能已经晚了。这就是为什么需要其他方法配合。
4.2 同比环比检测:用历史数据说话
同比环比,说白了就是跟过去比。
- 同比:今天 vs 上周同一天、上个月同一天
- 环比:今天 vs 昨天、这个小时 vs 上一个小时
为什么要用同比?因为很多业务有周期性。比如电商平台,周末流量就是比工作日高。如果你用环比,周末比工作日高20%,可能误报。但用同比(这周六 vs 上周六),就正常了。
我记得有一次,某支付接口的调用量突然下降了30%。阈值告警没触发(因为没超过阈值),但同比检测发现了——跟上周同时间比,少了30%。最后定位到是某个渠道的流量被切了。如果没有同比检测,这个问题可能要等到业务方投诉才发现。
# 同比环比检测示例
def yoy_mom_detection(current_value, historical_values, threshold_percent=20):
"""
current_value: 当前值
historical_values: 历史同期值列表
threshold_percent: 变化百分比阈值
"""
avg_historical = sum(historical_values) / len(historical_values)
change_percent = abs(current_value - avg_historical) / avg_historical * 100
if change_percent > threshold_percent:
return True # 异常
return False
关键点:同比环比检测的精度取决于历史数据的质量。如果业务有重大变更(比如上线新功能、做活动),历史数据可能失效。这时候需要手动排除异常时间段。
4.3 3Sigma异常检测:统计学的力量
3Sigma,基于正态分布。简单说:如果指标值偏离均值超过3个标准差,就认为是异常。
为什么是3?因为正态分布下,99.7%的数据都在均值±3σ范围内。超出这个范围的,大概率是异常。
这个方法特别适合响应时间、错误率这类指标。它们通常符合正态分布或近似正态分布。
我曾经用3Sigma抓过一个隐蔽问题:某服务的P99响应时间,平时在200ms左右,偶尔跳到300ms。阈值告警设的是500ms,所以一直没触发。但3Sigma发现,当响应时间超过350ms时,就已经偏离均值3个标准差以上了。后来发现是某个慢SQL导致的。
import numpy as np
def three_sigma_detection(data_point, historical_data, sigma_multiplier=3):
"""
data_point: 当前数据点
historical_data: 历史数据列表
sigma_multiplier: 标准差倍数,默认3
"""
mean = np.mean(historical_data)
std = np.std(historical_data)
upper_bound = mean + sigma_multiplier * std
lower_bound = mean - sigma_multiplier * std
if data_point > upper_bound or data_point < lower_bound:
return True # 异常
return False
注意:3Sigma对数据分布敏感。如果指标本身不符合正态分布(比如流量有周期性波动),直接套3Sigma会误报。我的做法是:先对数据做差分或去趋势处理,再套3Sigma。
4.4 移动平均法:平滑噪音,看清趋势
移动平均法,就是把一段时间内的数据取平均,用这个平均值来判断趋势。
举个例子:你每分钟采集一次CPU使用率。原始数据可能上下跳动,但移动平均后,趋势就清晰了。
常用的移动平均有两种:
| 类型 | 说明 | 适用场景 |
|---|---|---|
| 简单移动平均(SMA) | 等权重平均 | 平稳指标,噪音较小 |
| 指数加权移动平均(EWMA) | 越近的数据权重越大 | 趋势变化快,需要快速响应 |
我个人习惯用EWMA。为什么?因为SMA对历史数据一视同仁,但实际运维中,最近的数据往往更能反映当前状态。EWMA给近期数据更高权重,反应更灵敏。
# 指数加权移动平均(EWMA)
def ewma_detection(current_value, previous_ewma, alpha=0.3, threshold=0.1):
"""
current_value: 当前值
previous_ewma: 上一次的EWMA值
alpha: 平滑因子(0-1),越大对近期数据越敏感
threshold: 偏差阈值
"""
ewma = alpha * current_value + (1 - alpha) * previous_ewma
# 计算偏差
deviation = abs(current_value - ewma) / ewma
if deviation > threshold:
return True, ewma # 异常,返回当前EWMA
return False, ewma
避坑指南:我曾经把alpha设成0.8,结果EWMA几乎跟着原始数据走,失去了平滑效果。后来我总结了一个经验:alpha值越小,平滑效果越好,但反应越慢。一般取0.1~0.3比较合适。如果指标噪音大,alpha取小一点;如果趋势变化快,alpha取大一点。
4.5 四种方法怎么选?一张表说清楚
| 方法 | 优点 | 缺点 | 推荐指标 |
|---|---|---|---|
| 阈值告警 | 简单、直观、响应快 | 无法感知趋势、容易误报 | CPU、内存、磁盘 |
| 同比环比 | 消除周期性影响 | 依赖历史数据质量 | 日活、QPS、交易量 |
| 3Sigma | 统计意义强、自适应 | 对数据分布敏感 | 响应时间、错误率 |
| 移动平均法 | 平滑噪音、看清趋势 | 有滞后性 | 流量趋势、缓慢爬升 |
我的建议:别只用一种方法。我现在的做法是:阈值告警做第一道防线,3Sigma做第二道,同比环比做业务维度的校验,移动平均法做趋势预判。四层叠加,误报率能降到5%以下。
嗯,这一章的内容就到这。四种方法,每种都有它的脾气。你可以在自己的监控系统里先试一两种,慢慢找到最适合你业务的组合。记住,没有银弹,只有不断调优。