优雅地解决Spark Application jar包冲突问题 2018-09-19 22:07:52 ## 前言 当我们开发的Spark Application变得越来越复杂,依赖的jar包越来越多时,难免会碰到jar包冲突的问题。 举个例子: 1. 我们的业务代码用到了一个第三方库,好比:guava(虽然好用,但是版本间的兼容性差的一坨翔) 2. Spark本身也依赖了guava,但是和业务代码中依赖的guava版本不同 这种情况下,把我们的Spark Application提交到集群里执行,很有可能因为版本问题导致运行出错。 大家都知道,JVM的ClassLoader加载类的时候,同一个ClassLoader加载的类,如果出现重复,只有第一个会被加载,后面重复的类会被忽略掉。 就我们的例子来说,整个Spark Application会优先加载Spark `jars`目录下的guava包,那么我们的业务代码自然很有可能受到影响了。 虽然Spark提供了一个`spark.driver.userClassPathFirst`配置,用来解决这个问题,但这个实验性的参数用起来比较鸡肋。首先只能应用于cluster模式,另外,设定为ture的时候还有可能会影响Spark本身的依赖。总之,不能很好地解决jar包冲突的问题。 接下来,我们探讨一种更加优雅的解决方案。 ## 对依赖包做shade处理 Java的一大优势,就是基于字节码,我们也可以动态修改字节码文件。我们可以将项目中依赖的jar包中的类名改掉。 还是以guava为例,guava包中的包名以`com.google.common.*`开头,我们将guava包及代码依赖中的包名全部改掉,如:`my_guava.common.*`,然后打包到一起,就可以解决包冲突的问题。这种处理的效果,看起来就像是我们不在依赖guava了,自然也就不会和Spark自带的guava包产生冲突了。 这种处理我们称之为:`shade`化。好在我们常用的包管理工具已经有了`shade`化的处理方案了。 ### 基于sbt构建的项目 修改项目目录的`project/plugins.sbt`,添加assembly插件`addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.5")` 然后修改`build.sbt`在项目配置中添加以下设置: ``` assemblyShadeRules in assembly := Seq( // 处理guava版本和spark自带guava包版本冲突问题 ShadeRule.rename("com.google.common.**" -> "my_guava.common.@1").inAll ) ``` > sbt的assembly插件会将项目中所有的依赖都打包到一起,通常情况下我们的集群中已经有Spark的部署包了,不需要把Spark的包也打进来。 > 我们在添加依赖的时候通过`provided`将其排除掉即可,如下: ``` libraryDependencies ++= Seq( "org.apache.spark" %% "spark-core" % "2.3.1" % "provided" ) ``` 最后执行`sbt assembly`打包就可以了。 ### 基于maven构建的项目 maven项目可以通过`maven-shade-plugin`插件,将有冲突的jar包`shade`化。 关键代码如下: ``` <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> ... <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.1.0</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <relocations> <relocation> <pattern>com.google.common</pattern> <shadedPattern>my_guava.common</shadedPattern> </relocation> </relocations> <filters> <filter> <artifact>*:*</artifact> <excludes> <exclude>META-INF/maven/**</exclude> </excludes> </filter> </filters> </configuration> </execution> </executions> </plugin> </plugins> </build> </project> ``` 最后通过`mvn package`打包项目就可以了。 ## 验证 为了确保,我们确实`shade`化成功了,可以通过`JD-GUI`工具将打好的jar包反编译,正常情况下应该看不到`com.google.common`开头的包,而是包含`my_guava.common`开头的的包。如下图所示: ![JD-GUI](/api/file/getImage?fileId=5ba2544cba8bc2481f0003fa) 验证没问题的话就可以安心地提交到集群运行了。 ## 结语 通过`shade`化第三方jar包,避免jar包版本冲突问题是个通用的解决方案,不仅适用于Spark Application,其他Java项目依然适用。 最近关注了下HBase 2.0,发现HBase也引入了shade机制,这样大家使用HBase时,就不用担心项目的第三方包跟HBase冲突的问题了。 相比之下Spark没有`shade`化,出现冲突问题,只能用户侧自己解决了😪。 非特殊说明,均为原创,原创文章,未经允许谢绝转载。 原始链接:优雅地解决Spark Application jar包冲突问题 赏 Prev HDFS双NameNode发生故障后HA无法切换的问题 Next 记一次HDFS报EOFException: End of File Exception异常的问题