javascript
不错!SpringBoot发布Jar包优化瘦身指南!
概要說明
隨著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-level0 mvn?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-level1 mvn?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-starter-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-to-slf4j-2.13.3.jar?lib/log4j-api-2.13.3.jar?lib/jul-to-slf4j-1.7.30.jarlib/jakarta.annotation-api-1.3.5.jar?lib/spring-core-5.3.4.jar?lib/spring-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?lib/jackson-datatype-jsr310-2.11.4.jar?lib/jackson-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/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-level2 mvn?clean?installls?-lh?devops/ total?912 drwxr-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.jar?lib/spring-boot-starter-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-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/spring-core-5.3.4.jar?lib/spring-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?lib/jackson-datatype-jsr310-2.11.4.jar?lib/jackson-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/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-level3 mvn?clean?installls?-lh?devops/ total?912 drwxr-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變更文件,以此最小化傳輸文件。
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的不错!SpringBoot发布Jar包优化瘦身指南!的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: z字扫描和光栅扫描的转换_扫描转换计算机
- 下一篇: SpringBoot 如何统一后端返回格