背景
最近接手了一個(gè) 2018 年的老項(xiàng)目,因?yàn)樘眠h(yuǎn)了,功能上的代碼不敢亂動(dòng),雖然是老項(xiàng)目,但最近一年也在持續(xù)加功能,功能不穩(wěn)定,于是我就進(jìn)入了救火式改 Bug 的狀態(tài)。
功能不能妄動(dòng),但是這個(gè)項(xiàng)目還有一個(gè)問(wèn)題,打包模塊打出的全量包部署不起來(lái)。拿到這個(gè)項(xiàng)目的部署包,400 多兆,網(wǎng)速慢的情況下,下載、上傳都得好半天。分析了一下部署包,決定先優(yōu)化一下,本文記錄這個(gè) Java 應(yīng)用的部署包優(yōu)化過(guò)程。
優(yōu)化主要是清理 Java 依賴,內(nèi)容有:
- 無(wú)用依賴
- 測(cè)試相關(guān)的依賴
- 相同 jar 的不同版本
- 有沖突的 jar
- 容器自帶、但是項(xiàng)目無(wú)用的包
- 第三方組件中的無(wú)用文件,如 docs、.cmd 、NOTICES、src 源碼等
無(wú)用依賴包
項(xiàng)目創(chuàng)建初期的 pom 文件大概是從別的舊項(xiàng)目拷貝過(guò)來(lái)的,沒(méi)有做過(guò)清理,里面有一些引用包但是工程中沒(méi)有用到的。比如 ,ftpserver-core、sshd-core,注釋掉這些引用后,項(xiàng)目編譯能通過(guò),打包后生成的 lib 包中也沒(méi)有這些模塊,說(shuō)明就是無(wú)用的,可以清理掉。
此外,項(xiàng)目初期引 jar 的時(shí)候,有必要搞明白引入的包是實(shí)現(xiàn)什么功能的,項(xiàng)目是否用得到。如果不確定能否用到,可以只在 maven 父工程的依賴管理中定義,子模塊需要的時(shí)候再引入。
測(cè)試相關(guān)的依賴包
maven 項(xiàng)目引入模塊時(shí),雖然 scope 設(shè)置為 test,但是打包的時(shí)候,這些 jar 還是會(huì)被加入到第三方依賴 lib 目錄下。所以在整理項(xiàng)目部署包的時(shí)候,需要手動(dòng)剔除掉各種測(cè)試相關(guān)的依賴包。
主要有 junit、自動(dòng)化測(cè)試框架、第三方測(cè)試工具類等,搜索出來(lái):
這些都可以清理掉。
相同 jar 的不同版本
部署包中存在一些名稱相同、版本號(hào)不一樣的 jar ,需要手動(dòng)清理。
比如 netty 的低版本和 netty-all 高版本,如果引用了 netty-all ,就可以清理掉 netty 低版本了,netty-moduleX 開頭的低版本=netty-all 高版本,都引入就存在冗余了:
還有 JDK 的 tools 包:
這些都是磁盤蛀蟲,項(xiàng)目部署包中沒(méi)有,而且兩個(gè)文件都是一樣的只是版本不同。 JDK 中已經(jīng)有了,如果真的要用,用 JDK/jre/ext 下的就可以了。
有沖突的包
Java 框架發(fā)展過(guò)程中,有一些相互沖突的包,是不應(yīng)該同時(shí)引入的。同時(shí)引入,而且能正常運(yùn)行,只能說(shuō)是幸運(yùn)。
比如,servlet-api-2.5.jar 和 javax.servlet-api-3.1.0.jar。servlet-api-2.5.jar 這個(gè)版本,可以直接清理掉。
容器引用但是項(xiàng)目完全無(wú)用的包
比如項(xiàng)目沒(méi)有用到 websocket 功能,但是使用的容器自帶了這些包:
清理掉,積少成多,能少則少!
多模塊公共 jar 共享
這個(gè)項(xiàng)目組件比較少,一個(gè)后臺(tái)、一個(gè)前端,但是兩個(gè)模塊有公共的 jar ,梳理出來(lái)后,公共包有幾十兆。而項(xiàng)目源碼包也兩個(gè)模塊共同的包,每次發(fā)布補(bǔ)丁的時(shí)候都要同時(shí)更新兩個(gè)組件的依賴。
所以,徹底的優(yōu)化方案是,對(duì)項(xiàng)目模塊的 jar 進(jìn)行分類,按當(dāng)前工程分為四個(gè) jar 包目錄:
- commonLib:所有模塊公共引用的包
- moduleALib:模塊 A 引用的包
- moduleBLib:模塊 B 引用的包
- dynamicLib:應(yīng)用中支持動(dòng)態(tài)上傳的包
計(jì)算模塊 A 和模塊 B 公共依賴的方法,用 Shell 腳本就可以完成:
進(jìn)入 moduleA 全量包目錄,ll|grep -v 總量|awk '{print $NF}' > /home/alib.log
進(jìn)入 moduleB 全量包目錄,ll|grep -v 總量|awk '{print $NF}' > /home/blib.log
file1="/home/alib.log" #第一個(gè)文件名
file2="/home/blib2.log" #第二個(gè)文件名
#通過(guò)comm命令獲取公共行
common_lines=$(comm -12 <(sort "$file1") <(sort "$file2"))
echo "$common_lines" > /home/commlib.log
計(jì)算出公共包后,就可以將模塊 A、B 全量包中的公共文件移除到公共目錄了
進(jìn)入 moduleA 全量包目錄,cat /home/commlib.log |xargs -I file mv file /home/commonlib
進(jìn)入 moduleB 全量包目錄,cat /home/commlib.log |xargs -I file mv file /home/commonlib
這樣就得到了整個(gè)應(yīng)用的最終依賴包:
整個(gè)應(yīng)用的依賴包放在一起集中管理,目錄清晰,更新方便。目錄結(jié)構(gòu)規(guī)劃好之后了,就需要優(yōu)化啟動(dòng)腳本了,應(yīng)用通過(guò) -cp 參數(shù)將依賴包目錄下所有的 jar 文件拼接起來(lái)、然后啟動(dòng)的,很多 Java 工程都是用這個(gè)方式啟動(dòng)的,比如 Kafka、IDEA 啟動(dòng)某個(gè)主類。
這種設(shè)置 Java 類路徑的方法,有一個(gè)大問(wèn)題,就是如果依賴包過(guò)多時(shí),進(jìn)程的啟動(dòng)命令會(huì)拼接的很長(zhǎng),比如上面這個(gè),一屏都看不到這個(gè)進(jìn)程的全貌。
有三種方法可以改善這個(gè)問(wèn)題:
- -cp 拼接路徑可以用通配符:
-cp /xx/lib/*:/lib/*
。 - -Djava.ext.dirs:這是普通 Java 應(yīng)用的參數(shù)。
- -Dloader.path:SpringBoot 引用的啟動(dòng)參數(shù)。
這個(gè)工程是原生的 SpringMVC 項(xiàng)目,嘗試了第二種方法,但是找不到主類,最終選擇了第一種方法。
修改應(yīng)用中組件 A、B 的啟動(dòng)腳本,將拼接 -cp
參數(shù)的部分直接改為當(dāng)前應(yīng)用部署包中 lib 目錄:
模塊 A 的啟動(dòng)腳本中拼接依賴的地方 moduleALib = moduleALib+commonLib
CLASSPATH=${APP_HOME}/lib/commonLib/*:${APP_HOME}/lib/moduleALib/*
同理修改模塊 B 的啟動(dòng)腳本。
第三方組件的無(wú)關(guān)文件
最后一點(diǎn)可以優(yōu)化的是第三方組件中的無(wú)關(guān)文件了,部署包中顯然用不上。
主要有:
- docs :組件說(shuō)明文檔。
- src :源碼。
- LICENCES 文件。
- NOTICES 文件。
- cmd 啟動(dòng)腳本,目標(biāo)是 Linux ,顯然用不到 cmd 腳本。
- tools ,一些用來(lái)調(diào)試的工具。
啟示錄
經(jīng)過(guò)這一些列的操作后,部署包從 400 多兆減少到了 178M,使用精簡(jiǎn)之后的部署包運(yùn)行時(shí),如果啟動(dòng)失敗,再排查缺什么 jar ,就加上。還是比較順利的,5輪報(bào)錯(cuò)后,程序就正常啟動(dòng)了。沒(méi)有表面的錯(cuò)誤,其他功能有沒(méi)有影響,還需要繼續(xù)觀察。
最后一步,以精簡(jiǎn)之后的目錄結(jié)構(gòu)調(diào)整打包腳本,保證項(xiàng)目源碼打出的全量包是可用的,順手寫一個(gè)補(bǔ)丁包打包模塊。這極大方便了部署包的準(zhǔn)備工作,按之前的流程,要拿到第一版的部署包,將項(xiàng)目打包出來(lái)的 6個(gè) jar ,逐個(gè)替換部署包對(duì)應(yīng)目錄的文件。讓工程的打包模塊真正能打包,能極大減少人工操作。
部署包優(yōu)化其實(shí)是個(gè)費(fèi)力不討好的事情,中途搞一半有點(diǎn)弄不下去了,擔(dān)心優(yōu)化過(guò)度后項(xiàng)目跑不起來(lái)了怎么辦!況且,項(xiàng)目源碼存在這么多年、經(jīng)手人都多少波了,也沒(méi)有人考慮過(guò)這種問(wèn)題,而且豪橫的項(xiàng)目組磁盤資源根本不是事兒,這點(diǎn)優(yōu)化是否有必要呢?
誰(shuí)讓我碰到了呢!作為一個(gè)還算有點(diǎn)工匠精神的超級(jí)熟練程序員,真的忍不了這些問(wèn)題。優(yōu)化還是有成效的,至少方便自己了,經(jīng)過(guò)一輪改造后,部署、發(fā)包就方便多了。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-811511.html
其實(shí)本文記錄的工作應(yīng)該是項(xiàng)目開發(fā)完成后,發(fā)布部署包時(shí)就應(yīng)該做的工作,雖然部署包越來(lái)越大是趨勢(shì),例如:Kafka 從第一個(gè)版本到最新版本,大小幾乎翻了一倍;隨便下一個(gè)應(yīng)用幾百兆。但也值得思考,我們發(fā)布的應(yīng)用是不是可以更緊湊呢,里面真的這個(gè)應(yīng)用需要的文件嗎?文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-811511.html
到了這里,關(guān)于Java 應(yīng)用部署包優(yōu)化經(jīng)驗(yàn)分享的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!