劉一田,曹一鳴
(南京南瑞信息通信科技有限公司,南京 210003)
傳統(tǒng)的服務(wù)管理控制臺和應(yīng)用門戶在面臨業(yè)務(wù)快速發(fā)展之后,出現(xiàn)了單體應(yīng)用進化成巨石應(yīng)用的問題,隨著微服務(wù)架構(gòu)的廣泛應(yīng)用,業(yè)務(wù)應(yīng)用被劃分為更小顆粒度的微應(yīng)用,不同的微應(yīng)用可以采用不同的技術(shù)棧實現(xiàn),更好地提升了業(yè)務(wù)開發(fā)效率和需求響應(yīng)能力,從企業(yè)級管理需求角度,管理控制臺需要聚合微應(yīng)用并提供管理入口,支持租戶按需定制SaaS 層應(yīng)用門戶,對已在線上運行的項目,支持低成本地接入微應(yīng)用門戶,而不需要對現(xiàn)有開發(fā)和部署流程做大量兼容改造;支持不同技術(shù)棧微應(yīng)用分布式開發(fā)部署和運行時上下文資源分離,但允許開發(fā)階段微應(yīng)用之間良好聯(lián)調(diào),能保證單頁應(yīng)用的操作流程不中斷和體驗流暢性.目前,微應(yīng)用管理控制臺及業(yè)務(wù)服務(wù)微應(yīng)用的前端技術(shù)棧實現(xiàn)是主流的Vue、React、Angular 等前端框架,為了解決上述問題,主流解決方案有3 種:(1)基于iframe 的頁面嵌入.將不同的微應(yīng)用的入口頁面通過iframe 嵌入到門戶菜單中.集成方式簡單,缺點是頁面切換無法保持路由狀態(tài),父子應(yīng)用通信不穩(wěn)定,存在潛在性能瓶頸.(2)基于Nginx 導(dǎo)航路由.基于Nginx 中微應(yīng)用路由前綴的不同將請求路由到不同的微應(yīng)用.優(yōu)點是支持微應(yīng)用的技術(shù)棧獨立,缺點是頁面跳轉(zhuǎn)有時會閃屏,動態(tài)擴展需變更Nginx 配置.(3)基于微前端[1]Single-SPA 框架[2].即通過由獨立交付的多個微應(yīng)用動態(tài)組成整體管理控制臺的架構(gòu)模式.將前端應(yīng)用分解成一些更小、更簡單的能夠獨立開發(fā)、測試、部署的小塊,從用戶角度仍然是無縫管理體驗.管理控制臺應(yīng)用異步加載嵌入其內(nèi)部的微應(yīng)用的入口文件,動態(tài)創(chuàng)建微應(yīng)用裝載點.優(yōu)點是支持微應(yīng)用的開發(fā)技術(shù)棧獨立,缺點是微應(yīng)用之間開發(fā)聯(lián)調(diào)復(fù)雜.文獻[3]描述的微前端應(yīng)用以JS 文件入口方式實現(xiàn)微應(yīng)用加載,這種方式會導(dǎo)致微應(yīng)用之間運行時資源上下文混淆,增加了開發(fā)和維護的復(fù)雜度.
綜合上述問題及方案,結(jié)合項目中微應(yīng)用數(shù)量不斷迭代遞增、父子微應(yīng)用通信、微應(yīng)用資源按需加載和卸載、運行時上下文資源分離等需求,本文給出了微前端化管理管理控制臺的解決方案.(1) 提供基于管理控制臺消息總線的路由注冊機制,管理各個微應(yīng)用的生命周期,傳遞路由消息.(2) 定義微應(yīng)用渲染入口規(guī)范,提供微應(yīng)用注冊接入機制.(3) 提供微應(yīng)用加載工具庫,監(jiān)聽微應(yīng)用地址端口路由變化并快速加載或卸載微應(yīng)用,加載時讀取微應(yīng)用配置并為微應(yīng)用的實例提供門戶占位裝載點,卸載時反向移除微應(yīng)用相關(guān)資源.
微前端管理控制臺由管理門戶和微服務(wù)對應(yīng)的管理微應(yīng)用組成,如圖1所示.它們共享公共依賴庫以實現(xiàn)共享數(shù)據(jù)和資源依賴,進而降低開發(fā)管理成本,管理控制的權(quán)限刷新令牌采用REDIS 集群存儲,訪問令牌基于本地緩存存儲[4].本文中討論的微應(yīng)用基于Node.js環(huán)境部署運行,文獻[5]論證了前端應(yīng)用基于Node.js環(huán)境在微應(yīng)用系統(tǒng)運行期的相對優(yōu)勢.
圖1 微前端管理控制臺框架
微前端化管理控制臺的交互過程及關(guān)鍵處理機制包含分為以下7 個方面.
(1) 用戶登錄.用戶訪問管理門戶首頁,管理門戶鑒別是否已存儲已登錄的本地令牌,如無則跳轉(zhuǎn)到單點登錄頁面.
(2) 令牌存儲.登錄成功后分別記錄用戶訪問令牌到本地緩存,記錄刷新令牌到REDIS 中,其中REDIS采用集群部署方式.
(3)管理門戶微應(yīng)用啟動時加載所管理的微應(yīng)用配置.配置包括微應(yīng)用名稱、頁面入口地址和渲染方法引用,認證登錄成功后根據(jù)配置跳轉(zhuǎn)到默認微應(yīng)用頁面.
(4) 微應(yīng)用注冊及加載.微應(yīng)用注冊時,采用HTML作為集成入口,避免JS 入口方式資源未完全加載而異步構(gòu)建子微應(yīng)用容器節(jié)點,可能導(dǎo)致渲染失敗等問題,微應(yīng)用運行加載時,管理門戶微應(yīng)用注冊模塊通過提取HTML 獲取當(dāng)前子微應(yīng)用的靜態(tài)資源,同時將微應(yīng)用HTML 文檔作為子模板節(jié)點添加到管理門戶的頁面模板容器中,繼而觸發(fā)微應(yīng)用頁面渲染.
(5) 管理路由事件.提供導(dǎo)航路由模塊,通過監(jiān)聽不同微應(yīng)用的啟動端口偵測瀏覽器地址變化事件,在所管理的微應(yīng)用路由觸發(fā)或切換時,在緩存中保留切換前的微應(yīng)用完整的環(huán)境上下文狀態(tài),動態(tài)加載目標(biāo)路由微應(yīng)用,待再次路由返回時還原之前狀態(tài)的微應(yīng)用上下文運行時資源,確保微應(yīng)用切換時良好交互體驗.
(6) 微應(yīng)用模塊跨應(yīng)用依賴.為了更好地管理微應(yīng)用,增加微應(yīng)用的生命周期事件監(jiān)聽及相應(yīng)處理函數(shù),并通過Webpack 的umd 打包格式中的全局導(dǎo)出模式導(dǎo)出微應(yīng)用的生命周期事件監(jiān)聽[6],從而獲取微應(yīng)用的內(nèi)置模塊導(dǎo)出和跨微應(yīng)用模塊導(dǎo)入.
(7) 微應(yīng)用資源邊界上下文分離.監(jiān)聽微應(yīng)用的加載和卸載事件,在門戶路由系統(tǒng)跨微應(yīng)用切換時,在監(jiān)聽的路由事件中重新加載或卸載微應(yīng)用上下文中的JS 腳本和樣式表等資源.
圖1中微前端化管理控制臺由管理門戶微應(yīng)用和眾多業(yè)務(wù)管理微應(yīng)用組成,管理門戶采用Vue2+ElementUI[7]實現(xiàn),其中管理門戶微應(yīng)用作為父應(yīng)用負責(zé)其它微應(yīng)用的注冊和卸載,管理門戶微應(yīng)用目錄結(jié)構(gòu)如圖2所示,微應(yīng)用基于模塊化工程方式構(gòu)建[8],父應(yīng)用基于HTML5規(guī)范的歷史記錄對象狀態(tài)變更機制觸發(fā)微應(yīng)用路由的切換,在父應(yīng)用視圖模板中置入子微應(yīng)用的宿主容器,作為子微應(yīng)用在父應(yīng)用容器中的占位符.并需要實現(xiàn)以下3 種框架機制:(1) 微應(yīng)用注冊、加載和卸載機制;(2) 微應(yīng)用通信及交互機制;(3) 微應(yīng)用資源共享和運行時上下文資源分離機制.
圖2 微前端化微應(yīng)用管理控制臺工程目錄
由于控制臺管理采用延遲加載微應(yīng)用模式,當(dāng)瀏覽器重新加載時,管理控制臺的微應(yīng)用資源也會重新被異步再次加載,由于此時門戶的路由導(dǎo)航已經(jīng)觸發(fā)微應(yīng)用生命周期啟動階段事件,但微應(yīng)用運行時上下文JS 腳本和樣式等靜態(tài)資源不能確保已經(jīng)全部加載和正常解析完畢,間接導(dǎo)致了路由記錄緩存里找不到相對應(yīng)的導(dǎo)航規(guī)則,最終導(dǎo)致路由切換時出現(xiàn)異常而無法正常展示
因此,注冊微應(yīng)用時,需要基于管理控制臺前端事件總線,和父子微應(yīng)用生命周期不同階段鉤子事件實現(xiàn)父子微應(yīng)用及子微應(yīng)用之間的注冊、加載、卸載等交互,管理微應(yīng)用運行時上下文狀態(tài)緩存,微應(yīng)用注冊過程的形式化描述如圖3所示.
圖3 微應(yīng)用注冊過程
首先,在父應(yīng)用中根據(jù)微應(yīng)用配置規(guī)約定義每個子微應(yīng)用的加載入口頁面HTML 資源屬性、渲染及路由方法、微應(yīng)用生命周期事件等;父微應(yīng)用需要先加載每個子微應(yīng)用的入口資源,待入口資源加載完畢,確保子微應(yīng)用的路由系統(tǒng)注冊進父微應(yīng)用之后,再由子微應(yīng)用內(nèi)部的路由系統(tǒng)接管路由改變事件.同時在子微應(yīng)用路由卸載時,父微應(yīng)用觸發(fā)相應(yīng)的銷毀事件,子微應(yīng)用在監(jiān)聽到該事件時,調(diào)用自己的卸載方法卸載自身運行時上下文資源;其次,設(shè)置默認的子微應(yīng)用作為父應(yīng)用的默認路由規(guī)則,對默認微應(yīng)用進行預(yù)加載,并定義默認微應(yīng)用加載完成后的回調(diào)事件以支持父微應(yīng)用完成后的業(yè)務(wù)定制擴展;最后,啟動全局路由,根據(jù)全局路由規(guī)則切換微應(yīng)用上下文資源、渲染微應(yīng)用展現(xiàn)和卸載清理.
微應(yīng)用之間的通信及交互分為父子微應(yīng)用、微應(yīng)用內(nèi)部、微應(yīng)用之間等3 種通信及交互機制.其中父子微應(yīng)用及微應(yīng)用之間的交互基于管理控制臺事件總線實現(xiàn),如圖4所示.
圖4 跨微應(yīng)用通信及交互
3.2.1 父子微應(yīng)用通信
(1) 父微應(yīng)用傳遞子微應(yīng)用的方式.父微應(yīng)用中注冊子微應(yīng)用時,在子微應(yīng)用的配置中加入消息實體屬性,通過該屬性值的修改,動態(tài)將消息通知給子微應(yīng)用.
(2) 子微應(yīng)用傳遞父微應(yīng)用的方式.子微應(yīng)用中導(dǎo)出掛載前回調(diào)事件,在事件參數(shù)中,將子微應(yīng)用待傳遞的參數(shù)對象置入,發(fā)布到父微應(yīng)用的事件總線上,父微應(yīng)用監(jiān)聽到消息后,從其事件總線上獲取子微應(yīng)用的數(shù)據(jù)對象.形式化描述如圖5所示.
圖5 父子微應(yīng)用通信機制
3.2.2 微應(yīng)用內(nèi)部及之間通信
定義基于微應(yīng)用管理門戶主窗體的對象全局變量,在子微應(yīng)用內(nèi)部匹配切換路由時,路由管理通過Vue Router 實現(xiàn),路由設(shè)置為HTML5 歷史模式,如果偵聽到路由中包含了全局變量中指定的微應(yīng)用路由,則進行跨微應(yīng)用切換,否則進行微應(yīng)用內(nèi)部頁面的路由切換.
當(dāng)源微應(yīng)用準(zhǔn)備發(fā)消息給目標(biāo)微應(yīng)用時,通過父微應(yīng)用的事件消息總線進行指定主題事件的訂閱和發(fā)布,當(dāng)源微應(yīng)用完成指定操作后要跳轉(zhuǎn)到目標(biāo)微應(yīng)用時,則通過瀏覽器對象模型提供的歷史記錄對象的狀態(tài)改變方法,傳入目標(biāo)微應(yīng)用名稱和入口參數(shù),觸發(fā)微應(yīng)用之間路由的強制跳轉(zhuǎn)和消息通信傳遞.
3.3.1 實現(xiàn)微應(yīng)用資源及數(shù)據(jù)共享機制
(1) 運行時數(shù)據(jù)共享.全局的數(shù)據(jù)一般會存儲在父微應(yīng)用中,然后子微應(yīng)用可以直接共享,比如:當(dāng)前登錄用戶及隸屬組織、國際和本地化配置、API 令牌等,簡單的數(shù)據(jù)共享可以直接掛載在門戶微應(yīng)用主窗體上即可,為了讓每個子微應(yīng)用使用全局服務(wù)和模塊內(nèi)服務(wù)一致,通過在父微應(yīng)用中實例化這些服務(wù),然后在每個子微應(yīng)用的入口模塊中將該實例引用設(shè)置為子微應(yīng)用的全局變量,方便讓子微應(yīng)用業(yè)務(wù)開發(fā)人員共享使用這些公共服務(wù)數(shù)據(jù).
(2) 開發(fā)環(huán)境公共依賴共享.開發(fā)環(huán)境建立公共資源的目的是減少開發(fā)時的重復(fù)定義,并避免多微應(yīng)用修改帶來的管理維護難題,因此,本文定義了圖2中的目錄規(guī)范以規(guī)約公共資源定義及引用,在微應(yīng)用部署時,抽取微應(yīng)用公共依賴庫避免類庫重復(fù)打包,減少打包體積,從而有效提升運行時效率.
3.3.2 實現(xiàn)運行時微應(yīng)用上下文分離
微應(yīng)用的運行時上下文資源模型可以表示為一個五元組,形式化簡述為APPCXT=<JS,CSS,BOM,T,R>,其中JS 和CSS 分別表示微應(yīng)用的靜態(tài)資源,BOM 是瀏覽器對象模型,T 是頁面模板,R 表示當(dāng)前微應(yīng)用路由,運行時上下文表示了當(dāng)前路由下BOM 負責(zé)基于頁面模板將JS 和CSS 資源解析為可交互的頁面,為了確保路由切換后的BOM 性能最優(yōu)和內(nèi)存占用最小化,在微應(yīng)用之間導(dǎo)航切換時,通過監(jiān)聽不同微應(yīng)用端口的地址改變,在微應(yīng)用生命周期的啟動及裝載兩個階段的事件觸發(fā)時,記錄下每個微應(yīng)用的實時上下文資源狀態(tài),當(dāng)微應(yīng)用通過路由導(dǎo)航切換出去時,將微應(yīng)用上下文狀態(tài)還原至當(dāng)前微應(yīng)用生命周期開始之前的階段狀態(tài),以避免微應(yīng)用切換時歷史微應(yīng)用資源未及時卸載導(dǎo)致的內(nèi)存泄露等問題.當(dāng)微應(yīng)用被再次導(dǎo)航回來時,即時通過提取和加載之前緩存微應(yīng)用上下文狀態(tài)進行上下文恢復(fù),這樣微應(yīng)用切換上下文時就有效實現(xiàn)了運行時JS 等資源的分離.為了保證不同微應(yīng)用切換后樣式展示效果能動態(tài)無縫切換生效,在微應(yīng)用加載時采用了基于HTML 入口資源的方式,在微應(yīng)用卸載后,瀏覽器的特性本身會同時卸載掉該微應(yīng)用的運行時樣式表,從而實現(xiàn)了微應(yīng)用運行時樣式表的動態(tài)插入和移除,保證了切換微應(yīng)用上下文后的微應(yīng)用運行時上下文狀態(tài)一致性和資源分離.
為驗證本文微前端化微應(yīng)用管理控制臺方案,在國網(wǎng)某省公司調(diào)控云公共服務(wù)管理平臺測試環(huán)境中做了案例部署和評估.案例環(huán)境搭建在3 臺8 核32 GB 內(nèi)存的浪潮服務(wù)器(SA5248L)上,網(wǎng)關(guān)、服務(wù)注冊、工作流、權(quán)限、模型驅(qū)動、文件管理、頁面設(shè)計等微服務(wù)均采用容器化部署,容器副本數(shù)均設(shè)置為3,管理控制臺各微應(yīng)用前端管理微應(yīng)用采用獨立容器化部署,容器副本數(shù)均設(shè)置為2,所有微應(yīng)用在容器中基于Node.js環(huán)境運行,其中工作流微應(yīng)用基于Angular 技術(shù)棧實現(xiàn),其它微應(yīng)用基于Vue 技術(shù)棧實現(xiàn).部署實例以K8S部署的Docker 容器運行.測試評估點分別從不同前端技術(shù)棧的微前端化集成、微應(yīng)用通信及交互、運行時上下文資源分離、性能等方面進行驗證.瀏覽器采用Chrome78,通過Chrome 開發(fā)工具的性能分析工具記錄,跨微應(yīng)用路由切換時,卸載源微應(yīng)用的打點記錄日志如圖6所示,加載目標(biāo)微應(yīng)用打點日志如圖7所示.
圖6 跨微應(yīng)用路由切換之卸載源微應(yīng)用
圖7 跨微應(yīng)用路由切換之加載目標(biāo)微應(yīng)用
在監(jiān)測過程中,管理控制臺在Chrome 瀏覽器中耗費177 ms 左右完成了路由切換,切換過程貫穿了預(yù)定義的前后微應(yīng)用生命周期加載和卸載等鉤子事件,操作過程符合用戶預(yù)期體驗,DOM 結(jié)構(gòu)、靜態(tài)資源和微應(yīng)用上下文相適配,無頁面死鏈現(xiàn)象,反復(fù)壓測下無瀏覽器內(nèi)存泄露現(xiàn)象,符合預(yù)定義的測試驗證目標(biāo).
在實驗過程中,采用的容器化微應(yīng)用分布式部署方式有一定技術(shù)難度,亟需通過自動化向?qū)Р渴鸱绞教嵘\維便捷性.
本文研究了微前端的微應(yīng)用注冊和卸載、微應(yīng)用通信和交互、微應(yīng)用上下文資源分離等技術(shù),在此基礎(chǔ)上,設(shè)計了微前端化管理控制臺解決方案,給出了基于微應(yīng)用生命周期不同階段事件鉤子的微應(yīng)用注冊和卸載機制、基于管理控制臺事件總線的微應(yīng)用通信與交互機制、跨應(yīng)用共享變量事件注入及HTML 入口的資源共享、運行時上下文資源分離機制等創(chuàng)新點,闡述了該方案的架構(gòu)設(shè)計及關(guān)鍵實現(xiàn)技術(shù).最后,以國家電網(wǎng)某省公司調(diào)控云公共服務(wù)管理平臺微應(yīng)用集成為背景,給出了微前端化微應(yīng)用管理控制臺的應(yīng)用驗證評估,評估結(jié)果、效率優(yōu)化及生產(chǎn)試運行實踐表明,該框方案提升了微服務(wù)架構(gòu)下微應(yīng)用管理的開發(fā)效率,實現(xiàn)了管理控制臺微應(yīng)用即插即用機制和跨微應(yīng)用之間業(yè)務(wù)操作連續(xù)性,提高了國網(wǎng)調(diào)控云公共服務(wù)管理平臺應(yīng)用服務(wù)水平.后續(xù)將針對遺留問題持續(xù)改進優(yōu)化該解決方案.