标签档案:日志

Hadoop应用的根本原因分析

Parth Shah和Thai Bui

概述

Hadoop作业难以操作的原因之一是它们无法为用户提供清晰的、可操作的错误诊断消息。这源于Hadoop由许多相互关联的组件组成的事实。当组件发生故障或行为不佳时,故障将级联到其依赖组件,从而导致作业失败。

本文试图通过创建一个用户友好的、自助的、可操作的Hadoop诊断系统来帮助解决这个问题。

我们的目标

由于其复杂的性质,该项目被分为多个部分。首先,我们创建了一个诊断工具原型,通过提供清晰的根本原因分析和节省工程时间来帮助调试Hadoop应用程序故障。其次,我们有意地在集群上施加故障(通过一种称为混沌测试的方法),并收集数据以了解某些日志消息如何映射到错误。最后,我们研究了正则表达式和自然语言处理(NLP)技术,以便在生产中自动提供根本原因分析。

为了记录这一点,我们将博客分为以下几个部分:

  • 错误消息分析门户
    • 快速浏览一下已知的根本原因。
  • Datadog仪表板
    • 计算未知根本原因和已知根本原因相关的故障率。
    • 将基础设施故障与丢失的数据故障(丢失的分区)分开。
  • 数据访问
    • 来自Yarn、Oozie、HDFS、hive server等服务的所有相关日志消息都被收集并存储在一个具有过期策略的S3桶中。
  • 混沌数据生成
    • 使用混沌测试,我们产生了与内存、网络等相关的实际错误。这样做是为了了解日志消息和根本原因错误之间的关系。
    • 创建了一个服务,以创建一种高效而简单的方式来运行混沌测试并收集其相应/相关的日志数据。
  • 诊断消息分类
    • 由于日志消息的简单和重复性质(低熵),我们构建了一个自然语言处理模型,对未知故障的特定错误类型进行分类。
    • 在Bazaarvoice的特定工作负载的混沌数据上测试模型

错误消息分析门户

Bazaarvoice为最终用户提供了一个管理分析应用程序的内部门户工具。下面的屏幕截图演示了“由于缺少数据而导致作业失败”的示例消息。此消息是通过使用堆栈跟踪的简单正则表达式捕获的。正则表达式之所以有效,是因为由于缺少数据,作业只有一种可能失败。



DataDog仪表板

什么是分区?

分区是一种基于日期、组件或其他类别将表划分为相关部分的策略。使用分区,可以更容易、更快地查询一部分数据。然而,在我们的设计中,作业正在查询的分区有时不可用,这将导致作业失败。下面的仪表板计算指标并跟踪由于不可用分区而失败的作业。

仪表板将失败的作业分类为分区失败(延迟/丢失数据)或未知失败(Hadoop应用程序失败)。我们的诊断工具试图找到未知故障的根本原因,因为数据延迟或丢失是一个很容易解决的问题。



数据访问

由于我们的集群是由Apache Ambari提供支持的,因此我们利用并增强了Ambari Logsearch Logfeeder,将相关服务的日志直接发送到S3,并按照下面目录树图中的raw_log对数据进行了分区。然而,随着数据集越来越大,分区不足以有效地查询数据。为了提高读取性能和迭代速度,后来将数据转换为ORC格式。



将JSON日志转换为ORC日志

DROP TABLE IF EXISTS创建外部表默认值。temp_table_orc(集群STRING,文件STRING, thread_name STRING,级别STRING, event_count INT, ip STRING,类型STRING,…s3a:/// ORC -log/${workflowYear}/${workflowMonth}/${workflowDay}/${workflowwhour}/';插入默认值。temp_table_orc SELECT * FROM bazaar_magpie_rook。rook_log WHERE year=${workflowYear} AND month=${workflowMonth} AND day=${workflowDay} AND hour=${workflowwhour};

根本原因诊断查询样例

SELECT t1.log_message, t1。logtime, t1。水平,t1。类型,t2。频率FROM (SELECT log_message, logtime, TYPE, LEVEL FROM (SELECT log_message, logtime, TYPE, LEVEL, row_number() over (partition BY log_message ORDER BY logtime) AS r FROM bazaar_magpie_rook。row_log WHERE cluster_name = 'cluster_name' AND DAY = 28 AND MONTH=06 AND LEVEL = 'ERROR') S WHERE S.r = 1) t1 LEFT JOIN (SELECT log_message, COUNT(log_message) AS Frequency FROM bazaar_magpie_rook. log WHERE cluster_name = 'cluster_name' AND DAY = 28 AND MONTH=06 AND LEVEL = 'ERROR')row_log WHERE cluster_name = 'cluster_name' AND DAY = 28 AND MONTH=06 AND LEVEL = 'ERROR' GROUP BY log_message) t2 ON t1.log_message = t2.log_message WHERE t1.log_messagelogtime BETWEEN '2019-06-28 13:05:00' AND '2019-06-28 13:30:00' ORDER BY t1。logtime LIMIT 400;

SELECT log_message, logtime, type FROM bazaar_magpie_rookrow_log WHERE level = 'ERROR' AND type != 'logsearch_feeder' AND logtime BETWEEN '2019-06-24 09:05:00' AND '2019-06-24 12:30:10' ORDER BY logtime LIMIT 1000;

SELECT log_message, COUNT(log_message) AS Frequency FROM bazaar_magpie_rook。log_log WHERE cluster_name = 'dev-blue-3' AND level = 'ERROR' AND logtime BETWEEN '2019-06-27 13:50:00' AND '2019-06-29 14:30:00' GROUP BY log_message ORDER BY COUNT(log_message) DESC LIMIT 10;

混沌数据生成

混沌数据的生成过程占了整个项目的很大一部分,也是最重要的一部分。它源于混沌测试的过程,在软件和基础设施上进行实验,以了解系统承受意外或动荡条件的能力。这种测试的概念是由Netflix在2011年首次提出的。下面的伪代码解释了它的工作原理。

通过特定集群上的API提交一个普通作业为该集群中的所有可测试节点(例如工作者节点)创建一个IP地址列表。一旦我们有了所有关联的节点,就向它们注入失败(压力测试内存或网络),而作业还没有完成,让作业运行。此时,作业要么失败,要么成功。停止所有压力测试收集作业的详细信息,如开始时间、结束时间、状态,以及最重要的相关压力测试类型(内存、packet_corruption等)。以JSON格式存储工作详细信息及其报告

工作报告示例

{"duration":"45分钟","nominalTime":"2018-10-27 07:00:00", "cost":0.7974499089253186, "downloadLinks":[], "errorMessage":"Error: "Error: " Main class [org.apache.oozie.action.hadoop. "Hive2Main],退出代码[2]","startTime":"2019-07-25 18:45:37", "stopTime":"2019-07-25 19:31:18","failedAction":"hive-action", "chaos_error":"packet_corruption", "workflowId":"0001998-190628043141230-oozie-oozi-W", "status":"KILLED"}

我们遵循相同的过程来生成不同类型的注入故障的混沌数据,例如:

  • 主机内存占用率过高
  • 包腐败
  • 包丢失
  • 容器上的高内存效用

虽然模型能够学习和分类的错误肯定更多,但出于原型设计的目的,我们将失败类型保留为2-3个类别。

诊断消息分类

在本节中,我们将探讨两种类型的错误分类方法,一个简单的正则表达式和监督学习。

用Regex短期解决方案

有许多方法可以分析不同模式的文本。其中一种方法称为正则表达式匹配(regex)。正则表达式是“表示搜索操作中要匹配的模式的特殊字符串”。regex的一个用途是查找关键字,如“partition”。当作业由于缺少分区而失败时,其错误消息通常如下所示:即使尝试了16次,也不是所有分区都可用”。这个特定情况的正则表达式是这样的\ W *((?我)分区(? -我))\ W *

正则表达式日志解析器可以很容易地识别这个错误,并采取必要的措施来修复这个问题。然而,当涉及到分析复杂的日志消息和堆栈跟踪时,regex的功能非常有限。当我们发现一个新的错误时,我们将不得不手动硬编码一个新的正则表达式来匹配新的错误类型,从长远来看,这是非常容易出错和乏味的。

由于Hadoop生态系统包括许多组件,因此可以产生许多日志消息的组合;简单的正则表达式解析器在这里很难工作,因为它不能处理不同句子之间的一般相似性。这就是自然语言处理的作用。

监督学习的长期解决方案

这个解决方案背后的思想是使用自然语言处理来处理日志消息,并使用监督学习来学习和分类错误。与正常语言相比,这个模型应该能很好地处理日志数据,因为机器日志更有结构,熵更低。你可以把熵看作是一组数据的非结构化或随机性的度量。由于英语的句子结构有时不合逻辑,相对于机器日志,英语往往具有高熵。另一方面,机器生成的日志数据非常重复,这使得建模和分类更容易。

我们的模型需要对日志进行预处理,这被称为标记化。标记化是将一组文本分解为单独的单词或标记的过程。接下来,使用Word2Vec在高维空间中建模它们之间的关系。Word2Vec是一种广泛流行的模型,用于学习单词的向量表示,称为“单词嵌入”(word2vec).最后,我们使用向量表示来训练一个简单的逻辑回归分类器,使用之前生成的混沌数据。下图显示了类似的训练处理经验报告:基于自然语言处理的日志挖掘及其异常检测应用Christophe Bertero, Matthieu Roy, Carla Sauvanaud和Gilles Tredan。



与上图相反,由于我们只在错误日志上训练分类器,因此不可能对正常系统进行分类,因为它不应该产生错误日志。相反,我们在不同类型的压力系统上训练数据。使用混沌测试生成的数据集,我们能够确定每个失败作业的每个错误消息的根本原因。这使我们能够使我们的训练数据集如下所示。我们将数据集分成简单的70%用于训练,30%用于测试。

随后,对每个日志消息进行标记以创建一个词向量。所有的标记都被输入到预训练的word2vec模型中,该模型将每个单词映射到向量空间中,创建单词嵌入。每一行表示为一个向量列表,日志消息中所有向量的平均值表示高维空间中的特征向量。然后,我们将每个特征向量及其标签输入到分类算法中,如逻辑回归或随机森林,以创建一个可以从对数线预测根本原因错误的模型。然而,由于失败通常包含多个错误日志消息,因此仅仅从一个长错误日志中的一行日志中得出根本原因的结论是不符合逻辑的。解决这个问题的一个简单方法是将日志窗口化,并将窗口中的各个行输入到模型中,并将最终错误作为窗口中所有行输出的最常见错误输出。这是一种非常幼稚的分离长错误日志的方法,因此必须进行更多的研究来处理错误日志,同时不失去对其相关消息的有价值的见解。

下面是错误日志消息及其标记版本的几个示例。

使用实例原始日志

java.lang.RuntimeException: org.apache.thrift.transport.TSaslTransportException:没有数据或没有sasl数据流org.apache.thrift.transport.TSaslServerTransport Factory.getTransport美元(TSaslServerTransport.java: 219)美元org.apache.thrift.server.TThreadPoolServer WorkerProcess.run (TThreadPoolServer.java: 269) java.util.concurrent.ThreadPoolExecutor.runWorker (ThreadPoolExecutor.java: 1149)美元java.util.concurrent.ThreadPoolExecutor Worker.run (ThreadPoolExecutor.java: 624) java.lang.Thread.run (Thread.java: 748)[: 1.8.0_191]引起的:org.apache.thrift.transport.TSaslTransport.open(TSaslTransport.java:328) at org.apache.thrift.transport.TSaslServerTransport.open(TSaslServerTransport.java:41) ~[hive- exc -3.1.0.3.1.0.0-78.jar:3.1.0-SNAPSHOT] at org.apache.thrift.transport.TSaslServerTransport$Factory.getTransport(TSaslServerTransport.java:216)

标记化原始日志

[java,lang,RuntimeException,org,apache,thrift,transport,TSaslTransportException,No,data, No, sasl,data,stream,org,apache,thrift,transport,TSaslServerTransport,Factory,getTransport,TSaslServerTransport,java219] [org,apache,thrift,server,TThreadPoolExecutor,WorkerProcess,run,TThreadPoolServer,java,269]…

结果

该模型准确地预测了作业失败的根本原因99.3%我们测试数据集的准确性。乍一看,根据测试数据计算的指标看起来很有希望。然而,我们仍然需要评估其在生产中的效率,以获得更准确的情况。这个概念证明的初步成功证明了使用NLP对错误进行分类的进一步实验、测试和研究。

训练数据70%(前20行)

测试数据结果30%(前20行)

结论

为了在生产中实现这个工具,数据工程师必须将数据聚合和模型构建管道的某些方面自动化

自我报告错误

机器学习模型的好坏取决于它的数据。为了使这个工具健壮和准确,每当工程师遇到一个新的错误或一个模型不知道的错误时,他们应该报告他们对根本原因的信念,以便相应的错误日志被标记为特定的根本原因错误。例如,当一个作业由于类似的原因失败时,该模型将能够诊断并分类其错误类型。这可以通过一个简单的API、表单、Hive查询来完成,该查询包含作业id及其假设的root_cause。背后的想法是,通过创建一个表单,我们可以手动标记日志消息和错误,以防分类器无法正确诊断真正的问题。

自我报告的错误应该采取混沌错误的形式,以确保现有的管道能够正常工作。

自动化混沌数据生成

混沌测试应该定期进行,以保持模型的更新。这可以通过创建一个按常规节奏自动运行的Jenkins作业来实现。通常情况下,运行压力测试会使某些节点不健康。它导致我们的自动化无法再次SSH到节点以停止压力测试,例如当压力测试干扰到节点的连接时(参见下面的错误)。这可以通过为压力测试创建时间限制来解决,这样脚本就不必在任务完成后再次ssh。从长远来看,随着自报告误差的增长,模型应该减少对混沌测试的依赖。混沌测试为非常极端的情况生成日志,这可能不是正常生产环境的典型情况。

java.lang.RuntimeException:无法设置本地端口转发到10.8.100.144:22 at com. bazaarvoice.rook.infra.uti.remote.gateway .connect(Gateway.java:103) at com. bazaarvoice.rook.infra.regress.yarn.rootcausetest .kill_memory_stress s_test(RootCauseTest.java:174)