0%

java项目使用thin-jar分离依赖优化部署

项目打包时,直接用spring-boot maven plugin之类插件组装出一个包含了所有依赖的jar,即fat-jar的形式,可以直接启动,部署方便。但是并不是任何时候fat-jar都是最优解。

依赖文件分离打包在几种场景下的优化

稳定版本依赖包的分离对部署的优化

在项目开发中,依赖包我们期望是稳定的:版本的升级一定是审慎进行的,新依赖的加入也需要考量。也因此,在项目部署时,如果只打包源代码到一个thin-jar、将固定不变依赖包分离,打包得到的部署文件体积将大大减小——使用spring boot开发的项目打包成fat-jar百兆起步,但其实源代码打包的thin-jar体积很可能是KB级的。

尤其是在使用docker镜像部署jar时,对于镜像打包有优化:虽然对镜像体积减小不会有优化,但是可以将依赖文件置入单独的镜像层复用,镜像构建时多数情况下(依赖未变更的情况下)只需要重新构建源代码及它之上的层,构建速度将会得到提升。

不得不使用指定classpath的情况——依赖包面向不同OS/硬件架构

这里直接举一个例子

当我们集成luben zstd做文件压缩时,zstd的实现需要通过jni调用so/dll包,对不同OS/硬件架构需要引入不同的依赖包,此时启动项目时需要对不同的OS/硬件架构选择对应的依赖包

在很多开源项目中也可以看到,通过分离依赖包,可以根据部署情况替换依赖包版本。结合动态加载的机制,那么可以通过添加依赖jar扩展能力——比如hadoop扩展支持的shuffle实现、扩展支持的存储等

另一种情况:使用classpath引入线上依赖包

举一个例子,在大数据平台计算集群中,通常只需要客户端提供一个shade jar,而对于必须引用的hadoop依赖,通过线上环境的启动参数指定classpath引入的,这样对平台依赖的管理和升级方便。

依赖文件分离打包示例

这里给出一个简单的例子,演示如何使用maven plugin快速完成依赖分离打包和启动方式

maven默认的打包插件得到的是一个thin-jar,不过并没有自动的将依赖包归集到一起,可以使用如下配置来完成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97


<!-- other content in pom.xml -->
<!-- ....... -->

<dependencies>

<!-- your other dependencies -->
<!-- ....... -->

<!-- provided方式引入luben zstd依赖 -->
<dependency>
<groupId>com.github.luben</groupId>
<artifactId>zstd-jni</artifactId>
<version>1.5.4-2</version>
<!-- 在win_amd64平台开发 -->
<!--<classifier>win_amd64</classifier>-->
<scope>provided</scope>
</dependency>

</dependencies>


<build>
<!-- 引入必要依赖 -->
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.3.0</version>
</plugin>
</plugins>
</pluginManagement>
</build>

<profiles>
<profile>
<id>manual</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<build>
<plugins>
<!-- 将依赖包归集到构建输出路径下lib/下 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>copy-dependencies</goal>
</goals>
<phase>prepare-package</phase>
<configuration>
<includeScope>runtime</includeScope>
<outputDirectory>${project.build.directory}/lib
</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<!-- 将lib文件夹打包为tar、tar.gz、zip文件,方便部署 -->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<id>antrun-archive</id>
<goals>
<goal>run</goal>
</goals>
<phase>package</phase>
<configuration>
<target>
<property name="archive.includes" value="lib/*"/>
<property name="tar.destfile" value="${project.build.directory}/lib.tar"/>
<zip basedir="${project.build.directory}"
destfile="${project.build.directory}/lib.zip"
includes="${archive.includes}"/>
<tar basedir="${project.build.directory}" destfile="${tar.destfile}"
includes="${archive.includes}"/>
<gzip src="${tar.destfile}" destfile="${tar.destfile}.gz"/>
</target>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>

最终得到的target(默认构建输出路径)目录下结构为:

1
2
3
4
5
6
7
├─target
│ ├─lib # 依赖包目录
│ │ ├─x1.y1.z1.lib.jar
│ │ ├─x1.y1.z1.lib.jar
│ │ ├─...........
│ │
│ ├─lib-example.jar # 源代码打包得到的thin-jar

在linux上部署时,部署目录结构如下:

1
2
3
4
5
6
7
8
9
10
11
├─your_deploy_dir
│ ├─lib # 依赖包目录
│ │ ├─x1.y1.z1.lib.jar
│ │ ├─x1.y1.z1.lib.jar
│ │ ├─...........
│ │
│ ├─lib-example.jar # 源代码打包得到的thin-jar
│ │
│ ├─arch-lib # OS/硬件相关的依赖包的目录
│ │ ├─zstd-jni-linux_amd64.jar
│ │ ├─p.q.r-linux_amd64.jar

启动时通过指定classpath和mainClass启动:

1
2
3
cd your_deploy_dir/
# com.connorma.example.LibExampleMain为主类类名
java -classpath lib-example.jar:./lib/*:./arch-lib/* com.connorma.example.LibExampleMain