Press "Enter" to skip to content

实时日志智能分类实践

 

旺仔、opiece

 

旺仔:网易数据中心搬砖工程师,诚招大佬加入

 

opiece: 网易游戏高级运维工程师,目前主要负责 AIOPS 相关的算法以及部分开发工作,一个数据智能化以及智能产品落地方向的码农

 

实时日志智能分类实践

 

在游戏、平台等服务端中,大家越来越多会通过日志的形式来输出系统的状态、行为等信息以便协助系统状态跟踪、问题发现和定位,不知你是否也遇到过类似下面这些问题:

 

日志量级太大,导致难以发现异常模式或者潜在异常

 

想屏蔽已知问题的报警,但苦于担心影响未知问题的发现不敢屏蔽

 

新版本上线后,系统行为有变化,却无法感知

 

故障出现后,日志报警太多导致报警风暴

 

这些问题,归根到底是因为日志信息太多、模式不定,从而无法对日志进行很好的归类。

 

在网易我们基于 Flink 实时计算引擎和 AI 算法,支持了实时日志智能分类分析能力,结合日志告警和日志全文检索,可以比较好降低异常日志的发现和定位成本,帮助用户快速掌握日志全貌,及时发现异常,实际使用下来效果不错。

 

实时日志智能分析介绍

 

Loghub 实时智能分析是通过 AI 模型自动将相似度高的日志通过提取日志模板的方式进行实时分类:

 

 

以游戏服场景为例,整条数据处理流程如下:

 

 

基于我们 Loghub 数据流的实时采集上传能力,整个日志实时分类可以做到海量日志秒级输出自动分类结果。

 

功能特点

 

无需设置日志分类规则,通过 AI 模型自动合并相似日志

 

支持自定义调整分类精度,灵活控制分类数

 

支持对相似错误日志合并后报警,减少报警骚扰

 

应用场景

 

对日志类别难以依靠人工划分,配置日志分类规则困难,希望用算法自动分类

 

日志量级大、报警多,希望对相似的异常合并后再进行报警

 

日志格式多样,程序需要对无规则的日志进行一定的归类后再进行分析,降低稳定定位难度 / 优化日志

 

分类算法简介

 

通过对业内日志聚类方案、以及相关分类算法的调研,我们选择了提取日志模板的方式,来对日志进行分类。

 

日志模板相关概念

 

在上面的例子中,日志 1 日志 2 分为同一个类,而模板表示在该类别下所有日志的相同部分( * 表示不同的部分)

 

通过上面日志模板效果示例,我们来看一看为什幺要采用模板的方式:

 

 

如果不采用模板,聚类后,呈现给我们的就是日志 1 一条(或者数条相似的日志),我们无法判断这些日志归为一类是否合理,这一类代表的是什幺

 

采用模板结构化后的日志,我们通过一条简单的日志 1 和模板, 可以判断将部分内容归为  *  是否合理。同时我们也能清晰的明白,这一类日志代表了什幺。比如上例中,我们知道这个模板下的所有日志,都包含非   *   的内容

 

 

其中的非 * 可以认为是结构化后的常量部分,而 * 则是变量部分。

 

Drain 算法

 

在众多的模板提取算法中,我们在对比了算法的鲁棒性、准确率、计算速度后,选择了 Drain 算法,并且该算法可增量学习,为线上实时分类提供了更多的可能

 

这里对该算法进行一个简单的介绍:

 

概念说明:

 

在 NLP、文本分析时,经常需要对原始文本进行切分,比如常用的按空格进行切分,每个 Word 切分为一个单位。在日志分类时,也需要对文本进行切分,我们提供了默认的切分规则,切分后的每个单位我们称之为 Token,不再用 Word 表示的原因是切分后的每个单位可能不再是一个 Word,甚至是一个句子。

 

比如 "这是一个测试用的句子;用来表示 Token 的意思" 这句文本,默认切分后会变成两个 Token, "这是一个测试用的句子""用来表示 Token 的意思"

 

Parse Tree 结构

 

 

算法是基于固定深度树去构造日志模板,其中根节点为 Root,所有非结构化日志从同一个 Root 分裂,第二层为长度层,最下面的叶子节点存储了分到该节点的所有模板。

 

每一个节点对应一个 Token,只有匹配了对应的 Token,日志才往对应的路径分。这里采用树结构主要起到一个提速的作用,树深 Depth 一般设为 4,也就是只匹配日志最前面两个 Token。(在节点的子节点数量达到设的阈值时,会增加一个 * ,此时会匹配所有未成功匹配的日志)

 

这样带了点先验知识,前面 Depth 个 Token 更有可能是模板,虽然很神奇,但效果还不错

 

算法流程

 

Step1:Preprocess,可选择用正则提前将部分词替换为 * ,以便提高模型的速度、准确率,将日志切分为一个个的词

 

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

 

Step3:根据日志 Token 按顺序去进行分裂,这里受到 Depth 限制,分裂树深为 Depth – 2(去除 Root, Length 层)

 

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

 

Step5:更新 Parse Tree,当日志在叶子节点匹配到了模板,并且部分 Token 有差异,则用 * 替换。当没有匹配到模板时,则将新的日志加入到该叶子节点的模板列表

 

其中 step3:

 

每个节点为一个词,并且这些词是按在日志中的顺序进行分裂的,比如 Receive from node 4,分裂时先判断 Receive,再到其子节点上去判断 From。

 

但是如果日志起始词为一个变量呢?那幺可能导致这个节点下的分支爆炸,因此这里会引入一个 maxChild 参数,用来限制一个 Node 的最大子节点数,当达到 maxChild 时,则会在该 Node 下生成一个 * 节点,日志没有匹配到其它 Token 时,则会匹配这个 *

 

其中 step4:

 

相似度计算,当将日志分到叶子节点后,将日志依次与各个模板计算相似度,然后从相似度大于 st(参数,最小相似度)的模板中,找到 * 最多的模板返回:

 

 

其中 seq_1 表示新日志, seq_2 表示模板,其中 i 表示 Token 的位置,n 表示日志(模板)的长度

 

Traceback 模式

 

在日志分类中有一类针对 Traceback 的特殊场景,即生成的日志主要是堆栈信息,程序需要根据这些反馈逐步去 Fix。

 

针对上述问题,对 Drain 算法进行了特殊的优化:

 

针对 Traceback 日志,使用另一套默认参数,算法模型参数适用性较高,针对性的调整后,能够对 Traceback 日志进行比较好的适配

 

调整数据预处理,对 Traceback 日志提供额外的 Token 切分规则

 

考虑到 Traceback 可能会抛出用户信息,而这个信息较长,因此限制 Traceback 每行的数据长度

 

当然业界也有相关的算法,比如 Rebucket 算法,Rebucket 对堆栈日志的相似度的计算会相对更合理,比如会考虑函数帧相对于于栈顶距离,然后设置不同的权重。

 

但是其问题也比较明显:一个是复杂度较高,计算效率相对来说比较差;另外对日志要求比较高,需要纯净的 Traceback 日志,而从真实日志中观察,堆栈日志并不一定纯净,比如进程信息就会加载在日志里面,如果没有经过专门的处理,会导致 Rebucket 算法失效

 

测试结果

 

通过对几个开源数据的测试,从下面测试结果可以发现分类准确率都是比较高

 

但是在实际分类中,效果与日志本身规范程度有关,我们在某个线上产品的实际日志做过测试,其中 error 级日志,66w+ 条可以准确分类为 139 类;critical 级日志,1.7w+ 条可以准确分为 20 类;traceback 日志,3 万 + 条可以准确分为 190 类。不同的日志压缩比会有一定的差异,比如 critical 日志,相当于缩减了 800 倍的报警骚扰,而 error 日志压缩了近 5000 倍

 

计算效率

 

算法时间复杂度为 O((d + cm) * n),其中 d:树深,c:叶子节点模板数量,m:日志长度,n:日志数量。其中 d, m 是常量,c 由于每个节点模板数量差不多,所以也可以认为是常量,即时间复杂度可以认为是 O(n)。

 

目前单核情况下,对某大型游戏日志数据的测试,Golang SDK 能达到大概 170K QPS。

 

案例介绍

 

下面以我最近一个实际的案例来介绍这个日志实时智能分析可以怎幺协助进行问题的定位和发现。

 

案例背景

 

在我们的业务场景中,需要开发一个盐值管理系统,系统特性如下:

 

高并发:实际业务场景按预估需要支持 10w 以上的 QPS

 

海量数据:需要存储每个 Key 和盐值之间的映射关系,数据量在 200+TB 级别

 

唯一性:对于同一个 Key,获取到的盐值必须是相同的

 

综合服务功能、性能、可维护性等多方面考虑,整体实现架构设计如下:

 

 

案例详情

 

然后我要对这个服务做压测。因为服务流量很高,就算我每个请求只有一条日志,我压测 1w QPS 的话也有 1w TPS 的日志流量了。当然,如果只是很少量的出错,我们直接结合全文检索引擎从日志里面找到对应那几条 error 日志就可以协助定位问题了,比如我只想看看 Mongo 一个 Client.Timeout 的异常日志:

 

 

但是我的测试出现了非常多的错误(30% 以上的请求出错),后端的错误日志一大堆,而且类型很多,很难找到具体是什幺问题(毕竟在你不知道异常是什幺的时候,日志检索一次性能展现的错误日志有限,很难快速找到问题点)。因此我配置了对错误日志的日子内容本身做实时日志智能分类,然后重新做了一遍压测,测试过程中就可以在实时分类统计页面里面看到下面这样的结果了:

 

 

红框内的 10.xx.xx.93 是我 Mongo 集群里面的某一个集群,可以看到出错的都是在这个集群的请求处理上,然后是 QueryOneByAppKey 方法的报错。基于这个信息,我就快速定位到了问题点:目前压测使用的 Mongo 配置不高,然后这个 QueryOneByAppKey 所有请求都会有一个 Mongo 查询是路由到这个集群的,导致了性能瓶颈,通过加一个缓存就快速解决了问题。

 

我这里使用的实时分类需要日志重流一遍才能找到问题,那如果我线上出现了类似的问题,但是之前没接实时智能分析的话,在我们的日志检索引擎 Logtail 里面也集成了在查询时进行分类的能力,直接在查询的结果里面再进行自动分类:

 

 

总的来说,日志实时智能分析结合我们 Loghub 日志基础服务体系,可以做到比较低成本地对日志进行实时智能分类分析,在面向未知问题的定位和发现的时候有极大的作用。

 

从后续规划来说,结合日志智能分类 + 异常检测,就可以比较好地解决对于未知问题的发现和异常流量监控,比如下面这样一个例子:

 

我想对不同类型的 Python Traceback 日志做自动分类监控,但是有一些是已知的我不希望报警出来,除非出现的量级突变

 

基于智能分类对 Traceback 日志进行分类统计,再结合异常检测动态监控日志的波动情况,就可以比较好在避免报警风暴的同时准确监控异常问题了。

Be First to Comment

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注