指标异常检测:四种方法实战对比

异常检测,说白了就是帮我们回答一个问题:这个指标到底正不正常?

我在SLA运维的头两年,踩过不少坑。有一次半夜被电话叫醒,说某核心接口的响应时间飙到了500ms。我一看监控,阈值告警确实触发了。但后来复盘才发现——500ms其实只比平时高了20%,只是阈值设得太死。从那以后,我对异常检测的理解就变了:阈值只是底线,真正的异常藏在趋势里。

这一章,我把四种最常用的方法掰开揉碎讲清楚。每种方法都有它的脾气,用对了是利器,用错了是坑。

核心观点:没有万能的方法。阈值告警抓硬伤,同比环比看趋势,3Sigma抓统计异常,移动平均法平滑噪音。组合使用才是王道。

四种异常检测方法对比 阈值告警 固定上下界 同比环比 时间维度对比 3Sigma 统计分布检测 移动平均法 平滑趋势分析 适用场景 CPU > 90% 内存 > 85% 磁盘满 适用场景 日活/周活对比 节假日效应 业务周期波动 适用场景 响应时间异常 错误率突增 流量突刺 适用场景 缓慢爬升趋势 噪音过滤 长期趋势判断 组合策略建议 阈值告警(第一道防线)+ 同比环比(业务维度)+ 3Sigma(统计维度)+ 移动平均(趋势维度) 四层叠加,误报率降低 70% 以上

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%以下。

嗯,这一章的内容就到这。四种方法,每种都有它的脾气。你可以在自己的监控系统里先试一两种,慢慢找到最适合你业务的组合。记住,没有银弹,只有不断调优。