如何在一個基座上安裝更多的 Koupleless 模塊?
文|梁櫟鵬(立蓬)
螞蟻集團技術工程師
雲原生領域工程師
就職於螞蟻集團中間件團隊,參與維護與建設螞蟻 SOFAArk 和 Koupleless 開源項目,參與內部 SOFAServerless 產品的研發和實踐。
本文2773字,預計閲讀 7 分鐘
本文屬於 Koupleless 進階系列文章第五篇,默認讀者對 Koupleless 的基礎概念、能力都已經瞭解。如果還未了解過的可以在文末「閲讀原文」查看官網 (https://koupleless.io/ )。
進階系列一:Koupleless 內核系列|模塊化隔離與共享帶來的收益與挑戰
進階系列二:Koupleless 單進程多應用如何解決兼容問題
進階系列三:Koupleless 內核系列 | 一台機器內 Koupleless 模塊數量的極限在哪裏?
進階系列四:Koupleless 可演進架構的設計與實踐|當我們談降本時,我們談些什麼
在往期文章中,我們已經介紹了 Koupleless 的收益、挑戰、應對方式及存量應用的改造成本,幫助大家瞭解到 Koupleless 是如何低成本地為業務研發提升效率和降低資源成本的。在實踐中,開發者可以將多個 Koupleless 模塊部署在同一個基座上,從而降低資源成本。那麼,如何在一個基座上安裝更多的模塊呢?
通常來説,有三種方式:模塊複用基座的類、模塊複用基座的對象、模塊卸載時清理資源。其中,最簡單、最直接、最有效的方式是讓模塊複用基座的類,即模塊瘦身。
所謂模塊瘦身,就是讓模塊複用基座所有依賴的類,模塊打包構建時移除基座已經有的 Jar 依賴,從而讓基座中可以安裝更多的模塊。
在最初的模塊瘦身實踐中,模塊開發者需要感知基座有哪些依賴,並且在開發時儘量使用這些依賴,從而複用基座依賴裏的類。其次,開發者需要根據基座所有依賴,判斷哪些依賴可以移除並進行手工移除。在依賴移除後,還可能會出現以下場景:
- 由於開發者誤判,移除了基座沒有的依賴,導致模塊編譯正常通過,而運行期出現❌ClassNotFound、LinkageError 等錯誤;
- 由於模塊依賴的版本和基座不同,導致模塊編譯正常通過,而運行期出現❌依賴版本不兼容的錯誤。
由此,引申出了 3 個關鍵問題:
- 模塊如何感知基座運行時的所有依賴,從而確定需要移除的依賴?
- 如何簡單地移除依賴,降低普通應用和模塊相互轉換的改造成本?
- 如何保證在移除模塊依賴後,模塊編譯時和模塊運行在基座中的依賴是一樣的?
下面,本文就將介紹模塊瘦身原理、原則,並針對以上三個關鍵問題給出解決方式。
模塊瘦身原理
Koupleless 底層藉助 SOFAArk 框架,實現了模塊與模塊之間、模塊和基座之間的類隔離。模塊啓動時會初始化各種對象,會優先使用模塊的類加載器去加載構建產物 FatJar 中的 class、resource 和 Jar 包,找不到的類會委託基座的類加載器去查找。
基於這套類委託的加載機制,讓基座和模塊共用的 class、resource 和 Jar 包通通下沉到基座中,可以讓模塊構建產物非常小,從而使模塊消耗的 Metaspace 非常少,基座上能安裝的模塊數量也更多,啓動也更快。
其次,模塊啓動後 Spring 上下文中會創建很多對象。如果啓用了模塊熱卸載,可能無法完全回收,且安裝次數過多會造成 Old 區、Metaspace 區開銷大,觸發頻繁 FullGC。所以需要控制單模塊包大小 < 5MB,這樣不替換或重啓基座也能熱部署熱卸載數百次。
在模塊瘦身後,能實現以下兩個好處:
- 允許基座安裝更多的模塊數量,從而在合併部署場景下,進一步降低資源成本;在熱部署熱卸載場景下,不替換或重啓基座就能熱部署、熱卸載模塊更多次。
- 提高模塊安裝的速度,減少模塊包大小,減少啓動依賴,控制模塊安裝耗時 < 30秒,甚至 < 5秒。
模塊瘦身原則
由上文模塊瘦身原理可知,模塊移除的依賴必須在基座中存在,否則模塊會在運行期間出現 ClassNotFound、LinkageError 等錯誤。
因此,模塊瘦身的原則是,在保證模塊功能的前提下,將框架、中間件等通用的依賴包儘量放置到基座中,模塊中複用基座的依賴。這樣打出的模塊包會更加輕量。如圖:
關鍵一:可感知的基座運行時
在基座和模塊協作緊密的情況下,模塊應該在開發時就感知基座正使用的所有依賴,並按需引入需要的依賴,而無需指定版本。為此,我們提供了 “基座-dependencies-starter” 的打包功能,該包在中記錄了基座當前所有運行時依賴的 GAV 座標 (GAV: GroupId、ArtifactId、Version)。打包方式非常簡單,在基座的打包插件中配置必要的參數即可:
<build>
<plugins>
<plugin>
<groupId>com.alipay.sofa.koupleless</groupId>
<artifactId>koupleless-base-build-plugin</artifactId>
<configuration>
<!-- ... -->
<!--生成 starter 的 artifactId(groupId和基座一致),這裏需要修改!!-->
<dependencyArtifactId>${baseAppName}-dependencies-starter</dependencyArtifactId>
<!--生成jar的版本號-->
<dependencyVersion>0.0.1-SNAPSHOT</dependencyVersion>
</configuration>
</plugin>
</plugins>
</build>
執行 mvn 命令:
mvn com.alipay.sofa.koupleless:koupleless-base-build-plugin::packageDependency -f ${基座 bootstrap pom 對於基座根目錄的相對路徑}
然後,模塊配置項目的 parent 為 “基座-dependencies-starter”。
<parent>
<groupId>com.alipay</groupId>
<artifactId>${baseAppName}-dependencies-starter</artifactId>
<version>0.0.1</version>
</parent>
這樣一來,在模塊的開發過程中,開發者就能感知到基座運行時的所有依賴。
關鍵二:低成本的模塊瘦身
在應用中,最簡單的移除依賴的方式是把依賴的 scope 設置為 provided。但這種方式會增加普通應用轉換為模塊的成本,同時也意味着,如果模塊要轉為普通應用,需要將這些依賴配置回 compile,改造成本較高。
為了降低模塊瘦身的成本,我們提供了兩種配置模塊瘦身的方式:基於 “基座-dependencies-starter” 自動瘦身和基於配置文件瘦身。
基於 “基座-dependencies-starter” 自動瘦身
我們提供了基於 “基座-dependencies-starter” 的自動瘦身,自動排除和基座相同的依賴(GAV 都相同),保留和基座不同的依賴。配置十分簡單,在模塊的打包插件中配置 baseDependencyParentIdentity 標識即可:
<build>
<plugins>
<plugin>
<groupId>com.alipay.sofa</groupId>
<artifactId>sofa-ark-maven-plugin</artifactId>
<configuration>
<!-- ... -->
<!-- 配置 “基座-dependencies-starter” 的標識,規範為:'${groupId}:${artifactId}' -->
<baseDependencyParentIdentity>${groupId}:${baseAppName}-dependencies-starter</baseDependencyParentIdentity>
</configuration>
</plugin>
</plugins>
</build>
基於配置文件瘦身
在配置文件中,模塊開發者可以主動配置需要排除哪些依賴,保留哪些依賴。
為了進一步降低配置成本,用户僅需配置需要排除的頂層依賴,打包插件會將該頂層依賴的所有間接依賴都排除,而無需手動配置所有的間接依賴。如:
# excludes config ${groupId}:{artifactId}:{version}, split by ','
excludes=org.apache.commons:commons-lang3,commons-beanutils:commons-beanutils
# excludeGroupIds config ${groupId}, split by ','
excludeGroupIds=org.springframework
# excludeArtifactIds config ${artifactId}, split by ','
excludeArtifactIds=sofa-ark-spi
關鍵三:保證瘦身的正確性
如果基座運行時沒有模塊被排除的依賴,或者基座運行時中提供的依賴版本和模塊預期不一致,那麼模塊運行過程中可能會報錯。為了保證瘦身的正確性,我們需要在模塊編譯和發佈的環節做檢查。
在模塊編譯時,模塊打包插件會檢查被瘦身的依賴是否在 “基座-dependencies-starter” 中,並在控制枱輸出檢查結果,但檢查結果不影響模塊的構建結果。同時,插件允許更嚴格的檢查:配置一定參數。如果基座中不存在模塊被排除的依賴,那麼模塊構建失敗,直接報錯。
在模塊發佈時,在發佈流程中拉取基座的運行時依賴,檢查是否和 “基座-dependencies-starter” 一致。如果不一致,那麼卡住發佈流程,開發者可根據情況去升級模塊的 “基座-dependencies-starter” 或跳過該卡點。
模塊瘦身效果
以某個依賴了 16 箇中間件的模塊為例,將模塊的 parent 配置為 “基座-dependencies-starter” 自動瘦身,下表是瘦身前後的 ark-biz.jar 大小和 Metaspace 佔用的對比:
總結
通過上文相信大家已經瞭解,我們可以通過簡單的配置,讓模塊打包更小,從而在一個基座上安裝更多的 Koupleless 模塊,進一步降低資源成本。
最後,再次歡迎大家使用 Koupleless 和參與共建,我們期待您寶貴的意見!