YARN环境中应用程序JAR包冲突问题的分析及解决

Hadoop框架自身集成了很多第三方的JAR包库。Hadoop框架自身启动或者在运行用户的MapReduce等应用程序时,会优先查找Hadoop预置的JAR包。这样的话,当用户的应用程序使用的第三方库已经存在于Hadoop框架的预置目录,但是两者的版本不同时,Hadoop会优先为应用程序加载Hadoop自身预置的JAR包,这种情况的结果是往往会导致应用程序无法正常运行。

下面从我们在实践中遇到的一个实际问题出发,剖析Hadoop on YARN 环境下,MapReduce程序运行时JAR包查找的相关原理,并给出解决JAR包冲突的思路和方法。

一、一个JAR包冲突的

我的一个MR程序需要使用jackson库1.9.13版本的新接口:

图1:MR的pom.xml,依赖jackson的1.9.13

但是我的Hadoop集群(CDH版本的hadoop-2.3.0-cdh5.1.0)预置的jackson版本是1.8.8的,位于Hadoop安装目录下的share/hadoop/mapreduce2/lib/下。

使用如下命令运行我的MR程序时:

hadoop jar mypackage-0.0.1-jar-with-dependencies.jar com.umeng.dp.MainClass --input=../input.pb.lzo --output=/tmp/cuiyang/output/

由于MR程序中使用的JsonNode.asText()方法,是1.9.13版本新加入的,在1.8.8版本中没有,所以报错如下:

15/11/13 18:14:33 INFO mapreduce.Job:  map 0% reduce 0%

15/11/13 18:14:40 INFO mapreduce.Job: Task Id : attempt_1444449356029_0022_m_000000_0, Status : FAILED

Error: org.codehaus.jackson.JsonNode.asText()Ljava/lang/String;

二、搞清YARN框架用程序的

在继续分析如何解决JAR包冲突问题前,我们需要先搞明白一个很重要的问题,就是用户的MR程序是如何在NodeManager上运行起来的?这是我们找出JAR包冲突问题的解决方法的关键。

本篇文章不是一篇介绍YARN框架的文章,一些基本的YARN的知识假定大家都已经知道,如ResourceManager(下面简称RM),NodeManager(下面简称NM),AppMaster(下面简称AM),Client,Container这5个最核心组件的功能及职责,以及它们之间的相互关系等等。

图2:YARN架构图

如果你对YARN的原理不是很了解也没有关系,不会影响下面文章的理解。我对后面的文章会用到的几个关键点知识做一个扼要的总结,明白这些关键点就可以了:

  1. 从逻辑角度来说,Container可以简单地理解为是一个运行Map Task或者Reduce Task的进程(当然了,AM其实也是一个Container,是由RM命令NM运行的),YARN为了抽象化不同的框架应用,设计了Container这个通用的概念;
  2. Container是由AM向NM发送命令进行启动的;
  3. Container其实是一个由Shell脚本启动的进程,脚本里面会执行Java程序,来运行Map Task或者Reduce Task。

好了,让我们开始讲解MR程序在NM上运行的过程。

上面说到,Map Task或者Reduce Task是由AM发送到指定NM上,并命令NM运行的。NM收到AM的命令后,会为每个Container建立一个本地目录,将程序文件及资源文件下载到NM的这个目录中,然后准备运行Task,其实就是准备启动一个Container。NM会为这个Container动态生成一个名字为launch_container.sh的脚本文件,然后执行这个脚本文件。这个文件就是让我们看清Container到底是如何运行的关键所在!

脚本内容中和本次问题相关的两行如下:

export CLASSPATH="$HADOOP_CONF_DIR:$HADOOP_COMMON_HOME/share/hadoop/common/*:(...省略…):$PWD/*"

exec /bin/bash -c "$JAVA_HOME/bin/java -D(各种Java参数) org.apache.hadoop.mapred.YarnChild 127.0.0.1 58888 (其他应用参数)"

先看第2行。原来,在YARN运行MapReduce时,每个Container就是一个普通的Java程序,Main程序入口类是:org.apache.hadoop.mapred.YarnChild

我们知道,JVM加载类的时候,会依据CLASSPATH中路径的声明顺序,依次寻找指定的类路径,直到找到第一个目标类即会返回,而不会再继续查找下去。也就是说,如果两个JAR包都有相同的类,那么谁声明在CLASSPATH前面,就会加载谁。这就是我们解决JAR包冲突的关键!

再看第1行,正好是定义JVM运行时需要的CLASSPATH变量。可以看到,YARN将Hadoop预置JAR包的目录都写在了CLASSPATH的最前面。这样,只要是Hadoop预置的JAR包中包含的类,就都会优先于应用的JAR包中具有相同类路径的类进行加载!

那对于应用中独有的类(即Hadoop没有预置的类),JVM是如何加载到的呢?看CLASSPATH变量定义的结尾部分:"/*:$PWD/*"。也就是说,如果Java类在其他地方都找不到的话,最后会在当前目录查找。

那当前目录究竟是什么目录呢?上面提到过,NM在运行Container前,会为Container建立一个单独的目录,然后会将所需要的资源放入这个目录,然后运行程序。这个目录就是存放Container所有相关资源、程序文件的目录,也就是launch_container.sh脚本运行时的当前目录。如果你执行程序的时候,传入了-libjars参数,那么指定的JAR文件,也会被拷贝到这个目录下。这样,JVM就可以通过CLASSPATH变量,查找当前目录下的所有JAR包,于是就可以加载用户自引用的JAR包了。

在我的电脑中运行一次应用时,该目录位于/Users/umeng/worktools/hadoop-2.3.0-cdh5.1.0/ops/tmp/hadoop-umeng/nm-local-dir/usercache/umeng/appcache/application_1444449356029_0023,内容如下(可以通过配置文件进行配置,从略):

图3:NM中Job运行时的目录

好了,我们现在已经知道了为何YARN总是加载Hadoop预置的class及JAR包,那我们如何解决这个问题呢?方法就是:看源码!找到动态生成launch_container.sh的地方,看是否可以调整CLASSPATH变量的生成顺序,将Job运行时的当前目录,调整到CLASSPATH的最前面。

三、阅读码, 解决问题

追溯源码,让我们深入其中,透彻一切。

首先想到,虽然launch_container.sh脚本文件是由NM生成的,但是NM只是运行Task的载体,而真正精确控制Container如何运行的,应该是程序的大脑:AppMaster。查看源码,果然验证了我们的想法:Container的CLASSPATH,是由MRApps(MapReduce的AM)传给NodeManager的,NodeManager再写到sh脚本中。

MRApps中的TaskAttemptImpl::createCommonContainerLaunchContext()方法会创建一个Container,之后这个Container会被序列化后直接传递给NM;这个方法的实现中,调用关系为:createContainerLaunchContext() -> getInitialClasspath() -> MRApps.setClasspath(env, conf)。首先,我们来看setClasspath()

首先,会判断userClassesTakesPrecedence,如果设置了这个Flag,那么就不会去调用MRApps.setMRFrameworkClasspath(environment, conf)这个方法。也就是说,如果设置了这个Flag的话,需要用户设置所有的JAR包的CLASSPATH。

下面看setMRFrameworkClasspath()方法:

其中,DEFAULT_YARN_APPLICATION_CLASSPATH里放入了所有Hadoop预置JAR包的目录。能够看到,框架会先用YarnConfiguration.YARN_APPLICATION_CLASSPATH设置的CLASSPATH,如果没有设置,则会使用DEFAULT_YARN_APPLICATION_CLASSPATH

然后由conf.getStrings()把配置字符串按逗号分隔转化为一个字符串数组;Hadoop遍历该数组,依次调用MRApps.addToEnvironment(environment, Environment.CLASSPATH.name(), c.trim(), conf)设置CLASSPATH。

看到这里,我们看到了一线曙光:默认情况下,MRApps会使用DEFAULT_YARN_APPLICATION_CLASSPATH作为Task的默认CLASSPATH。如果我们想改变CLASSPATH,那么看来我们就需要修改YARN_APPLICATION_CLASSPATH,让这个变量不为空。

于是,我们在应用程序中加入了如下语句:

String[] classpathArray = config.getStrings(YarnConfiguration.YARN_APPLICATION_CLASSPATH, YarnConfiguration.DEFAULT_YARN_APPLICATION_CLASSPATH);

String cp = "$PWD/*:" +  StringUtils.join(":", classpathArray);config.set(YarnConfiguration.YARN_APPLICATION_CLASSPATH, cp);

上面的语句意思是:先获得YARN默认的设置DEFAULT_YARN_APPLICATION_CLASSPATH,然后在开头加上Task程序运行的当前目录,然后一起设置给YARN_APPLICATION_CLASSPATH变量。这样,MRApps在创建Container时,就会将我们修改过的、程序当前目录优先的CLASSPATH,作为Container运行时的CLASSPATH。

最后一步,我们需要将我们的应用依赖的JAR包,放入到Task运行的目录中,这样加载类的时候,才能加载到我们真正需要的类。那如何做到呢?对,就是使用-libjars这个参数,这个前面也已经解释过了。这样,运行程序的命令就改为如下:

hadoop jar ./target/mypackage-0.0.1-SNAPSHOT-jar-with-dependencies.jar com.umeng.dp.MainClass -libjars jackson-mapper-asl-1.9.13.jar,jackson-core-asl-1.9.13.jar --input=../input.pb.lzo --output=/tmp/cuiyang/output/

四、结语

本文中,我们通过分析Hadoop的源代码,解决了我们遇到的一个JAR包冲突问题。

即使再成熟再完善的文档手册,也不可能涵盖其产品所有的细节以解答用户所有的问题,更何况是Hadoop这种非以盈利为目的的开源框架。而开源的好处就是,在你困惑的时候,你可以求助源码,自己找到问题的答案。这正如侯捷老师所说的: “源码面前,了无秘密”。

时间: 2024-09-27 01:03:06

YARN环境中应用程序JAR包冲突问题的分析及解决的相关文章

【转】Android中引入第三方Jar包的方法(java.lang.NoClassDefFoundError解决办法)

原文网址:http://www.blogjava.net/anchor110/articles/355699.html 1.在工程下新建lib文件夹,将需要的第三方包拷贝进来.2.将引用的第三方包,添加进工作的build path.3.(关键的一步)将lib设为源文件夹.如果不设置,则程序编译可以通过,但运行的时候,会报: java.lang.NoClassDefFoundError # re: Android中引入第三方Jar包的方法(java.lang.NoClassDefFoundErro

websphere找不到类或jar包冲突

Jar包冲突问题是在大型Java软件开发中经常遇到的问题,系统开发人员经常会为解决类似的问题耗费大量的时间进行调试和测试,本文根据各种际情况,结合WebSphere中类加载器,讨论了几种解决jar包冲突问题的办法,并给出了具体实现的步骤及源代码.读者定位为具有Java和WebSphere开发经验的开发人员.读者可以学习到在WebSphere中类加载器的定义以及解决jar包冲突问题的几种办法,并可以直接使用文章中提供的Java代码,从而节省他们的开发和调试时间,提高效率. 大型的基于WebSphe

解决Maven的jar包冲突

最近的一次项目引入了MongoDB,撸完代码,启动服务,Junit单元测试一跑,报错: java.lang.NoClassDefFoundError: org/springframework/core/DefaultParameterNameDiscoverer at org.springframework.data.mapping.model.PreferredConstructorDiscoverer.<clinit>(PreferredConstructorDiscoverer.java

共享库方案解决WAS中JAR包冲突

(一)证书导入解决方案 1.登录管理控制台. 2. 展开"安全性"并单击"SSL 证书和密钥管理".在"配置设置"下面,单击"管理端点安全配置". 3. 为 (cell):server5Cell01 管理作用域选择适当的出站配置. 4. 在"相关项目"下面,单击"密钥库和证书",然后单击 CellDefaultTrustStore 密钥库. 5.  在"其他属性"下面

解决EBS中JAR包冲突的问题

同事解决的,摘抄上来备用. 问题描述:在OAF里调用ESB的服务报错如下: Error Page Exception Details. oracle.apps.fnd.framework.OAException: oracle.jbo.JboException: JBO-29000: Unexpected exception caught: java.lang.IllegalAccessError, msg=tried to access class org.apache.commons.log

Maven的Pom文件中的隐式依赖导致Jar包冲突的问题

在一次的maven项目中遇到这样一个bug: 编译器没有报什么错,但无法编译,或者能编译,项目启动不了.后来我才发现是以下的问题: 项目中的pom文件中,依赖了webx3.core,而webx3.core又隐式依赖了fasttext相关的jar包,同时我在pom中也引人了fasttext.all, fasttext.all也隐式依赖了fasttext相关的jar包,两类jar包版本还不一样,这样就导致了jar包冲突的问题,牵扯到的pom文件依赖如下: <dependency><group

Maven中日志jar包冲突报错:Class path contains multiple SLF4J bindings

错误表现: SLF4J: Class path contains multiple SLF4J bindings.SLF4J: Found binding in [jar:file:/D:/learn/Java/maven/repository_taotao/org/slf4j/slf4j-log4j12/1.6.4/slf4j-log4j12-1.6.4.jar!/org/slf4j/impl/StaticLoggerBinder.class]SLF4J: Found binding in [

重新看待Jar包冲突问题及解决方案

Jar包冲突是老生常谈的问题,几乎每一个Java程序猿都不可避免地遇到过,并且也都能想到通常的原因一般是同一个Jar包由于maven传递依赖等原因被引进了多个不同的版本而导致,可采用依赖排除.依赖管理等常规方式来尝试解决该问题,但这些方式真正能彻底解决该冲突问题吗?答案是否定的.笔者之所以将文章题目起为"重新看待",是因为之前对于Jar包冲突问题的理解仅仅停留在前面所说的那些,直到在工作中遇到的一系列Jar包冲突问题后,才发现并不是那么简单,对该问题有了重新的认识,接下来本文将围绕Ja

Jar包冲突解决方法

前言 对于Jar包冲突问题,我们开发人员经常都会有碰到,当我们使用一些jar包中的类.方法等,或者有时遇到一些日志系统的问题(参考另一篇文章Jar包冲突导致的日志问题),我们会遇到ClassNotFoundException,NoSuchFieldException,NoSuchMethodException 之类的运行时异常,从经验上我们就会判断,Jar包冲突了.解决Jar包冲突问题,每个人都有每个人的方法,这里我介绍一下我的方法,供大家参考. 处理方法 当遇到jar包冲突时,我们首先确定是哪