本文截取代码片段来自于对应的残缺示例源码工程:
- https://gitee.com/xautlx/pack...
- https://github.com/xautlx/pac...
相干代码和配置均理论执行测试过,如在验证过程发现有任何问题可Issue反馈以便及时更正,感激反对!
概要阐明
随着Spring Boot的风行,大家体验到只需构建输入一个jar文件,而后只需一个java -jar命令就能部署运行利用的痛快。常见一些单体利用随着我的项目规模的扩大单个jar文件的大小越来越大,动辄两三百MB。如果再引入微服务架构,动辄一二十个微服务,所有模块jar加起来整个零碎光部署文件就一两个GB。
一个零碎一旦上线运行,无论新需要迭代还是Bug修复,免不了须要做部署更新,尤其对于一些交付类型我的项目,首次部署或异地更新, 动不动就须要传输几百MB或几个GB的部署文件,的确是一个让人头疼的问题。
能够设想一下,线上零碎发现一个紧急重大Bug捅到了主管那里,交代马上紧急修复解决,研发共事火速剖析排查分分钟搞定提交代码并实现构建打包并交付给运维。过一会领导焦急上火来过问问题更新解决了吗?运维只能很难堪的答复:还没呢,部署包文件比拟大,正在上传有点慢…
一听领导就火了,就改了几行代码,部署更新为啥要上传几百MB的文件呢?难道没有方法优化一下吗?
遇到这样的状况,倡议你往下看,或者能找到你想要的答案。
本文内容包含:
- 如何把一两百MB的繁多Spring Boot jar文件,拆散为依赖组件lib目录和一个业务jar来进行部署,优化单个jar文件大小到一两百KB。。
- 如何把一二十个微服务高度重叠的依赖组件合并到繁多lib目录和多个一两百KB的业务jar来进行部署,优化整个我的项目部署文件大小从一两个GB大小到两三百MB。
本文内容不包含:
- 不包含进行Spring Boot配置文件拆散相干,个别简略采纳通过指定active profile从内部yaml配置文件笼罩jar文件中配置即可或是采纳Nacos等配置服务模式。
- 不包含Maven最佳实际用法,列入样例工程中出于演示不便的思考比方把一些本应放到各个Boot模块特定的配置申明间接放到顶层的parent中定义,请留神按理论状况优化调整应用。
- 不包含可执行jar的运行模式反对参考,文中实现形式次要面向java -jar运行模式。
瘦身打怪降级过程
Level 0:惯例的Fat Jar构建
参考我的项目目录:package-optimize-level0
次要配置:
<build> <finalName>${project.artifactId}</finalName> <!-- 特地留神: 我的项目仅仅是为了演示配置不便,间接在parent的build局部做了插件配置和运行定义。 然而理论我的项目中须要把这些定义只放到spring boot模块我的项目(可优化应用pluginManagement模式),防止烦扰其余util、common等模块我的项目 --> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins></build>
配置输入:
cd package-optimize-level0mvn clean installls -lh package-optimize-app1/target/package-optimize-app1.jar-rw-r--r-- 1 lixia wheel 16M Feb 24 21:06 package-optimize-app1/target/package-optimize-app1.jarjava -jar package-optimize-app1/target/package-optimize-app1.jar
重点阐明:
- (以后演示利用仅依赖了spring-boot-starter-web极少组件,所有构建输入只有十来MB)理论状况繁多构建依据我的项目依赖组件量输入jar个别在几十MB到一两百MB甚至更大。
- 如果有十来个微服务须要部署,那就意味着须要传输一两个GB的文件,耗时可想而知。就算是繁多更新个别微服务也须要传输一两百MB。
Level 1:常见的依赖jar拆散构建形式
参考我的项目目录:package-optimize-level1
关解决问题:
升高单个微服务jar的文件大小,以便部署过程秒传文件。
次要配置:
重点配置阐明请详见如下正文阐明:
<build> <finalName>${project.artifactId}</finalName> <!-- 特地留神: 我的项目仅仅是为了演示配置不便,间接在parent的build局部做了插件配置和运行定义。 然而理论我的项目中须要把这些定义只放到spring boot模块我的项目(可优化应用pluginManagement模式),防止烦扰其余util、common等模块我的项目 --> <plugins> <!-- 拷贝我的项目所有依赖jar文件到构建lib目录下 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <executions> <execution> <id>copy-dependencies</id> <phase>package</phase> <goals> <goal>copy-dependencies</goal> </goals> <configuration> <outputDirectory>${project.build.directory}/lib</outputDirectory> <excludeTransitive>false</excludeTransitive> <stripVersion>false</stripVersion> <silent>true</silent> </configuration> </execution> </executions> </plugin> <!-- Spring Boot模块jar构建 --> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <includes> <!-- 不存在的include援用,相当于排除所有maven依赖jar,没有任何三方jar文件打入输入jar --> <include> <groupId>null</groupId> <artifactId>null</artifactId> </include> </includes> <layout>ZIP</layout> </configuration> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> </plugins></build>
配置输入:
cd package-optimize-level1mvn clean installls -lh package-optimize-app1/target/package-optimize-app1.jar-rw-r--r-- 1 lixia wheel 149K Feb 24 20:56 package-optimize-app1/target/package-optimize-app1.jarjava -jar -Djava.ext.dirs=lib package-optimize-app1/target/package-optimize-app1.jar
实现成果:
- 繁多构建依据我的项目依赖组件量输入jar个别仅有一两百KB,根本能够做到秒传。
- 这个是网上可见最常见的优化计划,还值得持续深刻:如果有十来个微服务,每个服务一个jar和一个lib目录文件,首次部署也差不多须要传输一两个GB文件。
Level 2:合并所有模块依赖jar到同一个lib目录
参考我的项目目录:package-optimize-level2
解决问题:
- 合并所有模块依赖jar到同一个lib目录,个别因为各模块我的项目依赖jar重叠水平很高,合并所有服务部署文件总计大小根本也就两三百MB
- 然而如果采纳-Djava.ext.dirs=lib加载所有jar到每个JVM,一来每个JVM都残缺加载了所有jar消耗资源,二来各微服务组件版本不同会呈现版本抵触问题
次要配置:
重点配置阐明请详见如下正文阐明:
<build> <finalName>${project.artifactId}</finalName> <!-- 特地留神: 我的项目仅仅是为了演示配置不便,间接在parent的build局部做了插件配置和运行定义。 然而理论我的项目中须要把这些定义只放到spring boot模块我的项目(可优化应用pluginManagement模式),防止烦扰其余util、common等模块我的项目 --> <plugins> <!-- 基于maven-jar-plugin插件实现把依赖jar定义写入输入jar的META-INFO/MANIFEST文件 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <configuration> <archive> <manifest> <addClasspath>true</addClasspath> <classpathPrefix>lib/</classpathPrefix> <useUniqueVersions>false</useUniqueVersions> </manifest> </archive> </configuration> </plugin> <!-- 拷贝我的项目所有依赖jar文件到构建lib目录下 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <executions> <execution> <id>copy-dependencies</id> <phase>package</phase> <goals> <goal>copy-dependencies</goal> </goals> <configuration> <!-- 各子模块依照理论层级定义各模块对应的属性值,查看所有微服务模块依赖jar文件合并复制到同一个目录 详见各子模块中 boot-jar-output 属性定义 --> <outputDirectory>${boot-jar-output}/lib</outputDirectory> <excludeTransitive>false</excludeTransitive> <stripVersion>false</stripVersion> <silent>false</silent> </configuration> </execution> </executions> </plugin> <!-- Spring Boot模块jar构建 --> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <includes> <!-- 不存在的include援用,相当于排除所有maven依赖jar,没有任何三方jar文件打入输入jar --> <include> <groupId>null</groupId> <artifactId>null</artifactId> </include> </includes> <layout>ZIP</layout> <!-- 基于maven-jar-plugin输入微服务jar文件进行二次spring boot从新打包文件的输入目录 所有微服务构建输入jar文件对立输入到与lib同一个目录,便于独特援用同一个lib目录 详见各子模块中boot-jar-output属性定义 --> <!-- --> <outputDirectory>${boot-jar-output}</outputDirectory> </configuration> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> </plugins></build>
所有lib目录文件及各微服务构建jar聚合到devops公共目录。
微服务jar文件中的META-INFO/MANIFEST文件中会生成依据模块依赖组件列表的Class-Path属性, 从而防止了不同版本jar:
Class-Path: lib/spring-boot-starter-web-2.4.3.jar lib/spring-boot-starte r-2.4.3.jar lib/spring-boot-2.4.3.jar lib/spring-boot-autoconfigure-2.4 .3.jar lib/spring-boot-starter-logging-2.4.3.jar lib/logback-classic-1. 2.3.jar lib/logback-core-1.2.3.jar lib/slf4j-api-1.7.30.jar lib/log4j-t o-slf4j-2.13.3.jar lib/log4j-api-2.13.3.jar lib/jul-to-slf4j-1.7.30.jar lib/jakarta.annotation-api-1.3.5.jar lib/spring-core-5.3.4.jar lib/spr ing-jcl-5.3.4.jar lib/snakeyaml-1.27.jar lib/spring-boot-starter-json-2 .4.3.jar lib/jackson-databind-2.11.4.jar lib/jackson-annotations-2.11.4 .jar lib/jackson-core-2.11.4.jar lib/jackson-datatype-jdk8-2.11.4.jar l ib/jackson-datatype-jsr310-2.11.4.jar lib/jackson-module-parameter-name s-2.11.4.jar lib/spring-boot-starter-tomcat-2.4.3.jar lib/tomcat-embed- core-9.0.43.jar lib/jakarta.el-3.0.3.jar lib/tomcat-embed-websocket-9.0 .43.jar lib/spring-web-5.3.4.jar lib/spring-beans-5.3.4.jar lib/spring- webmvc-5.3.4.jar lib/spring-aop-5.3.4.jar lib/spring-context-5.3.4.jar lib/spring-expression-5.3.4.jar
配置输入:
cd package-optimize-level2mvn clean installls -lh devops/total 912drwxr-xr-x 34 lixia wheel 1.1K Feb 24 22:27 lib-rw-r--r-- 1 lixia wheel 150K Feb 24 22:31 package-optimize-app1.jar-rw-r--r-- 1 lixia wheel 149K Feb 24 22:31 package-optimize-app2.jar-rw-r--r-- 1 lixia wheel 149K Feb 24 22:31 package-optimize-app3.jarjava -jar devops/package-optimize-app1.jar
实现成果:
- 启动过程不再须要 -Djava.ext.dirs=lib 参数定义。
- 所有微服务jar援用所有我的项目合并依赖组件的公共目录,部署文件总计大小个别在两三百MB。
- 通过定制每个微服务jar文件中的META-INFO/MANIFEST文件中的Class-Path明确指明依赖版本组件类,解决各微服务不同组件版本抵触问题。
Level 3:反对system引入的非官方的三方依赖组件
参考我的项目目录:package-optimize-level3
解决问题:
- 有些非官方三方的诸如sdk jar,一种做法是提交到Maven本地私服中去援用,那和一般依赖jar解决雷同;然而在没有maven私服的状况下,常见的简化做法都是间接在我的项目中搁置依赖jar而后在pom中以system scope形式定义。
- 对于在pom中是以systemPath形式引入的,maven-jar-plugin组件没有间接参数申明蕴含指定scope的组件, 如果不做非凡解决META-INFO/MANIFEST中不会呈现这些scope定义的组件,导致运行时类找不到。
次要配置:
重点配置阐明请详见如下正文阐明:
<build> <finalName>${project.artifactId}</finalName> <!-- 特地留神: 我的项目仅仅是为了演示配置不便,间接在parent的build局部做了插件配置和运行定义。 然而理论我的项目中须要把这些定义只放到spring boot模块我的项目(可优化应用pluginManagement模式),防止烦扰其余util、common等模块我的项目 --> <plugins> <!-- 基于maven-jar-plugin插件实现把依赖jar定义写入输入jar的META-INFO/MANIFEST文件 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <configuration> <archive> <manifest> <addClasspath>true</addClasspath> <classpathPrefix>lib/</classpathPrefix> <useUniqueVersions>false</useUniqueVersions> </manifest> <manifestEntries> <!-- 有些非官方三方的诸如sdk jar在pom中是以systemPath形式引入的,maven-jar-plugin组件没有间接参数申明蕴含指定scope的组件 通过应用额定定义 Class-Path 值来追加指定依赖组件列表,在子模块按理论状况指定 jar-manifestEntries-classpath 值即可 例如(留神后面个点字符及各空格分隔符):. lib/xxx-1.0.0.jar lib/yyy-2.0.0.jar 详见各子模块中 boot-jar-output 属性定义示例 --> <Class-Path>${jar-manifestEntries-classpath}</Class-Path> </manifestEntries> </archive> </configuration> </plugin> <!-- 拷贝我的项目所有依赖jar文件到构建lib目录下 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <executions> <execution> <id>copy-dependencies</id> <phase>package</phase> <goals> <goal>copy-dependencies</goal> </goals> <configuration> <!-- 各子模块依照理论层级定义各模块对应的属性值,查看所有微服务模块依赖jar文件合并复制到同一个目录 详见各子模块中 boot-jar-output 属性定义 --> <outputDirectory>${boot-jar-output}/lib</outputDirectory> <excludeTransitive>false</excludeTransitive> <stripVersion>false</stripVersion> <silent>false</silent> </configuration> </execution> </executions> </plugin> <!-- Spring Boot模块jar构建 --> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <includes> <!-- 不存在的include援用,相当于排除所有maven依赖jar,没有任何三方jar文件打入输入jar --> <include> <groupId>null</groupId> <artifactId>null</artifactId> </include> </includes> <layout>ZIP</layout> <!-- 基于maven-jar-plugin输入微服务jar文件进行二次spring boot从新打包文件的输入目录 所有微服务构建输入jar文件对立输入到与lib同一个目录,便于独特援用同一个lib目录 详见各子模块中boot-jar-output属性定义 --> <!-- --> <outputDirectory>${boot-jar-output}</outputDirectory> </configuration> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> </plugins></build>
子模块次要配置:
<properties> <!-- 按各模块理论目录档次定义绝对数据,使所有服务模块输入资源汇聚到雷同目录 --> <boot-jar-output>../devops</boot-jar-output> <!-- 有些供应商的sdk jar在pom中是以systemPath形式引入的,maven-jar-plugin组件没有间接参数申明蕴含指定scope的组件 通过应用额定定义 Class-Path 值来追加指定依赖组件列表,按理论状况指定 jar-manifestEntries-classpath 值即可 例如(留神后面个点字符及各空格分隔符,lib前面局部是 artifactId-version.jar 格局而不是理论文件名):. lib/xxx-1.0.0.jar lib/yyy-2.0.0.jar --> <jar-manifestEntries-classpath>. lib/hik-sdk-1.0.0.jar</jar-manifestEntries-classpath> </properties> <dependencies> <!-- 以相对路径形式定义非官方三方依赖组件 --> <dependency> <groupId>com.hik</groupId> <artifactId>hik-sdk</artifactId> <version>1.0.0</version> <scope>system</scope> <systemPath>${project.basedir}/lib/hik-sdk-1.0.0.jar</systemPath> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>
微服务输入jar文件中的META-INFO/MANIFEST文件中会生成依据模块依赖组件列表的Class-Path属性, 最后面会追加 jar-manifestEntries-classpath 属性定义值:
Class-Path: . lib/hik-sdk-1.0.0.jar lib/spring-boot-starter-web-2.4.3.ja r lib/spring-boot-starter-2.4.3.jar lib/spring-boot-2.4.3.jar lib/sprin g-boot-autoconfigure-2.4.3.jar lib/spring-boot-starter-logging-2.4.3.ja r lib/logback-classic-1.2.3.jar lib/logback-core-1.2.3.jar lib/slf4j-ap i-1.7.30.jar lib/log4j-to-slf4j-2.13.3.jar lib/log4j-api-2.13.3.jar lib /jul-to-slf4j-1.7.30.jar lib/jakarta.annotation-api-1.3.5.jar lib/sprin g-core-5.3.4.jar lib/spring-jcl-5.3.4.jar lib/snakeyaml-1.27.jar lib/sp ring-boot-starter-json-2.4.3.jar lib/jackson-databind-2.11.4.jar lib/ja ckson-annotations-2.11.4.jar lib/jackson-core-2.11.4.jar lib/jackson-da tatype-jdk8-2.11.4.jar lib/jackson-datatype-jsr310-2.11.4.jar lib/jacks on-module-parameter-names-2.11.4.jar lib/spring-boot-starter-tomcat-2.4 .3.jar lib/tomcat-embed-core-9.0.43.jar lib/jakarta.el-3.0.3.jar lib/to mcat-embed-websocket-9.0.43.jar lib/spring-web-5.3.4.jar lib/spring-bea ns-5.3.4.jar lib/spring-webmvc-5.3.4.jar lib/spring-aop-5.3.4.jar lib/s pring-context-5.3.4.jar lib/spring-expression-5.3.4.jar
配置输入:
cd package-optimize-level3mvn clean installls -lh devops/total 912drwxr-xr-x 36 lixia wheel 1.1K Feb 24 23:14 lib-rw-r--r--@ 1 lixia wheel 150K Feb 24 23:14 package-optimize-app1.jar-rw-r--r-- 1 lixia wheel 150K Feb 24 23:14 package-optimize-app2.jar-rw-r--r-- 1 lixia wheel 150K Feb 24 23:14 package-optimize-app3.jarjava -jar devops/package-optimize-app1.jar
最终实现成果
- 所有服务的依赖组件合并到一个目录,总计大小在两三百MB,首次部署传输效率显著提速。
- 各微服务jar一两百KB大小,日常紧急修复Bug更新个别jar根本就是霎时秒传。
- 各微服务jar中各自定义依赖指定版本组件列表,不会呈现组件不同版本加载抵触问题。
- 非官方的三方依赖组件也能失常援用解决。
特地提醒
上述通过部署组件拆散解决后,日常更新只须要传输一两百KB的业务jar文件即可。然而如果某个我的项目的maven依赖组件做了变更配置,则须要留神把变更的jar文件要同步到公共的lib目录。
最小化变更jar文件的小技巧:能够把构建部署资源目录提交到GIT库,当前每次版本公布同时commit到GIT库, 通过提交视图能够清晰的辨认出lib目录下和业务jar本次版本公布的变更文件清单,包含微服务jar和依赖jar变更文件,以此最小化传输文件。