達達快送的運營系統,由各個業務線的子系統組合而成。隨著業務的高速發展,子系統數量呈現爆炸趨勢,復雜度急劇上升。2018年3月創建時,只有3個子系統。2021年3月拆分時,達到15個子系統。
由于各個業務線的子系統迭代周期不同,所以運營系統每天都有上線。表現出來的現象是:排隊上線、無法回滾、構建緩慢。
首先是排隊上線:從圖中可以看出,夜晚第1和第5個上線之間存在非常明顯的排隊等待現象。而且這不是純前端的等待,子系統涉及的后端、測試、產品也要跟著等。
其次是無法回滾:第2天發現昨晚第4個上線有問題需要回滾,只能撤銷代碼變更后重新上線,導致線上問題持續久。
最后是構建緩慢:子系統代碼即使沒有變更,也要參與整個打包構建過程。子系統越多,構建耗時越久。
問題有多嚴重呢?2021年3月:
1.夜晚排隊上線21次2.通過重新上線來回滾,耗時28分鐘3.構建482次,平均耗時10分鐘
解決上述問題:
1.減少排隊次數2.可以直接回滾3.構建速度更快
從上圖變成下圖:
之所以存在排隊上線、無法回滾、構建緩慢問題,是因為運營系統的前端屬于單體應用架構,所有子系統必須打包在一起后上線。
一旦某個子系統出現問題,就會造成集體回滾。所以為了避免集體回滾,子系統排隊上線。先上1個,觀察10分鐘,沒問題再上下1個。
這樣做的后果是,后一個上線包含前面所有上線,但是不包含接下來即將發生的上線。一旦回滾中間某次上線,在此之后上線的功能全部消失。為了避免這種情況發生,通常不直接回滾,而是撤銷代碼變更后重新上線。
每次上線,都要把所有子系統的代碼打包在一起。哪怕子系統代碼沒有變更,也要參與整個打包構建過程。隨著業務線的增加,子系統數量在增加,構建耗時也在增加。
既然根本原因是所有子系統必須打包成一個應用上線,那么拆分成多個應用獨立上線,問題不就解決了嗎?
過去就是這么做的,但是如果運營系統也走這條路,會降低研發效率,破壞產品交互體驗。
首先是降低研發效率:代碼復用變得困難。過去代碼在同一個倉庫,直接引入就好。現在代碼在不同倉庫,需要新建項目單獨維護依賴包,通過上傳和下載實現代碼復用,非常麻煩。
其次破壞產品交互體驗:這樣拆分后,子系統鏈接發生變化,過去能打開的鏈接現在打不開了。子系統切換方式發生變化,過去在瀏覽器中進行,現在去服務器繞一圈,白屏明顯。
所以我們需要更進一步,在運行時把多個獨立應用聚合成一個整體。做到開發過程解耦,用戶使用聚合。既解決了問題,又不降低研發效率或者破壞產品體驗。這就是微前端了。
通過微前端架構劃分子系統邊界,將系統整體復雜度隔離到各個子系統中,從而避免因子系統迭代周期不同造成的工程協同問題。
不能降低研發效率:
1.避免代碼復用變得困難
不能破壞產品交互體驗:
1.避免存量鏈接打不開2.避免頁面跳轉白屏明顯
不能投入太多人力:
1.基礎設施有多套環境,多條鏈路,情況復雜2.業務研發排期緊張,運營系統拆分成本要低
前端應用的渲染方式,大體上可以分為3類6種:
服務器渲染,是指通過后端模版渲染的傳統網頁。應用之間的切換和應用內的頁面跳轉,都基于后端路由。頁面復用方面,可以是瀏覽器中通過iframe直接內嵌HTML,也可以是服務器中通過Nginx SSI將多個HTML片段組合后返回瀏覽器。
瀏覽器渲染,是指基于React或Vue實現的現代Web應用。用戶打開頁面后,瀏覽器從服務器獲取沒有任何頁面內容的HTML,由HTML引入的JS動態創建內容。應用之間的切換有2種:一種是直接打開另一個應用的HTML,基于后端路由進行;另一種是HTML不變,直接打開另一個應用的JS,基于前端路由進行。頁面復用方面,2種方式都是基于React、Vue或Web Components格式的組件。
服務端渲染,是指基于React或Vue實現的現代Web應用:在用戶第一次打開頁面時,直接由服務器運行JS渲染好頁面內容后返回,從而提升頁面首屏打開速度;在用戶后續與頁面交互時,直接由瀏覽器運行JS渲染好頁面內容,從而提升用戶行為反饋速度。應用之間的切換有2種:一種是直接打開另一應用的HTML,基于后端路由進行;另一種是HTML不變,直接打開另一個應用的JS,基于前端路由進行,并且應用的JS和前端路由都支持在服務器運行。頁面復用方面,基于后端或前端路由的應用切換,都可以使用React或Vue的組件格式,但是不能使用Web Components格式。如果應用之間的框架不同,可以通過single-spa的Parcels實現跨React或Vue框架的可復用組件。
3類渲染方式中,運營系統基于瀏覽器渲染,也就是圖中紅色高亮部分。如果改為服務器渲染,會破壞產品交互體驗。如果改為服務端渲染,會投入太多人力。因此,渲染方式不能變。
瀏覽器渲染方式中,按照應用切換方式又可以細分為后端路由和前端路由。如果使用后端路由,會破壞產品交互體驗。所以使用前端路由最合適,也就是圖中綠色高亮部分。相對于現狀,只增加一層前端路由。
這個方向的解決方案,有2個特別流行:
1.國外開源的single-spa方案2.國內基于single-spa封裝的qiankun方案
他們在多個方面存在差異,需要結合具體業務場景進行技術取舍,沒有銀彈。
掛載應用的前提是找到應用。當我們把一個大應用拆分成多個小應用之后,如何通過應用名找到應用動態變化的文件鏈接就變成難題。類似的問題,后端微服務架構中也存在。每個服務名對應一個IP列表,IP列表中的IP動態變化。后端的解決辦法是,將服務名和IP列表的對應關系保存在配置中心,這個過程叫做服務注冊。當需要調用服務時,從配置中心下載完整的服務列表。從中找出可用的IP,然后調用服務,這個過程叫做服務發現。
這里的服務名就對應著前端的應用名,IP就對應著應用的文件鏈接。我們可以基于最新的瀏覽器標準Import maps和ES modules實現前端的服務注冊和發現,但是這對瀏覽器版本有要求,而且需要修改腳手架的配置。所以single-spa方案的思路是,使用瀏覽器補丁es-module-shims,或者使用瀏覽器標準兼容的模塊加載器SystemJS。至于修改腳手架配置,對于存量項目給出主流腳手架的修改指南,對于新增項目提供官方的腳手架配置。由于我們是把一個大應用拆成多個小應用,而非把多個小應用聚合成一個大應用,所以只用修改一個大應用的腳手架配置,剩下的克隆Git倉庫代碼即可,因此成本可接受。
如果是把多個小應用聚合成一個大應用,每個應用都改腳手架配置,成本很高,所以有了qiankun基于HTML的方案。它的大致做法是,在代碼中指定應用名對應的HTML地址。當需要加載應用時,下載HTML,從中解析出文件鏈接。好處是幾乎不用修改腳手架配置,但也有一些問題:
1.需要在代碼中指定HTML的鏈接。我們有多套環境多條鏈路,且鏈路域名隨機生成,導致修改HTML的鏈接有些麻煩2.HTML本身會包含文件鏈接以外的信息,文件大小可能在幾KB甚至幾十KB,這會影響頁面加載耗時3.從HTML中解析出應用入口的文件鏈接,需要引入額外的解析模塊,也會影響頁面加載耗時
由于移動設備的占比達到60%以上,期望引入微前端架構后,頁面打開速度不因此變慢,避免頁面白屏明顯,所以Import maps + ES modules的服務注冊和發現方案更合適。
拆分出來的應用,會引入一些相同的模塊。這些被依賴的公共模塊需要抽離出來,從而減少重復構建和加載,降低代碼構建和頁面加載的耗時。并且支持不同應用使用同一模塊的不同版本,從而避免公共模塊的修改,導致所有相關應用全部測試一遍。
對于應用和模塊的依賴關系預先明確的應用,可以在加載應用時預先加載它依賴的模塊。這樣能夠解決依賴瀑布問題,優化關鍵渲染路徑。此外,對于高頻應用,也可以通過這種方式預先加載,不一定等到用戶點擊頁面內的鏈接后才開始。
由于服務注冊和發現選擇了single-spa方案,而single-spa的推薦配置中沒有提到版本控制和預先加載,所以我們需要尋找相應的解決方案。版本控制方面,Import maps標準的scopes能夠解決這一問題。預先加載方面,SystemJS的depcache能夠實現。因此,使用SystemJS這一瀏覽器標準兼容的模塊加載器,能夠解決這2個問題。
拆分出來的應用在運行時,會共享同一個運行環境,所以彼此之間可能產生影響,這就需要對應用進行隔離。隔離主要有兩種情況,同屏單應用和同屏多應用。
同屏單應用主要是快照的思路。對于變量隔離,在應用掛載前保存運行環境的狀態,在應用卸載后恢復這個狀態。對于樣式隔離,在應用卸載后移除或禁用引入的樣式就好。
同屏多應用主要是沙盒的思路。對于變量隔離,通過new Function和Proxy為每個應用創建一個獨立的運行環境。對于樣式隔離,主要有3種方法:
1.給樣式名增加隨機字符串,確保樣式名唯一2.通過data attribute增加屬性選擇器,控制樣式影響范圍,實現類似已廢棄的瀏覽器標準Scoped CSS的效果3.通過Shadow DOM創建完全隔離的DOM節點,節點內外互不影響
對于變量隔離,解決方案比較成熟,沒有太多要做的事情。對于樣式隔離,沒有成熟的解決方案,需要結合具體情況進行取舍:
1.同屏單應用時,基于single-spa的生命周期鉤子,實現應用的樣式快照2.同屏多應用時,組件庫樣式名固定,不區分版本,所以在多版本共存時會存在樣式污染。3種樣式隔離方法中,Shadow DOM不用改樣式名,而且能夠很好的處理CSS動畫,可以滿足需求
這樣多種方式組合起來,基于各自的優缺點,能夠以較低的成本解決問題。
經過一番比較和取舍,方案基本確定,以國外開源的single-spa方案為基礎,結合業務場景進行定制:
1.基于Import maps和SystemJS實現服務注冊和發現2.基于Import maps的scopes和depcache實現版本控制和預先加載3.流量分發就用框架無關的前端路由single-spa4.變量隔離基于成熟的沙盒方案實現5.樣式隔離基于快照和沙盒方案組合實現6.應用間通信基于瀏覽器自定義事件實現
方案確定后,接下來是具體實踐。
2024最值錢的物流上市企業是誰?哪些物流企業被看好,哪些被看跌?
1807 閱讀連續5年的“春節主力軍”,德邦為何如此穩?
1450 閱讀CES 2025:NVIDIA OMNIVERSE驅動的智能倉儲數字孿生革命
1154 閱讀前海粵十完成新一輪戰略融資
1068 閱讀拼多多引領電商西進:帝王蟹進村,非遺剪紙出山
1066 閱讀制造業企業,不要逼物流公司降價了!
1005 閱讀AI改變物流業的游戲規則:從炒作到實踐的深度思考
1024 閱讀樂歌股份預計2024年歸母凈利潤下降約50%,大力發展海外倉
995 閱讀2024年12月份中國出口集裝箱運輸市場分析報告
992 閱讀菜鳥拆分為假消息,繼續大力發展全球物流業務
938 閱讀