上一周,咱们创建了一个pipeline聚合,将数千个数据点分解成少数表明性指标。 这造成了Atlas的基础,而且为实现异常检测器所作的全部重大工做。本周,咱们将结束实施并生成一些有趣的图表。html
咱们建立的聚合被设计为在特定的时间窗口上运行:给定日期范围,它将为每个metric发出第90个百分点的意外(surprise)值。要彻底实现Atlas,咱们须要随着时间的推移绘制第90个百分点值。目前这个功能仅仅使用Pipeline聚合是不可能使用的(虽然已经提出了一种“滑动柱状图”功能来弥补差距)。java
替代的,咱们将把责任移交给TimeLion,它很是适合这种后期处理(Timelion是一个新的{Re}search项目,在kibana内部进行流畅的时间序列操做,你能够在这里阅读更多)。node
假如你从新去看模拟器的代码,你将看到咱们在数据生成以后运行了一查询系列 。咱们以一小时的增长滑动咱们的Pipeline聚合数据(窗口的大小为24小时)。咱们还使用了filter_path来最小化输出,咱们实际并不关心60,000个buckets。咱们仅仅想要每一个metric的“ninetieth_surprise”。过滤响应大大较少了网络传输。而后将值索引回Elasticsearch,以便咱们稍后再对其进行统计。git
咱们在模拟器中提早预处理了这些值,以简化演示,但在一个真实的系统中,你可能会有一个Watcher或者cronjob每小时执行一次查询并保存结果。github
经过上周的艰难举措,咱们能够转而使用Timelion完成实施。第一个业务是下降特定指标的第90个值(the 90th values)。咱们可使用如下Timelion语法:服务器
.es('metric:0', metric='avg:value').label("#0 90th surprise")
它将生成看起来像这样的一张图表:网络
那看起来颇有趣!绝对有事情发生。咱们来看看这张图表的含义,由于它是Atlas的工做原理:函数
实际上,若是咱们看到一个凸起,咱们能够得出结论,基础数据已经发生了改变,以改变了咱们的正常方差,多是因为中断。这是Atlas的核心:不要看你的数据,由于它是如此的多。相反,观察偏离平均值的第90个百分位数的差别。测试
假如将上图表和metric #0的实际数据相比较,你将看到明显的区别:优化
固然,诀窍是如今自动识别那些凸起和图表/警告。让咱们开始构件逻辑。当第90个百分位数surprise是移动平均线以上3个标准差时,Atlas报警。假如你分解该问题,你将看到一些必要的组件:
首先,咱们构造滚动三标准差。咱们经过自定义movingstd()函数来作到这一点(参见注脚脚本,它与movingavg()函数基本相同),而后乘以3,以获得第三个sigma:
注意:我缩进了全部的查询,以使他们更加容易阅读。
.es('metric:0', metric='avg:value') .movingstd(6) .multiply(3)
其次,我写了一个计算数据自己滚动平均线的片断:
.es('metric:0', metric='avg:value') .movingaverage(6)
最后,咱们经过将这两个片断加在一块儿以建立“阈值”。这将建立一条在数据移动平均线以上三个标准差的线。
.es('metric:0', metric='avg:value') .movingaverage(6) .sum( .es('metric:0', metric='avg:value') .movingstd(6) .multiply(3) )
如今咱们有了一个“阈值”,咱们能够用原始数据绘制,并看看它们若是比较:
嗯,OK。若是阈值是否工做,如今还不清楚。该图表很难阅读,一旦surprise值凸起,就会致使阈值的后续的凸起。这是由于凸起致使方差的巨大变化,滚动标准方差会上升,致使阈值自己的上升。
假如咱们放大第一个凸起,咱们能够看到,在滚动标准方差上升以前,第90个百分位数稍微超过阈值:
(抱歉,此图表错误标注:“metric:0”应该显示为“#0 Threshold”)
如今很清楚:咱们想显示的是surprise超过阈值的时刻,而且另外忽略阈值(由于它只在第一瞬间有用)。当它超过阈值的时候,让咱们显示单独的条,以替代持续的线条。
为了作它,咱们构造了showifgreater()方法。这将只显示第一个系列中的数据点,若是它们大于第二个系列中的数据点(参见注脚脚本)。
.es('metric:0', metric='avg:value').showifgreater(...)
要完成咱们的查询,咱们仅仅但愿显示大于三个标准方差大的数据(假如它突破了阈值),而后咱们要显示为棒而不是线条。这组成了咱们最后的查询:
.es('metric:0', metric='avg:value') .showifgreater( .es('metric:0', metric='avg:value') .movingaverage(6) .sum( .es('metric:0', metric='avg:value') .movingstd(6) .multiply(3) ) ).bars() .yaxis(2) .label("#0 anomalies")
这产生了更好看的图表:
最后让咱们加回数据自己,这样就能够进行比较了:
.es('metric:0', metric='avg:value') .label("#0 90th surprise"), .es('metric:0', metric='avg:value') .showifgreater( .es('metric:0', metric='avg:value') .movingaverage(6) .sum( .es('metric:0', metric='avg:value') .movingstd(6) .multiply(3) ) ).bars() .yaxis(2) .label("#0 anomalies")
瞧!咱们已经实现了Atlas!完整的面板包括每一个metric的图表,以及显示中断建立时的图表(你显然不会在生产环境中使用,但对于验证咱们的模拟数据是有用的):

若是你经过中断图表(左上角)进行操做,你将至少在一个metric图表中找到相关的异常,一般几个在同时。使人鼓舞的是,异常被标记为全部类型的中断(node,query,metric)。注脚包含了一个中断的列表和它们的大小,以让你了解影响。例如,一个“Query Disruption”持续了三个小时而且仅仅影响总共500个查询中的12个(2.4%)。
在图表中看到的一个现象是一小段时间保持在高位的凸起。这部分是因为中断的持续时间,有些持续了几个小时。但也有多是因为咱们上周提到的pipeline聚合的局限性:咱们选择了每一个时间序列最大surprise,而不是最后的surprise。这意味着在最坏的状况下,中断会延长额外的24小时,由于一旦中断从窗口上脱落,surprise才会重置。这彻底依赖于选择窗口的大小,而且能够经过增长/减小窗口来改变敏感度。
这种现象不会影响的异常检测,尽管若是你尝试使用更长时间窗口,这一点变得更加明显。一旦pipeline聚合有选择“最后”的能力,这个现象应该就被解决了。
So,那就是Atlas,在eBay创建的一个很是简单--但很是有效--统计异常检测系统,如今在Elasticsearch +Timelion上实现了。在pipeline聚合以前,这多是由不少客户端逻辑实现的。可是,每小时将60K的buckets流向客户端处理的前景并不诱人,pipeline聚合已经将重要的举措转移到服务器以进行更有效的处理。
pipeline聚合还很年轻,随着时间的推移,期待更多功能被添加。假如你有一个难以在pipeline中表达的用例,请告诉咱们!
“但是,等等” 你说,“这只是绘制异常,我如何获取预警”。对于这个答案,你必须等到下周,当咱们实现了TimeLion语法做为观察者观察,如此你能得到email,Slack等等的自动预警,下周见!
原文地址:https://www.elastic.co/blog/implementing-a-statistical-anomaly-detector-part-2