Author: Hu, Elvin
摘要
监控(Monitor)对服务(Service)的重要性不言而喻。一个配置了有效以及可靠的监控的系统,就像拥有不间断雷达和卫星跟踪保护的民航飞机一样, 让人放心,在关键时刻亦能最大程度的发出警报并减少灾难带来的后果。
智能判断样本是否超越警戒线不是一件容易的事情。漏报和过多的误报都不可取。而样本通常由用户行为产生,且不符合严格的解析规律,这对阀值的设置造成很大困难。为了解决这个问题,我们采用观察变化率的方法,把不可解析的样本曲线转换成可解析的曲线,从而简化分析和判断过程,有效地捕捉突发异常事件,并降低误报率。
本文以eBay的支付流程为例,介绍我们的系统监控实践。
一.简介
eBay依靠 PayPal作为支付平台,而Payment系统处于eBay在线网站和PayPal之间。简单来看就是这个样子:
我们负责开发和维护就是其中的“Payment”部分。通常与钱相关的系统都比较重要,稳定性要求也相对比较高。所以 Payment 这条线必须保持时刻畅通以保障终端用户的支付体验。一旦发生了系统问题我们必须在第一时间知道并予以解决,同时把对用户的影响降低到最小。这一切都仰仗对其的监控能力。
二. 目标
一个合格有效的监控,在具有定性感知错误的能力外还要能定量的回答:什么时间(when),在哪一个环节(where),出了什么错(what)。
When(实时):实时性可理解为灾难发生到被感知的延迟时间 。这个时间当然是越短约好。但是出于对资源的考虑,对不同功能的系统,实时性的要求不完全一样。目前我们对Payment定义的实时为5分钟延迟。
Where(定位):下游系统被理解为上游系统的依赖(Dependency),所以PayPal是Payment 的依赖,Payment又是Checkout的依赖。当支付无法完成的时候,有可能是Payment本身出了问题,也有可能是PayPal出了问题。迅速的定位是解决问题的关键。
通常的做法是对自己监控,同时对下游做监控,因为当别人问起来的时候,我们就可以告诉他Payment有没有问题,是否是由于PayPal不Work而导致的,等等。常常被忽略的是对上游系统的监控,作为身在同一个战壕里的战友,Checkout 出了状况Payment也有责任及时感知并通知,毕竟都是对用户体验的影响。
What(症结):也理解为Root Cause。有可能是数据库连接失败,有可能是HTTPS证书过期,有可能是集群中一台机器掉线,等等。监控系统给出的信息量越多越能帮助我们找到这个Root Cause,从而更迅速的解决问题。
另外还有两点,也非常重要。
多重保护:最好有两套或更多不同监控机制,互相参考,互为备份,以防其中一个出现失误。
误报率:保险起见通常监控会比较保守,宁可错报也不能漏报。但是可以想象如果一个监控频繁地发出报警,就变相形成了一个 Spam。会让人麻木,同时淹没真正有效的报警。所以误报率需要尽可能的低。
三.实现
实现过程可以概括为三个部分。
第一步非常简单就不多说了。这是监控的数据来源和基石。重点在确保数据的准确。同时记录Audit Log的部分可以采用异步线程池,以减少对主业务进程的影响。
Sample:
AUDIT_ID |
… |
ERROR_NAME |
ERROR_DOMAIN |
CREATION_DATE |
5000454 |
… |
|
PAYPAL |
20140201000151 |
5000455 |
… |
VALIDATION_ERROR |
PAYPAL |
20140201000155 |
… |
… |
… |
… |
… |
第二步中的时间间隔可调。间隔时间越短越实时,但是产生的aggregation数据就越多,事后分析的计算量也越大。反之间隔时间越长,实时性越差,如果一天一次aggregation那就只能称之为Report。这需要根据资源和系统重要性做权衡。
Sample:
Aggregation的结果是放在 Mongo DB里的,所以以JSON 文件格式表示。
{ operationName : "someName" startInMillisecond : "1420790400000" endInMillisecond : "1420876800000" data : { CART_ID_NOT_FOUND : 6 VALIDATION_ERROR: 5 NULL : 982 } } |
其中可以看出,这5分钟内分别收到了6个CART_ID_NOT_FOUND和5个 VALIDATION_ERROR,NULL 即没有Error Code表示成功的请求,共982个。搜集一天的数据后可以为每一个Error Code 得到一条曲线,叠加在一起得到一幅图:
第三步比较tricky也最值得讨论。其困难点之处在于如何判断出异常情况。
拿刚才的例子和下面一张图做一个对比:
我们凭感觉会知道第二张图上有一个Spike,代表发生了一个系统异常。因为可以明显看出曲线上有个向下的尖峰,之后很快恢复正常。那么如何让程序自动产生这样的判断呢?由于这条NULL曲线由用户行为产生,非线性,也无法写出解析函数,很难得到参考基准值。我们尝试了多种方法:
1..统计出现的错误数量。成3功的曲线出现波动,我们很自然想到是由于。如果错误的数量增长超过一个阀值,则表示出现了问题。这种方法貌似可行,但是网站的流量随着时间变化,是一个不稳定的值。通常早上是低谷期,下午到凌晨是高峰期。流量增长会自然的引起错误数量的增长。实际操作中,误报率比较高。
2.统计错误率。为了排除流量的影响,我们会很自然的想到错误率的方式。用错误量除以总流量得到一个百分比。同样设置一个阀值,当错误率超过阀值时报警。依然是一个貌似可行的好办法。但是错误会分轻重,对于某些严重的错误,仅仅出现少量甚至一例也是需要关注的。有些则可以放宽松一些。但是如果给每一种错误分别设定阀值,工作量太大,PayPal会返回将近30多种不同的错误。而且这种方法对新出现的未知错误代码没有感知能力。不够灵活。
3.寻找规律性的参考标准。如果每天的流量变化都是类似的曲线,那么可以给高峰和低谷时期设置不同的阀值。但是实际情况并没有我们想象中那么简单。下面是一个连续8天的流量图,可以看出其规律并不如我们所想以“天”为单位,而更像是以“周”为单位在循环。如果要设置动态阀值的话,就要对一周中每一天都设置一个不同的阀值。依然不够灵活。
慢慢我们总结出两点:1.由错误量间接反应成功率不是个好办法。如果要分析某条曲线是否正常,还是紧盯这条曲线。2.阀值需要浮动。
基于这两个发现,我们回到原点重新思考,以人类的思维方式是如何判断出上面Figure 2的曲线中暗示着有异常发生的?
下面是我们的最终实现方法。先看一张图。
蓝色的NULL线依然是得到成功返回的曲线,下面的Delta是将当前NULL线上的值和上一时间点NULL线的值做差,并求出绝对值。所以Delta曲线可以理解为NULL曲线上各点的变化率。Delta值越高表示NULL曲线变化越大反之则越小。理想情况平缓变化的NULL曲线将带来一条几乎水平的Delta线。反之如果我们看到Delta线上出现了高峰,则表明NULL曲线出现了短期大幅波动。这就是我们所关注的异常情况。
画出上面Figure 2的Delta线,大家可以直观的看出这种效果。
这样我们可以一直关注我们需要关注的曲线,不再需要通过其他的变量(错误率等)来间接判断成功率。而且从关注一个无法写成解析函数的曲线本身转变成关注其变化情况,而后者几乎因该是一条直线。可解析,可预测。这甚至在一定程度上排除了“天”和“周”的影响,因为无论什么时候我们 期待Delta都将是一条近似的直线。
问题简化了,还剩下阀值问题需要解决。怎样在Delta上设置一个有效的阀值呢?我们可以凭借经验,在Delta上方画一条线,超过了这条线就意味着相应NULL曲线的波动幅度过大,超出了可接受的范围。这可行,但还不是最好。从Figure 4中可以看到,在峰值附近Delta本身就会产生较大的偏移,即使在正常的情况下峰值前后波动也比其他时间要大。
我们借用之前“寻找规律性的参考标准”的办法。假设:用户的行为在每天的不同时间是大致规律的。可以从Figure 3看出来,峰值总是出现在晚上10点左右,低谷总是出现在凌晨4点左右,且每天的变化符合同样的规律。基于这个假设,当需要判断一个Delta点的接受度时,可以先取到同一时间点前N天的Delta值,如果这些Delta值都很大,那么今天这个较大的Delta也被认为是可以接受的。甚至可以取这前N天值中最大的一个作为可接受范围。只要今天的值与之“接近”就算是没有问题。这个“接近”是另一个阀值,但可以简单很多。无论怎样, 我们得到了一个动态的参考阀值。
问题貌似彻底解决了,其实还差一步。如果这前N天同一时间的点中正好出现了一个Spike怎么办?这个Spike是这N个参考值中的Outlier,需要去掉。这是一个统计学的问题,可以用统计学的方法来解决。
举个例子,取同一时间25天的Delta值(未求绝对值):
1420820700000 -13 1420734300000 145 1420647900000 111 1420561500000 -30 1420475100000 124 1420388700000 110 1420302300000 114 1420215900000 -21 1420129500000 -6 1420043100000 7 1419956700000 -136 1419870300000 148 1419783900000 77 1419697500000 -93 1419611100000 150 1419524700000 -117 1419438300000 30 1419351900000 -90 1419265500000 235 1419179100000 -28 1419092700000 -30 1419006300000 -53 1418919900000 207 1418833500000 -61 1418747100000 -380 |
用R语言计算箱线图方法得到Outlier:
> x <- c( -13,145,111,-30,124,110,114,-21,-6,7,-136,148,77,-93,150,-117,30,-90,235,-28,-30,-53,207,-61,-380 ) > bp <- boxplot(x, range=1.5) > bp$out [1] -380 |
可以得到其中的Outlier是-380。将其去除后,用剩下的数得到参考阀值即可。动态的阀值有效的降低了误报。
在我们知道样本超出了阀值的同时,也可以知道其超出量。超出量越高则表明原曲线波动越大。这里可以得到一个Level值。比如得到的历史阀值为80,当前样本为400,超出阀值320。如果以100为一个Level等级,那么这次警报等级就是L3。这样可以帮助On Call人员快速评估异常的严重情况。
四.结论
有了这样一套机制,我们可以对NULL(无错误正常返回的请求)以及主要的Error Code做重点监控分析。这主要是监控我们的下游即PayPal返回的错误。同时前文提到的CAL(eBay内部的中央Log系统)会以类似的方式分析Payment内部发生的异常。如果我们发现流量曲线发生了大副下降波动,但是既没有大量PayPal的错误,又没有 Payment内部异常的错误,这时候我们就知道很可能是上游Checkout出了问题。
这套系统之前由于静态阀值问题,我们每天峰值前后多会收到近百封报警邮件。现在,新的算法在短期的调整过阀值后,基本上能够做到零漏报和几天一次的误报。而且邮件中都会包含危险等级,方便我们在短时间判断是否需要采取下一步行动。
Delta方法的局限:
没有完美的方法也没有普适的解决方案。这种Delta方法的原理是观察并发现平缓变化曲线上的突发峰值。所以如果被观察的曲线本身就有很大的起伏,这种方法将不适用。具体的问题需要具体的分析。