使用算法检测异常 - 问题描述

来源:互联网 发布:刘洪波四大讲座 知乎 编辑:程序博客网 时间:2024/06/09 16:46

背景

任何一个产生环境的IT系统如果要长久下去,必须对其进行监控告警。常见的实现分为三个部分

  1. 采集目标系统的指标,并上报到中央服务器
  2. 对指标按时间窗口进行统计,并存储成为曲线
  3. 对曲线进行异常检测,在必要的时候告警通知运维人员

在过去,对于第1、2两点我们已经积累非常多的文章和工具来谈论如何来实施一个“监控系统”。但是对于如何有效检测异常却非常缺少行业经验的积累。如果要对有效进行量化的话主要是两个指标:

  1. 误警率:在所有产生的告警中,有多少代表了真正的故障
  2. 敏感度:在所有真正的故障中,有多少产生了告警

一般来说,误警率可以放宽一些。虽然可能会产生狼来了的结果,但是稍微多一两条告警并不会产生什么实质的危害。需要卡控得比较严格的是漏警的情况。一旦监控系统漏警了,往往意味着一起责任事故和不小的损失。

曲线分类

首先,我不是一个理论工作者,对于自回归,方差等术语没有特别偏好,也没有通篇使用数学符号表达含义的表达能力。从经验观测的角度,我所知的曲线大概分为下面三类(该分类绝对不是完整分类):

  1. 成功率曲线(success rate):最常见的成功率曲线比如网络测量里的丢包率,还有HTTP接口里200到400之间返回码的占比。
  2. 延时曲线(latency):最常见的延时曲线比如网络测量里的网络延迟(比如ping值),还有HTTP调用从请求发出到请求收到之间的耗时。
  3. 调用量曲线(arrival rate,queue length):最常见的调用量曲线比如网络测量里的收发包量,收发字节数,还有HTTP请求量。

而所需要做的监控也一般分为两类:

  1. 可用性监控(availability):只需要回答挂了还是没挂两个问题,挂了就打电话,发短信,发微信通知运维人员,否则shut up。
  2. 性能监控(latency ~ user experience):一般不需要触发立即的运维介入,而是用于事后回答过去一段时间的用户体验是变好了还是变坏了。不需要运维介入是小的性能下降,一般运维没有办法,必须要开发发新版本来解决。而大的性能下降很快就会演变成可用性监控的范畴,所以无需在性能监控里做回答。

性能监控主要关注并发处理的请求数,以及与延时的关系。而可用性监控如果可能的话,首选成功率。如果我们可以知道最近一段时间内500的HTTP返回显著上升了,基本上可以断定系统出了可用性的问题了。如果对URL做一个分类,然后某一个分类里500显著上升那么这个推断就更加精确了。这种异常检测几乎也无需太复杂的算法了,甚至一个绝对的阈值就可以了。

特别注意的是成功率的曲线不一定非要在一个调用的地方有一个错误码之类的东西,它可以是来自同一个流程上两个环节的数据。比如一个成功的业务过程,需要经过a,b,c三个点。我们如果知道a点的调用量,b点的调用量,也就相当于知道了a点的成功率了。所以大部分时候,我们是可以计算出成功率曲线的。

但是有的时候,比如因为组织架构的原因,无法要求我们的监控对象必须是某类曲线。假设我们是一个互联网的监控服务提供商,比如监控宝。我们无法要求我们的用户必须提供HTTP的成功率曲线给我们,用户只能给我们HTTP调用的绝对数量。然后仅凭这么一条调用量的曲线来检测是否有异常。

本文的讨论对象是当我们仅有“调用量曲线”的时候,如何依靠算法检测出异常。

总体思路

一般来说,所谓的算法来实现所谓的智能,有两条道路。

  1. 搞一个牛b的算法,“自动”找出异常。然后人来解读这个异常代表了什么含义。
  2. 人根据经验“知道”什么样的曲线代表了异常,然后想办法搞一种算法来逼近人眼来看的效果。

对于算法检测异常出告警这件事情来说,直觉智慧还是占了上风的。和高维度的聚类之类的场合不同,业务/系统指标的曲线对于异常可以非常可视化地体现出来。如果我们以算法为第一位,直觉放第二位的话,会产生这样的情况:

  1. 不小心误警了,但是从曲线上“看来”完全没问题,我们无法向运维人员解释算法的细节
  2. 不小心漏警了,从曲线上“看来”非常明显,我们还是无法向运维人员解释算法的细节

从明哲保身但求无过的角度来看,算法必须能够到达和运维人员肉眼观察曲线相似的效果,否则无法向“人”交代。在满足了这个前提下,才去看那些单曲线无法感知的,但是综合多个曲线可能可以推测出的系统性故障。

如网站的访问人数之类的业务指标,本身具有周期性的波峰波谷,而且非常容易受到营销事件的影响。比如下面这个曲线

曲线自身就一直在震荡,在这样的曲线上如何检测异常?异常长什么样?比如这个

这段区间的数据掉底了(数据为零),这是非常显而易见的异常。

这个异常就需要放大了才能观察出来。在下降的过程中,突然陡降了一点,然后又有一个缓慢回升。

这个异常几乎肉眼无法分辨。如果不是那个时间点真的出了故障,也很难从曲线上找出来。

上面也是两个真实的故障。所有上述的故障,人的肉眼可以发现出来基本上我认为都是因为人在观察曲线的时候对于曲线的平滑和不平滑的交界处非常敏感。我们的算法第一位的需求是要把人肉眼可以观察出来的波动,锯齿,坑都检测出来。
除了上面的这些情况之外,还有一类异常,可能就会比较难以捕捉,不如:

上面的图示一条曲线的七天叠加,也就是根据七天的规律,基本上波形是一致的。但是假如在红线处,业务曲线实际上应该有一个波峰的时候,是一条水平线过去的,那是该告警呢,还是不告警呢?如果单纯看一根线是完全没有平滑不平滑的问题的,因为几乎是直的。从历史统计的角度来看,这样平过去的情况肯定是异常了的,需要告警出来。更极端的的一些情况,比如这个时间点有营销资源的投放,案例曲线应该有明显的抬升,但是实际曲线和往日差不多,那是不是也代表了异常?这样的一些情况的处理,我认为是算法检测的加分项,本职工作把人肉眼可以观察到的波动捕捉到就可以了,对于第二种情况就能做到多好就做到多好,实在没检测出来,也是可以理解的,因为确实很难。

statistical process control

我们搞工程的是没有本事重新发明什么算法的。IT系统的业务数据的监控告警,看似很新鲜,其实这是一个早就被研究多年的成熟领域了。它的名字叫statistical process control(基于统计的流程控制),简称SPC。最早的奠基人是Walter Shewhart ,他发明的Shewhart Control Chart是最早的用于告警的曲线控制图。在这个基础之上,因为工业质量控制领域用途广泛,有了非常深入的发展。著名统计软件SAS,甚至有一个专门的工具SAS/QC用于这个领域。

最基本的shewhart control chart长成这个样子:

假设这张图代表了钻探领域里打的孔的孔径。因为钻头的大小是固定的,所以孔径应该会在一个范围上下波动。在正常的情况下,这个波动应该在一个合理的范围。在钻头损坏的情况下,波动应该会超过这个合理的范围。我们要做的告警就是检测到钻头损坏这样的场景。怎么做?

根据shewhart control chart,首先需要根据历史的统计数据得到一个平均孔径的大小。以及在钻头良好的情况下,实际打的孔径在这个平均上下波动的方差。什么是方差?

对应的方差就是

http://en.wikipedia.org/wiki/Standard_deviation


在统计上,方差的含义就是代表了一组数据的振动幅度。利用这些钻头良好情况下的值得出的良好情况下的振动幅度,我们就可以知道什么是“合理的范围”。比如,拍个脑袋,在两倍振幅之内是合理的。振幅也就是历史值的方差,数学符号叫sigma,所以可以说在“两个sigma”之内。于是乎,我们可以在图上标记一个上界和下界。如果观测的值超过了上界或者下界,那么可能就是一个异常点。

用拍脑袋的方式决定“两个sigma”也是非常不科学的。实际使用中,这个n sigma的n如何确定也是头大的问题。对于这个问题,我们可以用反推的方法来解决。在给定历史数据的情况下,先计算出sigma,然后再计算出历史数据相对于历史的平均值每个点是几倍的sigma。根据这些历史数据对应的sigma的倍数(统计上叫zscore),我们可以知道大部分的历史数据都是n个sigma之内的,从而把n求出来。

shewhart control chart的问题

shewhart control chart不是包治百病的,没有任何一种control chart是适合所有类型的曲线的。shewhart control chart的适用范围是:

  1. 目标的曲线是stationary的,也就是没有明显的上升和下降趋势。也就是从统计上来说,每个点的下一个点都是可升,可降的。(本人不是统计学出身,不要扣细节哈)
  2. 曲线的振动幅度是恒定的,不会随着时间推移和变化,也没有周期性规律

到这里就需要引入行业特性了。如果我们监控的是HTTP请求量这样的曲线的话。一般来说都会有以下特征:

  1. 曲线不是stationary的,而是一段时间内持续上升(比如晚上9点到11点),而一段时间持续下降(比如凌晨2点到4点)。
  2. 曲线的震动幅度也是非常不固定的,某个时间段可能发生非常剧烈的变化(比如特定时刻开始抢红包之类的营销手段影响),某个时间段又非常平稳。
  3. 曲线基本上以天为周期单位循环

而我们的目标就是,结合shewhart control chart开创的基本思想,与我们监控的曲线的特征,选择一个最合适的算法来检测出异常。

周期性规律

最容易被想起来利用的是周期性规律。比如,下面这个故障场景:

对比昨日和今日的曲线,我们发现在非故障的时候两条曲线基本重合,和故障的时候下跌明显。那么很容易就想到,为什么不用昨日的曲线,或者历史的平均值来预测今日的曲线呢?
的确是可以的,比如我们可以用过去7天的平均值来作为今日的预测值。然后用过去7天的数据与预测值比较得出方差,用方差来确定上下界。效果差不多是这样的:

首先对于时间点xx:yy,我们用过去3天的历史数据得出一个平均值。然后我们可以用这些平均值得出一条预测曲线,也就是上图中的绿色的线。然后用黄色的实际值做对比。我们可以发现在业务稳定的情况下,近期的历史是可以很好的预测今天的实际曲线的。

但是实际查看历史三天的曲线,我们可以发现上下是有波动的。这些波动就会造成方差的不稳定。

我们可以看到,三条曲线比较重合的时候,方差非常小,而有些时间点上方差变大非常大。

方差波动的后果就是我们很难用方差来确定所谓的“合理范围”。
残差 = 实际值 - 预测值
波动幅度 = 残差 / 方差
上面这张图就是波动幅度的曲线,而标了红色方框的区域是故障时间段。可以看到从统计特征上来说,是明显的,但是不够明显。用这种方式来告警,可能就会产生比较多的误警。

实际使用的过程中可以考虑几个优化:

  1. 预测值可以选择平均值,移动平均值,中位数,曲线拟合(比如自回归)
  2. 方差计算的过程中要剔除掉明显的异常点

这些优化可以有改善,但是无法从根本上克服业务本身的不规则上下波动造成的统计特征的模糊。更要命的是因为业务经常整体上下移动,比如周末和节日。这些整体的上下会进一步扩大方差,使得本来异常的波动被认为在合理的范围之内,从而造成漏警。而我们知道,漏警是致命的。

所以直接用历史值的周期性规律来做预测和告警是很难做的,因为业务可能会昨日重复今日,但是积极上升中的业务不会每日都一样。

0 0
原创粉丝点击