搞定这个日志智能分析设计,大小故障都无处可逃

sw 2022-06-05 11:26:00

一、背景

 

随着系统的日益复杂,生成的日志是海量的。当发生故障时,人工从海量错误日志中定位异常的成本非常高,主要原因:

 

  • 日志格式繁多,难以依靠人工划分,而传统的日志规则分类需要配置复杂的规则和正则,难以通用化;

 

  • 日志量级大,报警多,难以定位需要关注的异常,一些无关的错误日志容易掩盖真正的问题。

 

日志智能分类算法可以自动根据日志的相似性对日志进行流式聚类,并提取其中的关键信息——日志模版,有效避免相似日志的无效查看,大幅节省排查时间。并且日志异常检测可以从海量日志中发现潜在的异常日志模式,帮助程序快速定位异常。

 

二、日志智能分类

 

 

1、设计方案

 

最初的日志分类服务在具体的算法选用上选择了 Drain 算法来对日志模板进行提取。随着业务经验的累积,为了提高日志分类的准确性,用两级模型对日志进行流式分类并生成日志模版。其中一次分类相当于是预分类,然后把预分类结果再进行二次分类,将预分类没有分好的日志进行融合,形成最终满足预期的分类结果。

 

 

2、一次分类

 

一次分类使用的是一种改进的前缀树。

 

图片

 

从根节点开始,第一层为长度层,同样长度的日志进到同一个结点,接下来的节点就是根据token进行判别,只有匹配了同样的token的日志,才往对应的路径分。此外,还会设置一个阈值,当叶子结点到达阈值后,会增加一个<*>结点,用于匹配所有未成功匹配的日志。由于树深是提前设定的,一般会设得比较小,因此匹配的速度会非常快。整个树生成和匹配的过程实际上就是一个加入长度层的前缀树。

 

算法流程如下:

 

  • 预处理,以分隔符/空格为单位将日志切分为一个个的token;

 

  • 根据日志token长度去第二层(每个节点对应一个长度)寻找对应节点,比如Receive from node 4匹配的节点对应日志token长度为4;

 

  • 根据日志token按顺序去进行分裂,这里受到depth限制,分裂树深为depth-2(去除root和length层);

 

  • 分裂到叶子节点后,计算日志与各个模板的相似度simSeq,返回simSeq大于阈值st并且相似度最高的模板;

 

  • 更新Parsetree,当日志在叶子节点匹配到了模板,并且部分token有差异,则用<*>替换;当没有匹配到模板时,则将新的日志加入到该叶子节点的模板列表,作为新的模版。

 

 

3、二次分类

 

二次分类使用的是基于最长公共子序列匹配的算法。

 

图片

 

一次分类第一层会有一个长度匹配,那不同长度的日志必然不会被分到一类;而且一次分类是根据token的顺序一个个匹配的,计算相似度也是顺序计算的,两条很像的日志,如果仅中间某些token划分不能对齐,就会导致无法分到同一类。为了克服上述问题,引入了基于最长公共子序列的匹配算法,将一次分类获得的模版进行二次合并,获得聚类效果更佳的模版。同时,由于最长公共子序列的时间复杂度较高,因此设计了前缀树、简单循环等两种前置预匹配方法,减少部分LCS的匹配,提升算法整体的效率。

 

算法流程如下:

 

1)预处理,以分隔符/空格为单位将日志切分为一个个的token。

 

2)前缀树预匹配,可以匹配到则返回匹配的分类和模版;匹配不到转到(3)。

 

3)简单循环匹配,可以匹配到则返回匹配的分类和模版;匹配不到转到(4)。

 

4)计算最长公共子序列,进行LCS匹配,可以匹配到则返回匹配的分类和模版,计算更新后的模版,差异部分用<*>替换;匹配不到则新增分类和模版。

 

 

4、traceback日志的处理

 

traceback日志与普通日志不一样,普通错误日志以分隔符/空格来划分token比较合适,但是traceback错误日志是一个trace的结构,按行看比较合适,因此把每一行traceback日志作为一个token,这样可以把trace类似的错误聚到一起,方便查找问题。

 

 

5、解决冷启动问题

 

日志智能分类算法会比较日志和模版,保留相同部分(常量),填充差异部分(变量)为<*>,更新模版,因此天然具有提取日志公共部分的能力。但是如果直接把原始日志输入到算法中,需要较长时间才可以完成模版的收敛;且收敛后的模版与输入的顺序有很大的关系,无法获得一个鲁棒的结果。这样会导致新接入的项目无法很好地应用日志智能分类。但如果能提前识别出变量,提前将其填充为<*>,则可极大地提升日志模版的收敛速度,也可以获得更加鲁棒的日志模版。通过正则将数字、base64编码和地址编码提前填充为<*>极大地提升了模版的收敛速度,原来要一周才能稳定并可用的模版,现在接入后马上可用。

 

三、日志异常检测

 

 

1、设计方案

 

目前业界对日志异常检测的研究有很多,总结来说主要为以下几类:

 

图片

 

我们日志异常检测算法主要是是基于统计+无监督的日志模版异常检测算法,完全没有人工标注成本,计算简单并具有较好的可解释性。目前日志异常检测算法需要依赖日志智能分类算法,需要在获得日志实时分类模版后,根据各模版的日志量的历史数据进行异常判定,从而发现其中的异常模式,并将对应常通过告警发送给用户。

 

 

2、具体算法

 

日志异常检测算法设计为1min粒度的:

 

1)按机器维度获取该机器的日志模版以及对应日志量的历史数据。

 

2)将不同模版的日志量历史数据使用卡方分布进行聚合。

 

3)对聚合后的数据进行异常检测,当发现异常时,触发(4),如果未发现异常,则返回结果为正常。

 

4)对每个日志模版对应的日志量的历史数据进行异常检测,根据异常程度,返回Top5的异常模版。

 

5)如果没有返回异常模版,则本次检测返回结果为正常;如果返回了异常模版,则本次检测返回结果为异常,并将异常结果、异常模版告警。

 

这里的异常检测设计为两步主要是因为:

 

  • 每台机器的日志生成的模版数量都很多,对应日志量历史数据中噪声也较多,直接进行模版的异常检测容易产生较多无效的报警;

 

  • 模版数量较多,全量进行1min粒度的异常检测计算压力较大,可能不能在1min内完成检测。因此考虑先从全局的角度设计一个噪声较少的指标,先对该指标进行异常检测,当异常检测结果为异常后,再下钻到具体的模版进行异常检测,找到异常模版,既能减少误报,也可以降低CPU压力。

 

设计平方和作为全局具有代表性的指标,采用卡方分布进行数据聚合,假设n个模版的频率特征符合标准正态分布,基于此构建模版的平方和特征,该特征满足卡方分布,在n较大时,卡方分布可近似为正态分布,可以使用3sigma的方式进行异常检测。

 

图片

 

日志异常检测使用的异常检测算法除了使用3sigma方法外,还使用了箱线图的方法,将两者的结果转换为异常分数后再进行融合。

 

 

3、减少周期性误报

 

周期性异常是指昨天或者上周出现过的同样的异常,此类报警属于周期性误报。此时,昨天或者上周对应时间点的异常分数也会较高,可以利用这个历史分数对当前分数进行抑制,达到消除周期性误报的效果。但是,出现异常的时间点可能会有一定的偏移,因此需要对昨天和上周一定时间窗口内的分数进行中心rolling_max操作,再使用当前异常分数减去昨天或者上周较大的那个分数,得到抑制后的分数。

 

 

4、新日志类别报警

 

某些情况下希望当出现新的日志模版类的时候,对新日志类别进行告警。但是当日志智能分类结果尚未稳定的时候,会经常出现新的日志类别,有可能产生很多误报。我们设计了自适应的新日志类别报警,当一天内出现新的日志类别超过一定阈值时,对新日志类别报警进行抑制;当低于一定阈值时,进行新日志类别的告警。

 

四、应用效果

 

图片

 

当日志异常检测检测到日志异常时,会告知用户异常的模版。

 

图片

 

进入日志计数页面可以看到,当前时刻确实出现了异常的日志暴增。

 

>>>>

活动推荐

 

相信大家通过上文,在日志智能分析上汲取了不少经验,在智能运维的落地过程中,大家还面临着这些难题:如何进行时序数据的异常检测?如何有效缩短故障定位时长?如何进行硬件故障预测?

 

在即将到来的6月17日,Gdevops全球敏捷运维峰会-广州站邀请了网易游戏-技术中心-智能监控AIOps团队负责人施望,给大家带来的《网易游戏AIOps探索与实践》主题分享,将会给大家解答上述疑难。同场还有高可用架构设计、故障自愈、智能化运维等精选数据库议题,欢迎扫描下图二维码了解更多:

 

 

图片

活动预告