问题描述
在用springboot工程提交distcp.execute()
任务时,YARN报错如下:
org.apache.hadoop.yarn.exceptions.YarnRuntimeException: java.lang.RuntimeException: java.lang.ClassNotFoundException: Class com.xxx.hadoop.tools.mapred.CopyOutputFormat not found
at org.apache.hadoop.mapreduce.v2.app.MRAppMaster$2.call(MRAppMaster.java:518)
at org.apache.hadoop.mapreduce.v2.app.MRAppMaster$2.call(MRAppMaster.java:498)
at org.apache.hadoop.mapreduce.v2.app.MRAppMaster.callWithJobClassLoader(MRAppMaster.java:1593)
at org.apache.hadoop.mapreduce.v2.app.MRAppMaster.createOutputCommitter(MRAppMaster.java:498)
at org.apache.hadoop.mapreduce.v2.app.MRAppMaster.serviceInit(MRAppMaster.java:284)
at org.apache.hadoop.service.AbstractService.init(AbstractService.java:163)
at org.apache.hadoop.mapreduce.v2.app.MRAppMaster$5.run(MRAppMaster.java:1551)
at java.security.AccessController.doPrivileged(Native Method)
at javax.security.auth.Subject.doAs(Subject.java:422)
at org.apache.hadoop.security.UserGroupInformation.doAs(UserGroupInformation.java:1911)
at org.apache.hadoop.mapreduce.v2.app.MRAppMaster.initAndStartAppMaster(MRAppMaster.java:1548)
at org.apache.hadoop.mapreduce.v2.app.MRAppMaster.main(MRAppMaster.java:1481)
Caused by: java.lang.RuntimeException: java.lang.ClassNotFoundException: Class com.xxx.hadoop.tools.mapred.CopyOutputFormat not found
at org.apache.hadoop.conf.Configuration.getClass(Configuration.java:2195)
at org.apache.hadoop.mapreduce.task.JobContextImpl.getOutputFormatClass(JobContextImpl.java:223)
再次确保依赖包存在,并切换springboot版本(2.2.X换成1.5.19)后,还是无济于事。
解决方案
问题排查
通过将distcp中的job.setOutputFormatClass(CopyOutputFormat.class);
代码注释带掉,程序卡住;然后在YARN UI页面看到NM机器,去NM机器上找正在执行的job的jar包,发现jar被被重命名为job.jar,里面的内容为springboot全部内容(包含springboot目录结构,这是不合理的)。
springboot打成jar包为可执行jar包,改变了打包后的目录结构,其目录包含BOOT-INF/class等文件夹,这两层目录对于YARN来说是多余的。造成找不到class。
解决方案
1. 想办法改变springbootjar包的结构
网络上大多数的去掉springboot BOOT-INF
文件夹方法不可行。更改后,jar包不可执行。如下pom是不行的。
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
是否有办法更改springboot包结构,并可执行呢?暂未找到。
2. 将distcp依赖包打进jar包
通过jar uf xxx.jar file
将distcp包打进jar的根目录,测试,暂未生效。
3. 解决:打成war包
将springboot打成war,放置外置tomcat,问题解决。
springboot版本:1.5.19
<version>1.5.19.RELEASE</version>
jar包换war包设置:
pom.xml
//1
<packaging>war</packaging>
//2
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!--移除默认启动容器-->
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-tomcat</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- 使用外部容器-->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-servlet-api</artifactId>
<version>7.0.47</version>
<scope>provided</scope>
</dependency>
...
增加启动类(在原启动类同级目录)
package com.xxx.antyportal;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.support.SpringBootServletInitializer;
public class SpringBootStartApplication extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
// 注意这里要指向原先用main方法执行的Application启动类
return builder.sources(xxxPortalApplication.class);
}
}
vim job.jar
打成war后,放置tomcat,在任务运行时,及时查看job.jar
结构:
META-INF/
META-INF/MANIFEST.MF
com/
com/xxx/
com/xxx/hadoop/
com/xxx/hadoop/tools/
com/xxx/hadoop/tools/mapred/
com/xxx/hadoop/tools/mapred/lib/
com/xxx/hadoop/tools/util/
com/xxx/hadoop/tools/CopyListing$AclsNotSupportedException.class
com/xxx/hadoop/tools/CopyListing$DuplicateFileException.class
com/xxx/hadoop/tools/CopyListing$InvalidInputException.class
com/xxx/hadoop/tools/CopyListing$XAttrsNotSupportedException.class
com/xxx/hadoop/tools/CopyListing.class
...
com/xxx/hadoop/tools/util/ThrottledInputStream.class
distcp-default.xml
可以看到,正确的job.jar结构如上。并不是整个springboot 包,而是job需要的class文件(已经到Class级别了),并且根目录就是从com开始,不含包裹。
小结
springboot整合distcp出现找不到类的情况(或许其他MR任务也有类似问题),通过将jar包放置外部tomcat解决。后期尝试使用jar仍可访问的方案。