劉一田,曹一鳴
(南京南瑞信息通信科技有限公司,南京 210003)
傳統的服務管理控制臺和應用門戶在面臨業務快速發展之后,出現了單體應用進化成巨石應用的問題,隨著微服務架構的廣泛應用,業務應用被劃分為更小顆粒度的微應用,不同的微應用可以采用不同的技術棧實現,更好地提升了業務開發效率和需求響應能力,從企業級管理需求角度,管理控制臺需要聚合微應用并提供管理入口,支持租戶按需定制SaaS 層應用門戶,對已在線上運行的項目,支持低成本地接入微應用門戶,而不需要對現有開發和部署流程做大量兼容改造;支持不同技術棧微應用分布式開發部署和運行時上下文資源分離,但允許開發階段微應用之間良好聯調,能保證單頁應用的操作流程不中斷和體驗流暢性.目前,微應用管理控制臺及業務服務微應用的前端技術棧實現是主流的Vue、React、Angular 等前端框架,為了解決上述問題,主流解決方案有3 種:(1)基于iframe 的頁面嵌入.將不同的微應用的入口頁面通過iframe 嵌入到門戶菜單中.集成方式簡單,缺點是頁面切換無法保持路由狀態,父子應用通信不穩定,存在潛在性能瓶頸.(2)基于Nginx 導航路由.基于Nginx 中微應用路由前綴的不同將請求路由到不同的微應用.優點是支持微應用的技術棧獨立,缺點是頁面跳轉有時會閃屏,動態擴展需變更Nginx 配置.(3)基于微前端[1]Single-SPA 框架[2].即通過由獨立交付的多個微應用動態組成整體管理控制臺的架構模式.將前端應用分解成一些更小、更簡單的能夠獨立開發、測試、部署的小塊,從用戶角度仍然是無縫管理體驗.管理控制臺應用異步加載嵌入其內部的微應用的入口文件,動態創建微應用裝載點.優點是支持微應用的開發技術棧獨立,缺點是微應用之間開發聯調復雜.文獻[3]描述的微前端應用以JS 文件入口方式實現微應用加載,這種方式會導致微應用之間運行時資源上下文混淆,增加了開發和維護的復雜度.
綜合上述問題及方案,結合項目中微應用數量不斷迭代遞增、父子微應用通信、微應用資源按需加載和卸載、運行時上下文資源分離等需求,本文給出了微前端化管理管理控制臺的解決方案.(1) 提供基于管理控制臺消息總線的路由注冊機制,管理各個微應用的生命周期,傳遞路由消息.(2) 定義微應用渲染入口規范,提供微應用注冊接入機制.(3) 提供微應用加載工具庫,監聽微應用地址端口路由變化并快速加載或卸載微應用,加載時讀取微應用配置并為微應用的實例提供門戶占位裝載點,卸載時反向移除微應用相關資源.
微前端管理控制臺由管理門戶和微服務對應的管理微應用組成,如圖1所示.它們共享公共依賴庫以實現共享數據和資源依賴,進而降低開發管理成本,管理控制的權限刷新令牌采用REDIS 集群存儲,訪問令牌基于本地緩存存儲[4].本文中討論的微應用基于Node.js環境部署運行,文獻[5]論證了前端應用基于Node.js環境在微應用系統運行期的相對優勢.

圖1 微前端管理控制臺框架
微前端化管理控制臺的交互過程及關鍵處理機制包含分為以下7 個方面.
(1) 用戶登錄.用戶訪問管理門戶首頁,管理門戶鑒別是否已存儲已登錄的本地令牌,如無則跳轉到單點登錄頁面.
(2) 令牌存儲.登錄成功后分別記錄用戶訪問令牌到本地緩存,記錄刷新令牌到REDIS 中,其中REDIS采用集群部署方式.
(3)管理門戶微應用啟動時加載所管理的微應用配置.配置包括微應用名稱、頁面入口地址和渲染方法引用,認證登錄成功后根據配置跳轉到默認微應用頁面.
(4) 微應用注冊及加載.微應用注冊時,采用HTML作為集成入口,避免JS 入口方式資源未完全加載而異步構建子微應用容器節點,可能導致渲染失敗等問題,微應用運行加載時,管理門戶微應用注冊模塊通過提取HTML 獲取當前子微應用的靜態資源,同時將微應用HTML 文檔作為子模板節點添加到管理門戶的頁面模板容器中,繼而觸發微應用頁面渲染.
(5) 管理路由事件.提供導航路由模塊,通過監聽不同微應用的啟動端口偵測瀏覽器地址變化事件,在所管理的微應用路由觸發或切換時,在緩存中保留切換前的微應用完整的環境上下文狀態,動態加載目標路由微應用,待再次路由返回時還原之前狀態的微應用上下文運行時資源,確保微應用切換時良好交互體驗.
(6) 微應用模塊跨應用依賴.為了更好地管理微應用,增加微應用的生命周期事件監聽及相應處理函數,并通過Webpack 的umd 打包格式中的全局導出模式導出微應用的生命周期事件監聽[6],從而獲取微應用的內置模塊導出和跨微應用模塊導入.
(7) 微應用資源邊界上下文分離.監聽微應用的加載和卸載事件,在門戶路由系統跨微應用切換時,在監聽的路由事件中重新加載或卸載微應用上下文中的JS 腳本和樣式表等資源.
圖1中微前端化管理控制臺由管理門戶微應用和眾多業務管理微應用組成,管理門戶采用Vue2+ElementUI[7]實現,其中管理門戶微應用作為父應用負責其它微應用的注冊和卸載,管理門戶微應用目錄結構如圖2所示,微應用基于模塊化工程方式構建[8],父應用基于HTML5規范的歷史記錄對象狀態變更機制觸發微應用路由的切換,在父應用視圖模板中置入子微應用的宿主容器,作為子微應用在父應用容器中的占位符.并需要實現以下3 種框架機制:(1) 微應用注冊、加載和卸載機制;(2) 微應用通信及交互機制;(3) 微應用資源共享和運行時上下文資源分離機制.

圖2 微前端化微應用管理控制臺工程目錄
由于控制臺管理采用延遲加載微應用模式,當瀏覽器重新加載時,管理控制臺的微應用資源也會重新被異步再次加載,由于此時門戶的路由導航已經觸發微應用生命周期啟動階段事件,但微應用運行時上下文JS 腳本和樣式等靜態資源不能確保已經全部加載和正常解析完畢,間接導致了路由記錄緩存里找不到相對應的導航規則,最終導致路由切換時出現異常而無法正常展示
因此,注冊微應用時,需要基于管理控制臺前端事件總線,和父子微應用生命周期不同階段鉤子事件實現父子微應用及子微應用之間的注冊、加載、卸載等交互,管理微應用運行時上下文狀態緩存,微應用注冊過程的形式化描述如圖3所示.

圖3 微應用注冊過程
首先,在父應用中根據微應用配置規約定義每個子微應用的加載入口頁面HTML 資源屬性、渲染及路由方法、微應用生命周期事件等;父微應用需要先加載每個子微應用的入口資源,待入口資源加載完畢,確保子微應用的路由系統注冊進父微應用之后,再由子微應用內部的路由系統接管路由改變事件.同時在子微應用路由卸載時,父微應用觸發相應的銷毀事件,子微應用在監聽到該事件時,調用自己的卸載方法卸載自身運行時上下文資源;其次,設置默認的子微應用作為父應用的默認路由規則,對默認微應用進行預加載,并定義默認微應用加載完成后的回調事件以支持父微應用完成后的業務定制擴展;最后,啟動全局路由,根據全局路由規則切換微應用上下文資源、渲染微應用展現和卸載清理.
微應用之間的通信及交互分為父子微應用、微應用內部、微應用之間等3 種通信及交互機制.其中父子微應用及微應用之間的交互基于管理控制臺事件總線實現,如圖4所示.

圖4 跨微應用通信及交互
3.2.1 父子微應用通信
(1) 父微應用傳遞子微應用的方式.父微應用中注冊子微應用時,在子微應用的配置中加入消息實體屬性,通過該屬性值的修改,動態將消息通知給子微應用.
(2) 子微應用傳遞父微應用的方式.子微應用中導出掛載前回調事件,在事件參數中,將子微應用待傳遞的參數對象置入,發布到父微應用的事件總線上,父微應用監聽到消息后,從其事件總線上獲取子微應用的數據對象.形式化描述如圖5所示.

圖5 父子微應用通信機制
3.2.2 微應用內部及之間通信
定義基于微應用管理門戶主窗體的對象全局變量,在子微應用內部匹配切換路由時,路由管理通過Vue Router 實現,路由設置為HTML5 歷史模式,如果偵聽到路由中包含了全局變量中指定的微應用路由,則進行跨微應用切換,否則進行微應用內部頁面的路由切換.
當源微應用準備發消息給目標微應用時,通過父微應用的事件消息總線進行指定主題事件的訂閱和發布,當源微應用完成指定操作后要跳轉到目標微應用時,則通過瀏覽器對象模型提供的歷史記錄對象的狀態改變方法,傳入目標微應用名稱和入口參數,觸發微應用之間路由的強制跳轉和消息通信傳遞.
3.3.1 實現微應用資源及數據共享機制
(1) 運行時數據共享.全局的數據一般會存儲在父微應用中,然后子微應用可以直接共享,比如:當前登錄用戶及隸屬組織、國際和本地化配置、API 令牌等,簡單的數據共享可以直接掛載在門戶微應用主窗體上即可,為了讓每個子微應用使用全局服務和模塊內服務一致,通過在父微應用中實例化這些服務,然后在每個子微應用的入口模塊中將該實例引用設置為子微應用的全局變量,方便讓子微應用業務開發人員共享使用這些公共服務數據.
(2) 開發環境公共依賴共享.開發環境建立公共資源的目的是減少開發時的重復定義,并避免多微應用修改帶來的管理維護難題,因此,本文定義了圖2中的目錄規范以規約公共資源定義及引用,在微應用部署時,抽取微應用公共依賴庫避免類庫重復打包,減少打包體積,從而有效提升運行時效率.
3.3.2 實現運行時微應用上下文分離
微應用的運行時上下文資源模型可以表示為一個五元組,形式化簡述為APPCXT=<JS,CSS,BOM,T,R>,其中JS 和CSS 分別表示微應用的靜態資源,BOM 是瀏覽器對象模型,T 是頁面模板,R 表示當前微應用路由,運行時上下文表示了當前路由下BOM 負責基于頁面模板將JS 和CSS 資源解析為可交互的頁面,為了確保路由切換后的BOM 性能最優和內存占用最小化,在微應用之間導航切換時,通過監聽不同微應用端口的地址改變,在微應用生命周期的啟動及裝載兩個階段的事件觸發時,記錄下每個微應用的實時上下文資源狀態,當微應用通過路由導航切換出去時,將微應用上下文狀態還原至當前微應用生命周期開始之前的階段狀態,以避免微應用切換時歷史微應用資源未及時卸載導致的內存泄露等問題.當微應用被再次導航回來時,即時通過提取和加載之前緩存微應用上下文狀態進行上下文恢復,這樣微應用切換上下文時就有效實現了運行時JS 等資源的分離.為了保證不同微應用切換后樣式展示效果能動態無縫切換生效,在微應用加載時采用了基于HTML 入口資源的方式,在微應用卸載后,瀏覽器的特性本身會同時卸載掉該微應用的運行時樣式表,從而實現了微應用運行時樣式表的動態插入和移除,保證了切換微應用上下文后的微應用運行時上下文狀態一致性和資源分離.
為驗證本文微前端化微應用管理控制臺方案,在國網某省公司調控云公共服務管理平臺測試環境中做了案例部署和評估.案例環境搭建在3 臺8 核32 GB 內存的浪潮服務器(SA5248L)上,網關、服務注冊、工作流、權限、模型驅動、文件管理、頁面設計等微服務均采用容器化部署,容器副本數均設置為3,管理控制臺各微應用前端管理微應用采用獨立容器化部署,容器副本數均設置為2,所有微應用在容器中基于Node.js環境運行,其中工作流微應用基于Angular 技術棧實現,其它微應用基于Vue 技術棧實現.部署實例以K8S部署的Docker 容器運行.測試評估點分別從不同前端技術棧的微前端化集成、微應用通信及交互、運行時上下文資源分離、性能等方面進行驗證.瀏覽器采用Chrome78,通過Chrome 開發工具的性能分析工具記錄,跨微應用路由切換時,卸載源微應用的打點記錄日志如圖6所示,加載目標微應用打點日志如圖7所示.

圖6 跨微應用路由切換之卸載源微應用

圖7 跨微應用路由切換之加載目標微應用
在監測過程中,管理控制臺在Chrome 瀏覽器中耗費177 ms 左右完成了路由切換,切換過程貫穿了預定義的前后微應用生命周期加載和卸載等鉤子事件,操作過程符合用戶預期體驗,DOM 結構、靜態資源和微應用上下文相適配,無頁面死鏈現象,反復壓測下無瀏覽器內存泄露現象,符合預定義的測試驗證目標.
在實驗過程中,采用的容器化微應用分布式部署方式有一定技術難度,亟需通過自動化向導部署方式提升運維便捷性.
本文研究了微前端的微應用注冊和卸載、微應用通信和交互、微應用上下文資源分離等技術,在此基礎上,設計了微前端化管理控制臺解決方案,給出了基于微應用生命周期不同階段事件鉤子的微應用注冊和卸載機制、基于管理控制臺事件總線的微應用通信與交互機制、跨應用共享變量事件注入及HTML 入口的資源共享、運行時上下文資源分離機制等創新點,闡述了該方案的架構設計及關鍵實現技術.最后,以國家電網某省公司調控云公共服務管理平臺微應用集成為背景,給出了微前端化微應用管理控制臺的應用驗證評估,評估結果、效率優化及生產試運行實踐表明,該框方案提升了微服務架構下微應用管理的開發效率,實現了管理控制臺微應用即插即用機制和跨微應用之間業務操作連續性,提高了國網調控云公共服務管理平臺應用服務水平.后續將針對遺留問題持續改進優化該解決方案.