Koupleless 內核系列|模塊化隔離與共享帶來的收益與挑戰

文|趙真靈(花名:有濟)

Koupleless 項目負責人螞蟻集團技術專家

本文 3724 字    閱讀 10 分鐘

聯繫作者/加入共建/使用產品

本篇文章屬於「Koupleless 進階系列文章」之一,默認讀者對 Koupleless 的基礎概念、能力都已經瞭解,如果還未了解過的可以查看官網

在 Koupleless 模塊化架構下,有四大特點:快、省、靈活部署、平滑演進。這些主要的優勢來自於對應用架構的縱向和橫向的分層解耦,在隔離與共享之間尋找最佳的平衡,同時也對應用全生命週期 (需求 -> 研發-> 測試驗證 -> 發佈 -> 線上運維調度等) 流程進行升級,包括基座提前預熱、模塊獨立迭代、機器合併複用等上。

這篇文章會從隔離和共享的角度分析 Koupleless 模塊化的優勢、對應的性能 benchmark 對比,同時也會詳細介紹模塊化背後的挑戰和解決方式。

共享帶來的優勢分析

傳統的 SpringBoot 之間完全隔離,相互之間通過 RPC 的方式進行通信,模塊化的模式在於模塊之間存在更直接的共享能力,包括類和對象,以及更直接更高效的本機 jvm service 調用等,從而在隔離與共享之間找到一個更佳的平衡點。爲了深入分析在隔離的基礎上增強共享帶來的效果,我們以社區應用 eladmin 爲基礎拆出一個基座 + 三個模塊進行實驗,統計了一些數據提供給用戶查看,如果有興趣可以自行下載驗證。

類的共享

Koupleless 採用 SOFAArk 將模塊裏的類委託給基座加載,可以讓模塊 ClassLoader 運行時只加載模塊特有的類,模塊打包不會包含這些被複用的類,從而降低打包構建產物的大小以及運行 MetaSpace 的內存消耗,根據模塊與基座相同類的佔比不同,降低的大小和比例也不同。在 eladmin 中,可以看到鏡像化在 300MB,而模塊化構建產物只有 100KB。

構建產物大小對比(假設基礎鏡像爲 200M)

對象的共享

對象的共享主要通過如下兩種方式來複用基座上的對象、邏輯、資源、連接等:

1、static 變量或對象的共享
2、模塊通過 JVM Service 調用基座的邏輯,類似 API 調用且沒有序列化的開銷

static 對象共享

多個模塊通過類委託加載機制複用基座類,這些複用的類裏一些 static 變量在基座啓動時已經初始化完成,模塊啓動時發現這些類裏的 static 變量已經初始化過就會直接返回基座初始化的值,這樣基座類裏定義的 static 變量或對象會被模塊共享,特別是單例對象,在中間件裏存在不少這樣的對象。這些對象在模塊初始化時自動判斷是否存在實例,存在了就會複用,例如 ehcache 裏的 CacheManager。

模塊 JVM Service 調用基座

基座內的通用邏輯定義在基座 bean 或者接口裏,模塊可以通過註解 (Dubbo 與 SOFARPC 的註解) 或者 API 的方式調用這些 bean 或接口。這樣模塊啓動可以無需再次初始化基礎設施服務或者連接,能降低模塊資源消耗提升模塊啓動速度。同時由於這些接口和 bean 是在同一個進程裏,通過 API 或 JVM Service 調用沒有序列化與反序列化開銷,所以也不會出現調用性能下降問題。

通過上述介紹的類和對象複用,我們可以看到模塊的內存消耗相對於傳統應用,從原來的 200MB 下降到了 30 MB,同時因爲減少了一些類與對象的初始化邏輯,啓動時間也從原來的 8 秒下降到 4 秒。

內存消耗大小對比

啓動耗時對比

詳細數據表

可以看到共享帶來了成倍級的收益,收益的多少和複用多少基座的類和資源有關,如果基座沉澱更多的邏輯,那麼模塊的啓動速度還可以提升更多。

在螞蟻集團內部由於基礎設施的 sdk 較多,將這些下沉到基座後,大部分應用的啓動速度從分鐘級降到了 10 秒左右。

共享帶來的問題分析

共享除了帶來收益外,在一些特殊情況下也會帶來一些問題。這些問題主要在於多應用與熱部署這兩方面:

1.多應用主要是  Static 變量共享、多 ClassLoader 的切換

2.熱部署主要是動態卸載時部分資源不會釋放

Koupleless 結合在螞蟻累積 5 年的經驗,提煉了遇到的問題列表,並給出對應的解決方案。這裏將遇到的問題列出來,讓大家清楚模塊化的問題與挑戰,與社區一起共同完善模塊化框架。

類共享

當前類共享的設計是模塊將一些公共類委託給基座,模塊類查找時可以複用基座的類。

Static 共享變量

在單進程多個應用模式下,模塊複用基座裏的類,這些類裏的 Static 變量在基座啓動時會完成一次初始化,模塊再次啓動時,在如下兩種情況下

1.直接複用基座的值

  • 那麼大部分是符合預期的,在少部分模塊希望使用自己的值,這時候會發現使用的是基座值而與實際預期不符

2.直接覆蓋基座的值

  • 對於一些希望使用模塊自身值的情況,直接覆蓋原有的值,那麼會出現覆蓋問題,static 最後只保留最後一次安裝的值

解決方式:遇到這類情況只要將原來 static 變量增加一層 key 爲 classLoader 的 map 即可解決。

多 ClassLoader

我們定義了類委託關係,是優先查找模塊裏的類,模塊查找不到再查找基座裏的類。但由於模塊除了一些能夠委託給基座的類外,一定存在一些無法委託給基座的類,也就是部分委託的方式。所以在類查找過程中會有這 5 種情況。

這 5 種情況下,在少數情況下會出現如下的一些異常 case:

  1. 模塊和基座裏都有的類:如果某些類同時被兩個 ClassLoader 加載,且涉及到類的 instanceOf 等判斷,會導致一些 LinkageError 或者 is not assignable to的錯誤。

  2. 模塊裏有、基座裏沒有的類:如果由模塊 ClassLoader 進入到基座 ClassLoader,然後在基座 ClassLoader 裏執行 Class.forName ("模塊類名") 方法,會查找不到類。

解決方式:這裏我們需要通過 adapter 確保查找類的時候,傳入正確的模塊 ClassLoader。另外在線程池下,存在線程複用,需要將線程與對應  ClassLoader 的綁定正確。

部分資源沒有自動卸載(熱部署纔有)

模塊 SpringBoot 的關閉實際上只是調用了 SpringContext 的 Shutdown 方法,底層依賴 Spring 的 Context 管理進行服務和 Bean 的卸載、依賴 JVM 的 GC 進行對象的回收,沒有執行真正的 JVM 關閉操作。由於框架和業務設計和開發一般較少關注 Shutdown 時的資源清理的,主要依賴 JVM 的關閉操作自動完成資源的清理。所以模塊在熱部署是先卸載後安裝可能存在一些線程、資源、連接等未清理問題。

解決方式:只要用戶主動監聽 SpringContext Close 事件,主動做下清理工作即可。熱部署還有另外一個 metaspace 會在每次安裝增漲的問題,這個 Koupleless 會在安裝時檢測 metaspace 閾值,超過閾值可以回退到重啓安裝即可解決 metaspace 增漲問題。

共享帶來問題的解決方案**

爲了更系統的解決這些問題,我們針對問題的發現 -> 治理 -> 防禦都做了設計和工具實現,這些會在系列的後續文章裏進行介紹。當然通過用戶需求分析,我們也發現並不是所有的問題都需要解決,不同場景只需要解決其中部分問題即可。

中臺模塊

中臺模式,需要追求啓動快、合併在一個 jvm 裏省資源,模塊較爲輕量,一般都是代碼片段,一般不需要處理 static / classLoader / 資源卸載問題,這種模式是問題域最小的,上述所說問題基本不存在,可以直接集成接入 Koupleless 使用。

應用模塊

相對於中臺模塊的是應用模塊,應用模塊也就是模塊較重,和普通應用一樣可以使用各種中間件等能力,這種模式會存在上述提到的一些問題。對於應用模塊存量兩種場景,一種是長尾場景,另一種是非長尾場景,非長尾場景遇到的問題相對可以較少,我們先看下非長尾場景。

非長尾應用

非長尾應用指每臺機器的流量都較充分,已經較充分利用了每一臺機器的計算能力。這類應用一般沒有因爲拆分微服務造成的資源浪費問題,更多關心的是啓動速度、迭代效率。我們可以部署和調度策略、解決一部分問題。首先可以通過 1:1 的方式在一個基座機器上僅安裝一個模塊,能在達到較快啓動的同時,避開多個應用合併在一起的問題。同時可以通過調度的方式,每次模塊安裝新版本的時候可以選擇空基座 (沒有安裝過任何模塊) 機器進行安裝,原來老版本可以通過異步重啓的方式卸載掉老模塊,解決熱部署的卸載殘留問題。以一個模塊的升級過程爲例,具體過程如下:

第一步,初始狀態:

第二步,從 buffer 裏篩選一臺機器安裝模塊 1 版本 2;

第三步,將版本 2 的基座機器調度給基座 1,將版本 1 的基座機器調度給 buffer;

第四步,buffer 集羣發現有機器有已廢棄的模塊實例,則發起重啓,從而清理掉上面的模塊實例;

以此類推,把基座 1 所有機器都升級到版本 2。

這套方式的效果如下表:

長尾應用/私有化交付

這個場景裏追求省資源,這就需要把多個應用合併在一個 jvm 裏,就會遇到多應用的 Static 變量和 ClassLoader (如果解決 RPC 調用消耗上把多應用合併在一起也有類似問題) 。這種模式根據是否需要追求迭代也分兩種:

1、不需要高效迭代,可以直接使用靜態合併部署,不會存在熱部署的卸載殘留問題。

2、動態合併部署,該模式會有多應用、熱卸載的問題,多應用的問題無法規避,需要通過我們提供的整套工具逐步完善解決,熱卸載的問題可以通過模塊部署的時候同步或異步的方式重啓基座解決。

後續我們會建設 ModuleController 和對應的平臺能力,針對不同的場景提供不同的“套餐”,幫助各位按需選擇適合的模式。這是 Koupleless 爲了幫助存量應用解決對應實際問題,從框架和平臺角度共同解決的思路,如果感興趣的同學,歡迎訪問官網 koupleless.io 加羣共同建設。

瞭解更多…

Koupleless Star 一下✨:

https://github.com/koupleless/koupleless

Koupleless官網:

https://koupleless.io/

相關閱讀

圖片

圖片

圖片

圖片

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章