京東中臺化底層支撐框架技術分析及隨想

本文大約1.7萬字,閱讀需要13分鐘。

導讀:近幾年,除AIGC外,軟件領域相關比較大的變化,就是各相關業務領域開始如火如荼地建設中臺和去中臺化了。本文不探討中臺對公司組織架構涉及的變化和影響,只是從中臺化演進的思路,及使用的底層支撐技術框架進行分析探討,重點對中臺及前臺協作涉及到的擴展點及熱部署包的底層技術細節,結合京東實際落地情況,對涉及的核心技術框架進行源碼初探分析,探討技術框架的考慮點,拓寬大家的思路,歡迎大家審閱。

1、序言

中臺及其建設,區別於單體應用建設及微服務建設,最大的差異在於中臺建設有一個較爲獨特的概念,即前臺角色。中臺建設並採用標準協議,開放了一系列標準豐富的能力供前臺角色去編排、擴展使用。從外部的視角來看,其實看不到中臺和前臺,單純還是功能交付,外界的感知沒有太大的差異;從產研的視角來看,功能交付是中臺能力疊加一系列前臺個性化能力的結合物,職責上期望通過中臺底層能力建設和前臺個性化能力增強,兩方獨立開發,再通過底層技術支撐框架來將兩方能力進行結合,期望達到中臺、前臺角色分工清晰,獨立交付,提升交付速度的美好願望。

從上述定義就可以看到,在外部視角感知不強的情況下,交付速度其實是個很明顯的衡量指標。中臺建設成功的標準,重點並不在於對中臺建設的豐富能力進行衡量,也不在於前臺開發了多少獨立的擴展能力,仍在於同之前未建設中臺相比,交付速度是否有質的提升。若這個關鍵指標沒有變化,預估此類建設思路也會出現相關的變化及轉型,轉型的下一步思路和方法也有不少,不在這裏探討。

後續的內容,均會聚焦中臺的底層技術支撐框架(後文簡稱爲Matrix),來將中臺、前臺兩方能力進行結合的技術細節和考慮點進行展開。

在筆者看來,Matrix框架,作爲京東實施PaaS化相關的技術解決方案,期望的是建立劃分合理的業務領域,完成業務建模及抽象,分離出核心邏輯及高頻個性化業務,並將個性化的邏輯隔離出來,期望交給名爲前臺的組織或者部門去共建開發,來達成中臺邏輯穩定,前臺可以並行開發,最終提升交付效率的目的。

當然,在京東或者其他商業化公司,應用系統覆蓋的業務領域及技術方向存在多種,包括但不限於各端的APP等前臺、各端的web前臺、各應用後端系統、各數據算法平臺、各報表運營平臺等等。目前的Matrix框架對其他某些領域可能存在不適應的情況,在筆者看來,着實屬於正常情況。世界上的工具萬萬千,刀槍劍戟 斧鉞鉤叉 閒棍槊棒 鞭鐗錘抓;各類解決方案千千萬,很難存在一個錘子可以砸所有的釘子。但是隻要總體的理念是合適的,併爲其他各類適配此理念的工具提供合適成長的土壤,預判此解決方案可以逐步豐滿成熟。

2、底層支撐框架分析

分析前提:我們只聚焦與中臺底層支撐框架,其他如中臺的業務領域切分、領域建模等內容暫不涉及。需要注意的是,本次原理探究,是從使用人員及業務應用的角度出發,而非從真正對Matrix設計者的角度出發,對Matrix原理的探索難免會存在以偏概全的毛病,對其全貌也必然會存在描述不準確的情況。加之框架底層也在不斷豐富和完善,很多技術內容存在不斷變更和發展,本文描述的內容在短時間內也會存在描述不再準確的情況。但是好在基本原理應該是準確的,內容描述的過程中,如有錯誤,歡迎大家指正。

本文重點對底層支撐框架涉及的幾個核心技術點進行展開:前臺包熱部署設計原理、前中臺隔離原理、前臺業務身份設計原理

3、前臺包熱部署設計原理

在中臺化的實施路徑上,伴隨的節奏一般爲:中臺能力改造升級-->中臺開放標準擴展能力-->前臺使用標準擴展能力完成個性化開發-->前中臺發佈集成。其中發佈集成有2種可選的方式,一種是中臺和前臺採用手工半自動化的方式進行集成,另外一種是中臺和前臺使用全自動的方式進行集成。全自動的方式,就免不了需要將前臺的功能包,採用“熱部署”的方式部署至中臺相關的應用系統中。

本文對前臺包發佈、熱部署的技術方案及思路進行探究,讓我們的讀者瞭解其設計理念。這樣以後在工作中遇到類似前臺包發佈相關的問題,亦或者後續有機會使用並研究其他類似的技術框架,均可以做到舉一反三,心中瞭然。

3.1、熱部署基本原理

在開始介紹之前,假定我們作爲底層框架的設計方,可以嘗試回答下方的三個問題,並與後面給出的結果進行對比,看看與自己預判的答案是否一致。若不一致,本文應該對你有些幫助,可以細細品鑑;若一致,那麼恭喜你,說明你的技術功底相當紮實,對相關的原理及實踐瞭解透徹。

問題:

1)使用前臺擴展包,在發佈平臺操作完成(完成推送、生效),應用端進行擴容上線,擴展點包是否可以自動拉取加載、自動掛載運行?

2)使用前臺擴展包,在發佈平臺操作完成(完成推送、生效),應用端進行擴容上線,擴展點包是否可以自動拉取加載、自動掛載運行?

3)使用前臺擴展包,在發佈平臺對部分容器完成前臺包灰度推送,但沒有觸發生效,此時對這些容器執行重啓操作,推送的前臺包是否會掛載運行?

4)在測試環境亦或是其他環境,一不小心刪除了熱部署包路徑及對應的文件,會有什麼問題?如何解決?

在開始回答上面的問題之前,我們要先來給大家介紹下PaaS化下幾個相關的名詞,及這些名詞背後的相互作用的關係。

可以先看下如下的架構設計圖(筆者自己理解,與官方理解可能存在一定的差異):

 



 

 

 

相關名詞及對應的業務含義簡要說明

1)控制檯相關:前臺或者中臺相關組織或角色在控制檯操作上傳前臺包,控制檯將相關的包信息寫入至相關存儲,並主動通知應用系統,應用系統內的Matrix SDK接收到通知後,執行相關的熱更新及其他操作

2)Matrix SDK相關:熱部署的包變更、發佈等均依賴遠端的控制檯操作,在控制檯操作審批通過後,遠端的控制檯將信息變更寫入到相關存儲,併發送變更通知到各個docker應用實例,各docker實例接受變更通知並執行如熱部署等不同的操作。docker實例執行熱部署的工作主要有:從遠程私服下載jar到本地目錄,並執行一系列的操作流程,包括但不限於前臺包卸載、前臺包更新等,其中前臺包更新包括但不限於:前臺包類加載、前臺包類初始化、前臺包生命週期管理、前臺包動態代理管理等。所謂萬丈高樓平地起,Matrix平臺框架的功能再複雜,實際的地基就是基於這一點,並在基礎夯實疊加其他各類優化和加強的功能,大家可以仔細領悟。

3)應用層執行相關:中臺應用層,在執行至擴展點相關的業務邏輯時,會動態代理至Matrix SDK,並由SDK接管邏輯,並確認業務是否命中相關的業務身份,在在業務身份命中後執行前臺包的實際邏輯。

4)監控及安全相關:控制檯及Matrix SDK相互合作,採集心跳、擴展點執行時間等各類統計維度數據,此處也是一個較爲複雜的體系,不詳細展開。

備註:以上架構圖,只是筆者理解的Matrix技術框架的示意圖,僅供參考。



前臺包發佈、熱部署及管理相關的主要的鏈路示意圖如下所示:



 

 

 

從圖中可以看到,我們的前臺包發佈、熱部署存在“推”、“拉”兩條數據鏈路,兩條數據鏈路也分別適配不同的業務場景,如下詳細展開。

3.1.1、“推”鏈路

此鏈路的全過程可以表述爲:軟件實施人員在控制檯,通過推送生效等可視化界面工具操作完成後,控制檯會將用戶操作數據實時寫入至相關的數據中心,數據中心依賴的核心組件DUCC(一種類似ZK的存儲介質)會對外發布實時變更消息。集成了SDK的應用容器收到DUCC變更消息通知後,感知控制檯的操作並依據操作類型不同做出差異化的反映:SDK依據操作的不同指令,在應用容器中執行不同的業務操作。

目前SDK中的操作策略指令包括但不限於如下4類:

1)推送:對應的功能可表述爲:SDK接收到指令後,從遠端文件服務器將相關版本的前臺包下載至應用容器的固定目錄中。此功能在Matrix技術框架中中對應的處理類爲:PublishActionImpl。

2)生效:對應的功能可表述爲:SDK接收到指令後,從遠端文件服務器將相關版本的前臺包下載至應用容器的固定目錄中,並執行前臺包的熱加載功能(具體熱加載的功能說明,可以參考第二篇文章,此處不在贅述)。此功能在Matrix技術框架中中對應的處理類爲:TakeEffectActionImpl。

3)卸載:對應的功能可表述爲:SDK接收到指令後,將相關的前臺包從容器中卸載,並處理註銷相關的數據(備註:此功能在藏經閣控制檯默認情況下不會展示,我們平常情況下看不到這個功能菜單)。此功能在Matrix技術框架中中對應的處理類爲:UnloadActionImpl。

4)探活:對應的功能可表述爲:SDK接收到指令後,從當前的容器服務器發送相關的心跳包,用以採集監控當前容器服務器相關的實時狀態數據。此功能在Matrix技術框架中中對應的處理類爲:DoAliveActionImpl。

“推”鏈路適用的場景爲:應用的場景爲實時性要求較強的相關場景,包括但不限於新版本灰度及發佈、版本回滾及控制、版本下線及監控等。此類場景要求在控制檯執行相關的操作步驟後,控制檯及相關的應用容器在短時間內(秒級或者毫秒級內)做出實時反映。主打的就是保障通知的及時性。

3.1.2、“拉”鏈路

此鏈路的全過程可以表述爲:軟件實施人員,在相關的容器部署平臺,通過對容器進行擴容等操作,完成應用擴容。在對擴容的機器進行發佈上線的時候,集成了SDK的應用容器,會自動從數據中心獲取相關的應用元數據,在識別到元數據中存在部署版本的時候,自動從從遠端文件服務器將最新生效版本的前臺包下載至應用容器的固定目錄中,並執行前臺包的熱加載功能。

此處需要注意一點,在京東現有環境內,容器擴容包含2步操作:1)部署平臺創建新的docker環境,完成應用實例的創建,並處理複製更新好相關的鏡像環境;2)軟件實施人員對擴容出來的應用實例,執行發佈上線等操作。其中“拉”鏈路出現的時機出現在步驟二中。從邏輯代碼中,我們可以看到,在應用系統發佈上線後,在spring的生命週期內,Matrix會執行相關“拉”邏輯。

“拉”鏈路適用的場景爲

1)對實時性要求不是很高的場景,如應用擴容等;

2)“推”鏈路無法觸達或觸達成本較高的場景。其中第二種場景有很多細分的業務場景,比如軟件實施人員對容器中的部分目錄文件執行了誤刪除等操作,或者容器中的部分目錄文件出現了不可預知的損壞等異常場景。



3.1.3 、“推”和“拉”兩條鏈路設計理念說明

從上面可以看到,“推”和“拉”兩條鏈路都有其獨自適應的應用場景,那麼筆者提出一個問題,是否有可能只保留其中的一條鏈路呢?讀者在不看後方的內容時可提取思考一下,爲什麼Matrix框架會執行如此設計呢?

在直接得出結論前,我們可以假定一下若只存在單鏈路,然後看看在單鏈路的情況下相關的複雜場景及其對應的解決方案,最後我們再來綜合評定最優方案。

1、只存在“推”單鏈路:

需要應對的複雜場景:應用擴容。

複雜場景下需要解決的問題:在應用擴容時,在“推”鏈路下,數據中心如何能快速感知到擴容容器的存在,同時對此類場景實時下發處理消息降低實施成本。

可選的解決方案:在應用擴容發佈時,應用端主動向數據中心發送相關的消息,獲取數據中心最新的應用數據等;數據中心接收到消息後,再實時下發相關的處理消息。應用容器接收到消息後,再執行下載前臺包、熱更新等前臺包部署操作。

弊端:應用擴容發佈後,按一般情況,應用即可對外提供服務。爲了防止出現應用已對外提供了服務,但是相關的前臺包還沒有拉取到應用本地,導致相關的擴展點出現“空轉”等情況。爲了解決此問題,上方提及的可選的解決方案,務必要保障實時性特別高,即務必保障在系統啓動並對外提供服務前,快速完成解決方案。那麼怎麼保障?最好的辦法就是在未處理成功前,設定系統沒有啓動成功,不能對外提供服務。如此,此方案落地層面,涉及SDK和數據中心一來一回兩次交互,存在方案較爲複雜,存在後續維護運維成本高的問題;同時還存在消息鏈路太長,導致整個項目的啓動時長變得不可控,擴容體驗變得極差的問題。



2、只存在“拉”單鏈路:

需要應對的複雜場景:軟件實施人員在控制檯實時操作“發佈”、“推包”等操作。

複雜場景下需要解決的問題:軟件實施人員執行的操作,在“拉”鏈路下,在應用端要可以及時感知到。

可選的解決方案:可選的方案有2個:1)控制檯和應用端建立直接聯繫,在控制檯完成相關操作後,直接通過聯繫渠道將操作傳遞給應用端;2)應用端定時獲取數據中心的最新應用狀態數據。但是方案一基本變相等同於是“推”鏈路,此處可以忽略不計。方案二理論上可行,但是也存在2個弊端:1)定時的時長不好控制,太長了,則操作可被感知具有一定的延遲性,存在客戶體驗不佳的情況;2)定時時長太短了,多個應用無腦同時請求數據中心,對數據中心的高可用提出了極高的要求。對此弊端,我們可選的方案有:制定業務可接收的延時時間,並定時去請求數據中心。並對請求時,請求數據和返回數據存在數據過大的情況,採取數據極限裁剪、數據壓縮等方案。並設定獨立專業團隊,通過建立雙數據中心等機制,保障數據中心的安全性及高可用性。

弊端:從根上來講,相關的通知鏈路總會存在或多或少的時延,客戶體驗或多或少都有一定的影響;同時方案複雜性變高,維護成本變大。

小結:從上述兩處推測方案可以看到,單純只是採用一種解決方案,均存在較多的方案瑕疵。而將“推”和“拉”兩個方案進行結合,並將方案應用在其適應的領域,則上述提及的弊端均可以以最小的成本來得以化解。而“推”、“拉”結合的解決方案,在業界的各類場景下,也存在廣泛運用。大家可以多加領悟。

3.2、熱部署相關注意事項

1、需要注意控制大面積擴容的節奏:從上述的表述中,我們可以看到,應用在擴容發佈的時候,集成了SDK的應用服務器,會從遠端服務器拉取最新的前臺包至應用服務器。假定我們的前臺包裁剪的大小比例不合理,一個前臺包的大小在300M及以上,加之我們擴容採取的是大面積擴容發佈上線應對線上緊急情況的場景,那麼大促的某個瞬間,會存在同一時刻,應用容器集中請求文件服務器,可能導致文件服務器過載,進而導致下載失敗和發佈上線啓動不成功的情況。這種情況,我是頂不住,不知道你頂不頂得住?

對於這種情況,推薦3類解決方案:1)每次只進行小批量擴容,保障擴容數量的合理性;2)仍然採取大批量擴容,但在應用發佈啓動時採用小批量的方式分批進行;3)在大面積擴容時,通知Matrix相關同事,要求其對文件服務器及擴容操作進行重點保障。至於具體落地方案怎麼選擇,大家可多加思考,並自由抉擇。

2、需要注意控制前臺包的大小:不管是“推”還是“拉”哪條鏈路,均會涉及到對前臺包從遠端服務器下載至本地的操作。從感官上來,此項操作對網絡資源的消耗、應用服務器本地資源的讀寫均會在瞬間造成較大的影響。如此,就要求我們的共建方前臺角色、中臺角色嚴格把控前臺包的大小,避免無關的包打入,控制前臺包的大小在相對合理的區間。

3.3、問題解答及分析

依據上方的講解,我們再來看下序言中提及的三個問題,來看看最終的答案,不知和你心中的答案是否一致呢?

問題1)使用前臺擴展包,在發佈平臺操作完成(完成推送、生效),應用端進行擴容上線,擴展點包是否可以自動拉取加載、自動掛載運行?

答案:可以。此時應用端擴容上線,因爲應用服務器上不存在任何前臺包,會採取“拉”鏈路,將相關的前臺包一次性拉取到本地進行加載、自動掛載運行。



問題2)使用前臺擴展包,在發佈平臺操作完成(完成推送、生效),應用端進行擴容上線,擴展點包是否可以自動拉取加載、自動掛載運行?

答案:可以。此時仍然是“拉”鏈路在生效,但是需要注意一點,次數“拉”鏈路拉取的數據,爲發佈平臺管理的上一次(如果有的話)上線的最新信息。在完成擴容上線後,如果我們沒有在發佈平臺對新擴容的機器進行推包、發佈等操作,線上應用會存在並運行兩個版本的前臺包。

 

問題3)使用前臺擴展包,在發佈平臺對部分容器完成前臺包灰度推送,但沒有觸發生效,此時對這些容器執行重啓操作,推送的前臺包是否會掛載運行?

答案:不會。雖然說前臺包已完成灰度推送,相關的前臺包文件已在應用容器中存在,但是容器執行重啓操作時,SDK會自動按分組、IP等檢測最新已生效的版本,若發現當前版本並沒有生效,哪怕這個前臺包文件在容器中已存在,也不會掛載運行。

 

問題4)在測試環境亦或是其他環境,一不小心刪除了熱部署包路徑及對應的文件,會有什麼問題?如何解決?

答案:分情況。

情況1:若是我們將前臺包相關的目錄進行了整個移除,則在應用容器再次進行啓動的時候,會執行“拉”鏈路,將相關的前臺包一次性拉取到本地進行加載、自動掛載運行。

情況2:若是我們講前臺包相關的目錄中的部分文件進行移除(具體移除了哪類文件此處不詳細展開),“拉”鏈路識別前臺包文件已存在,“拉”鏈路不會得以執行。但是因爲文件已損毀,相關的數據不完整,SDK在記載的過程中,會拋出相關的錯誤異常,並給出相關的錯誤信息,最終呈現的效果爲整個應用啓動失敗。

 

4、前中臺隔離原理

前文介紹的熱部署操作完畢後,前臺擴展包,就可以正確在中臺相關的容器中完成部署了加載了。那麼此處問題來了,前臺和中臺會共用某些相關的類,如何保障執行的安全性及可靠性呢。那麼就不得不提到了類隔離機制,和隔離機制背後的原理:雙親委派模型。



類隔離機制:前臺和中臺使用不同的ClassLoader來加載實現隔離,中臺使用默認的ClassLoader,前臺使用自定義的ClassLoader,其中部分類爲了避免衝突,前中臺共享了默認的ClassLoader,這些類包括但不限於RPC框架、緩存框架等基礎組件包。

筆者早期工作的時候,在web應用系統,使用Eclipse的osgi概念在開發處理相關的bundle包的時候,對於涉及web應用中登錄的session等複雜的數據結構進行信息傳遞處理的時候,經常出現2類及以上的ClassLoader協同出現問題,導致應用系統出現一些莫名的、難以排查的ClassNotFoundException。表象爲應用的工程包明明存在,在執行的時候就是會出現錯誤的奇特場景。當時初畢業,技術功底淺薄,對此類問題印象極其深刻。而在我們在京東的實際使用的過程中,暫無發現此類問題。如果大家有遇到此類問題,歡迎進一步交流。



說到類加載,就不得不提Java體系中大名鼎鼎的“雙親委派模型”(英文描述爲:parents delegation model)。有關這個描述,此處多說一點。中文含義中的“雙”,其實在英文描述中並沒有對應的描述,也即“雙”不“雙”,其實不緊要,緊要的是“委派”二字。“雙親委派模型”的核心內容,用稍粗鄙點的言語可以表述爲:有事找爸爸(爸爸如果也有事,可以找他爸爸的爸爸,以此類推)。此設計理念的好處內外網相關文章介紹得較多,此處不在贅述。

 



 

 

 

此模型的簡要代碼可以表述如下:

    protected Class<?> loadClass(String name,boolean resolve)
        throwsClassNotFoundException
    {
        synchronized(getClassLoadingLock(name)){
            // First, check if the class has already been loaded
            Class<?> c =findLoadedClass(name);
            if(c ==null){
                long t0 =System.nanoTime();
                try{
                    if(parent !=null){
                        c = parent.loadClass(name,false);
                    }else{
                        c =findBootstrapClassOrNull(name);
                    }
                }catch(ClassNotFoundException e){
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if(c ==null){
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 =System.nanoTime();
                    c =findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if(resolve){
                resolveClass(c);
            }
            return c;
        }
    }


所謂“雙親委派模型”,從上述代碼可以看到,先檢查類是否被加載過(第6行),若沒有加載則調用父加載器進行加載(第11行),若父加載器爲空則默認使用啓動類加載器進行加載(低13行)。可以看到,若都失敗,則調用當前類加載器進行加載(第24行)。是不是和上述找“爸爸”的描述,比較契合?

在Matrix框架下,中臺相關的邏輯和前臺相關的業務包,是由2個獨立的ClassLoader類分開管理,其中前臺相關的業務包,是由名爲com.jd.matrix.core.classloader.BizClassLoader的類進行管理。我們先來看下這個類的具體邏輯:

public class BizClassLoader extends URLClassLoader{
    public BizClassLoader(URL[] urls){
        super(urls);
    }

    protected Class<?>loadClass(String name,boolean resolve) throwsClassNotFoundException{
        Class<?> clazz =null;
        clazz =this.findLoadedClass(name);
        if(clazz !=null){
            return clazz;
        }else{
            try{
                ClassLoader jdkClassLoader =ClassLoaderFactory.getJDKClassLoader();
                clazz = jdkClassLoader.loadClass(name);
                if(clazz !=null){
                    return clazz;
                }
            }catch(ClassNotFoundException exception){
            }

            if(ExportClassManager.match(name)){
                clazz =ClassLoaderFactory.getInternalClassLoader().loadClass(name);
                if(clazz !=null){
                    return clazz;
                }
            }

            try{
                clazz =this.findClass(name);
            }catch(ClassNotFoundException exception){
            }

            if(clazz ==null){
                clazz =ClassLoaderFactory.getInternalClassLoader().loadClass(name);
            }

            return clazz;
        }
    }

    protected Class<?> findClass(String name) throws ClassNotFoundException{
        returnsuper.findClass(name);
    }}


從代碼邏輯可以看到,Matrix提供的類加載器,其實是破壞了“雙親委派模型”,破壞點從13行開始,若當前類沒有加載,沒有調用父加載器進行加載,而是使用了jdkClassLoader去加載;同時若開啓了Matrix包屏蔽的策略(21行),則會使用中臺的ClassLoader去加載。

此處破壞的妙處在於:前臺包可以找到並使用中臺包對應的相關類;但是中臺包不可以找到並使用前臺包對應的相關類。粗俗點可以描述爲:大哥不知道小弟,但是小弟在大哥的開放區域,可以有限瞭解大哥。

從此處也可以看到,Matrix對於類加載的管理與控制,與OSGI等標準規範相比,安全性等管控方面要鬆弛得多。從嚴格意義上來,此類管理的方式,安全性其實並不足,若中臺開放的區域過大,加上前臺包被有心人動手腳,其實是存在安全隱患,並防不勝防。



我們可以看到,Matrix會結合容器側的spring配置裏的exportClassConfig屬性,使用自定義的類加載器(BizClassLoader)去加載垂直業務包裏的類,主要有兩個核心內容:

1、對於不同的前臺業務包裏的類,分別使用BizClassLoader的不同實例去加載,對於垂直業務包裏的自己開發的業務類,即使不同的垂直業務包中存在全限定名相同的類,但因爲加載他們的BizClassLoader實例不同,不會出現衝突問題,類隔離的要求能夠得到滿足。

2、對於exportClassConfig這個屬性配置的相關類(包含但不限於:中臺提供的sdk包所包含的類、基礎RPC框架類),不會由1裏所提及的自定義的classLoader的實例去加載,只會由同一個類加載器(加載中臺容器業務類的appClassLoader)去加載,以此來保證中臺容器調用擴展點實現時參數類型校驗的一致性。

從上方的信息和代碼中,我們可以看到, 假定前臺和中臺存在相同包名、相同類名的類,在執行前臺擴展點的時候,分爲前後2個步驟:1)執行前臺包擴展點的業務身份匹配邏輯;2)執行前臺包擴展點的實際業務邏輯。2者的區別,主要在於後者有一個主動切換當前類加載器的邏輯。也即在步驟1時,當前的類加載器爲應用容器業務類的加載器;步驟2時,當前的類加載器切換爲獨立的類加載器。

 

5、前臺業務身份設計原理

中臺和前臺合作的巧妙之處,在於中臺開放了多種標準的、可擴展的能力,前臺基於自己的不同的業務身份實現多種個性化業務場景。即一種中臺的能力,可對應多個前臺擴展部署包。那麼運行時,中臺如何知道應該使用哪些前臺包呢?

這裏就不得不提業務身份這個概念了,在中臺的建設思想中,每個前臺包都有自己獨立的業務身份。中臺系統運行的時候,依次匹配前臺部署包,並選擇業務身份命中的前臺包執行相關邏輯。



業務身份的識別方式:Matrix業務身份有2種識別方式

1、前臺包手工編寫識別業務身份。即在前臺自己在元數據(Annotation)定義自己獨立的parserClass ,識別方式,由前臺業務系統手工編寫識別。這種方式的好處是純手寫代碼,加上前中臺組合review的時候,一般不會出現功能問題及性能瓶頸。但是也會存在很大一個弊端,即對某個業務而言,是不是屬於自己的業務身份,是前臺自己說了算,在一般情況下是沒有問題的,但是如果前中臺reivew的機制失效了,前臺基於某些特定的原因,將前臺的業務身份識別方式調整擴大或縮小,會導致影響的業務範圍也會對應的增大或者縮小,影響範圍非常不可控。

從中臺建設初期來看,本來預計這種方式,以後一定會廢棄掉。從過去幾年的實踐經驗來看,這個地方預判錯了,這種形式目前仍然是主流的形式,只是不同的系統對同一個業務身份的識別,邏輯千差萬別,在串聯業務流程的過程,確實會存在較爲痛苦的情況。



 

 

 

2、中臺統一管控識別業務身份。在在前臺自己在元數據(Annotation)設置autoParser的值爲true,此時不用前臺來判斷處理和業務身份相關的控制邏輯了,這個邏輯會內置在中臺,中臺相關的業務身份的解析類爲AutoBizCodeParser,此類的定義與實現框架與前臺包定義的方式無異,只是實現了平臺通用的能力:即由中臺來決定是否可以命中某一個業務身份,在管理平臺增加相關配置來匹配是否可命中業務身份。

核心的邏輯是Matrix內置定義了一個簡單的腳本模板,在系統採用熱部署指令,加載啓動前臺包的時候,識別到前臺包包含App元數據(Annotation)是自動識別業務身份的時候,會在系統內按內置的腳本模板採用javassist框架來初始化緩存一套字節碼。在實際去匹配前臺業務身份的時候,實際執行的是此類字節碼來動態判斷業務身份是否可以命中。字節碼的模板代碼如下所示:

需要注意一點,目前底層框架生成字節碼的時間點,並不是系統自動加載或者熱部署生效的時候,而是嘗試命中業務邏輯的時候。預判肯定會存在執行過程中第一次速度緩慢的情況,實際使用的時候可以多加註意。

#{ClassStart}
import java.util.*;
#{Package}
public class #{ClassName} {
   #{MethodInfo}
}
#{ClassEnd}

#{PackageStart}
import #{TypeName};

#{PackageEnd}

#{VarStart}
    #{Type} #{Var}=(#{Type})context.get("#{Var}");
#{VarEnd}

#{executeStart}
 public boolean execute(Map context){
       #{VarInfo}
       return #{Express};
    } 
 #{executeEnd} 
 
   
#{allStart}
    private boolean all(#{Var} ){

       Iterator iterator =#{Collection}.iterator();
        while (iterator.hasNext()) {
          #{ItemType} #{ItemVar}=(#{ItemType})iterator.next();
           if(!(#{Right})){
              return false;
            }

        }
        return true;
    } 
#{allEnd}

#{anyStart}
    private boolean any(#{Var} ){

        Iterator iterator = #{Collection}.iterator();
        while (iterator.hasNext()) {
          #{ItemType} #{ItemVar}=(#{ItemType})iterator.next();
           if((#{Right})){
              return true;
           }

        }
        return false;
    } 
#{anyEnd}



業務身份命中基本流程:此處其實無需多言,業務身份命中的邏輯其實隱含在前面介紹的前臺包業務邏輯實際執行的地方,只是Matrix框架在執行邏輯前,創造了一個類似會話(Session)的概念,在會話中去對業務身份是否命中執行相關匹配邏輯,實際就是對相關的前臺業務包執行fiter方法,判斷評估業務身份命中。在會話(Session)中對前臺包依次執行filter去匹配業務身份。

存在的風險

1)部分前臺包業務身份識別邏輯較重,會導致整體中臺邏輯運行緩慢,存在性能風險;對應的建議解決方案:業務身份識別邏輯一定要輕量級;

2)目前Matrix框架默認仍然只是捕獲了Exception類別的異常,對於Throwable級別的異常仍然是沒有捕獲的。部分前臺包業務邏輯存在偶發性問題,執行業務身份匹配filter邏輯時,若拋出Throwable級別的異常,會導致其餘業務身份的前臺包也無法執行。這真是一粒老鼠屎,搞壞一鍋粥。



業務身份基本原理:Matrix實際以業務身份來管理業務包與擴展點,同一個業務身份,可以命中一個垂直擴展點(代號爲Y)和一些水平擴展點(代號爲X)。針對同一個待識別的業務規則,比如同一個sku或者同一個訂單而言,不可以命中多個業務身份,若實際命中了多個業務身份,Matrix會限制只會命中其中的某一個業務身份對應的擴展點方法。也即對這種業務場景,Matrix即使會命中多個業務身份的fiter方法,但是實際只會執行其中唯一的一個業務身份對應的擴展點方法,具體命中的業務身份,初看有一定的隨機性,具體順序參加下方的描述。

業務身份的順序:從Matrix遍歷業務身份處理擴展點的業務邏輯來看,業務身份的順序至關重要,排名靠前的業務身份,其對應的擴展點方法執行的機率會大於排期靠後的業務身份。經排查,Matrix對業務身份排名的邏輯由業務身份(App)的三個屬性來共同確定,具體爲:priority(由大到小)、version(由大到小)、code(由大到小)。在滿足上述排序規則的前提下確定業務身份的優先級。假定業務上存在如下場景:對同一個訂單或者同一個sku的場景,同時可命中二級業務身份和三級業務身份,理論上業務期望三級業務身份被命中到。但是很不巧,priority、versionSpec等值設置不合理,導致二級業務身份被命中到出現與預期不一致的情況。對於此類場景,我們就需要制定嚴格的代碼規範,對前臺業務側限定其priority、version、code的數值,確保業務邏輯的正確性。

重點提示:業務身份的定義與使用,是業務系統出現問題最多的地方。業務身份在Matrix中是一個很重要的概念,其設計理念期待整個業務身份是一顆節點互不交叉的樹,但是在實際業務執行的時候,經常會存在業務身份重疊的情況。設計定義前臺包的人員,屬於兩個不同的部門,屬於兩撥不同的人,2者很難意識到業務存在交叉重疊的情況,已經發生多次線上業務跑飛了出現與預期不一致的情況,後來花了大力氣來解決的場景。常見出現問題的案例舉例供大家參考:我們提供了一個擴展點供前臺業務使用,其中一個前臺包的業務身份是大家電,另外一個前臺包的業務身份是五星。結果在實際業務上,同一個sku或者同一個訂單,既是大家電的業務,也是五星的業務。對於這種場景,如何解決呢?這個問題留給大家來思考。

Matrix對業務身份排名的具體代碼如下所示,代碼邏輯參見BizCodeSpec.compareTo方法:

    public int compareTo(BizCodeSpec o) {
        if (o == null) {
            return -1;
        } else if (this.priority != null && o.priority != null) {
            int ret = o.priority - this.priority;
            if (ret != 0) {
                return ret;
            } else if (this.versionSpec != null && o.versionSpec != null) {
                ret = this.versionSpec.compareTo(o.versionSpec);
                if (ret == 0) {
                    ret = o.bizCode.compareTo(this.bizCode);
                }

                return ret;
            } else {
                return 0;
            }
        } else {
            return 0;
        }
    }


最佳實踐

1、一般而言,我們在設計並編寫前臺包的時候,包名應該具有前臺獨立的業務屬性,也即包名和中臺的包名應該有一定的區隔。對於可能會和中臺包衝突的業務場景,我們可以在框架中進行類名或者包名的排除。

2、中臺開放給前臺的包名範圍,應該儘量限定在最小範圍,防止前臺不經意的惡意行爲。

除這2點外,可能還存在其他各類場景對應的最佳實踐,有待我們進一步探索。

6、思考

不管是中臺化也好,還是去中臺也好。核心思路,都在於如何以技術,服務好我們的業務。中臺化,亦或者是品類差異化思路,均爲交付過程中選擇使用的差異化工具。但是工具背後,必然隱藏着對應的落地技術關鍵點,而這些關鍵點,道理都是相通的。作爲技術人員,持續思考,如何從漫天繁花中,找到底層最核心的癥結,不斷打磨豐富我們的產品,提供優質的服務,值得我們持續探索。

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