達(dá)達(dá)快送的運(yùn)營系統(tǒng),由各個業(yè)務(wù)線的子系統(tǒng)組合而成。隨著業(yè)務(wù)的高速發(fā)展,子系統(tǒng)數(shù)量呈現(xiàn)爆炸趨勢,復(fù)雜度急劇上升。2018年3月創(chuàng)建時,只有3個子系統(tǒng)。2021年3月拆分時,達(dá)到15個子系統(tǒng)。
由于各個業(yè)務(wù)線的子系統(tǒng)迭代周期不同,所以運(yùn)營系統(tǒng)每天都有上線。表現(xiàn)出來的現(xiàn)象是:排隊上線、無法回滾、構(gòu)建緩慢。
首先是排隊上線:從圖中可以看出,夜晚第1和第5個上線之間存在非常明顯的排隊等待現(xiàn)象。而且這不是純前端的等待,子系統(tǒng)涉及的后端、測試、產(chǎn)品也要跟著等。
其次是無法回滾:第2天發(fā)現(xiàn)昨晚第4個上線有問題需要回滾,只能撤銷代碼變更后重新上線,導(dǎo)致線上問題持續(xù)久。
最后是構(gòu)建緩慢:子系統(tǒng)代碼即使沒有變更,也要參與整個打包構(gòu)建過程。子系統(tǒng)越多,構(gòu)建耗時越久。
問題有多嚴(yán)重呢?2021年3月:
1.夜晚排隊上線21次2.通過重新上線來回滾,耗時28分鐘3.構(gòu)建482次,平均耗時10分鐘
解決上述問題:
1.減少排隊次數(shù)2.可以直接回滾3.構(gòu)建速度更快
從上圖變成下圖:
之所以存在排隊上線、無法回滾、構(gòu)建緩慢問題,是因為運(yùn)營系統(tǒng)的前端屬于單體應(yīng)用架構(gòu),所有子系統(tǒng)必須打包在一起后上線。
一旦某個子系統(tǒng)出現(xiàn)問題,就會造成集體回滾。所以為了避免集體回滾,子系統(tǒng)排隊上線。先上1個,觀察10分鐘,沒問題再上下1個。
這樣做的后果是,后一個上線包含前面所有上線,但是不包含接下來即將發(fā)生的上線。一旦回滾中間某次上線,在此之后上線的功能全部消失。為了避免這種情況發(fā)生,通常不直接回滾,而是撤銷代碼變更后重新上線。
每次上線,都要把所有子系統(tǒng)的代碼打包在一起。哪怕子系統(tǒng)代碼沒有變更,也要參與整個打包構(gòu)建過程。隨著業(yè)務(wù)線的增加,子系統(tǒng)數(shù)量在增加,構(gòu)建耗時也在增加。
既然根本原因是所有子系統(tǒng)必須打包成一個應(yīng)用上線,那么拆分成多個應(yīng)用獨(dú)立上線,問題不就解決了嗎?
過去就是這么做的,但是如果運(yùn)營系統(tǒng)也走這條路,會降低研發(fā)效率,破壞產(chǎn)品交互體驗。
首先是降低研發(fā)效率:代碼復(fù)用變得困難。過去代碼在同一個倉庫,直接引入就好?,F(xiàn)在代碼在不同倉庫,需要新建項目單獨(dú)維護(hù)依賴包,通過上傳和下載實現(xiàn)代碼復(fù)用,非常麻煩。
其次破壞產(chǎn)品交互體驗:這樣拆分后,子系統(tǒng)鏈接發(fā)生變化,過去能打開的鏈接現(xiàn)在打不開了。子系統(tǒng)切換方式發(fā)生變化,過去在瀏覽器中進(jìn)行,現(xiàn)在去服務(wù)器繞一圈,白屏明顯。
所以我們需要更進(jìn)一步,在運(yùn)行時把多個獨(dú)立應(yīng)用聚合成一個整體。做到開發(fā)過程解耦,用戶使用聚合。既解決了問題,又不降低研發(fā)效率或者破壞產(chǎn)品體驗。這就是微前端了。
通過微前端架構(gòu)劃分子系統(tǒng)邊界,將系統(tǒng)整體復(fù)雜度隔離到各個子系統(tǒng)中,從而避免因子系統(tǒng)迭代周期不同造成的工程協(xié)同問題。
不能降低研發(fā)效率:
1.避免代碼復(fù)用變得困難
不能破壞產(chǎn)品交互體驗:
1.避免存量鏈接打不開2.避免頁面跳轉(zhuǎn)白屏明顯
不能投入太多人力:
1.基礎(chǔ)設(shè)施有多套環(huán)境,多條鏈路,情況復(fù)雜2.業(yè)務(wù)研發(fā)排期緊張,運(yùn)營系統(tǒng)拆分成本要低
前端應(yīng)用的渲染方式,大體上可以分為3類6種:
服務(wù)器渲染,是指通過后端模版渲染的傳統(tǒng)網(wǎng)頁。應(yīng)用之間的切換和應(yīng)用內(nèi)的頁面跳轉(zhuǎn),都基于后端路由。頁面復(fù)用方面,可以是瀏覽器中通過iframe直接內(nèi)嵌HTML,也可以是服務(wù)器中通過Nginx SSI將多個HTML片段組合后返回瀏覽器。
瀏覽器渲染,是指基于React或Vue實現(xiàn)的現(xiàn)代Web應(yīng)用。用戶打開頁面后,瀏覽器從服務(wù)器獲取沒有任何頁面內(nèi)容的HTML,由HTML引入的JS動態(tài)創(chuàng)建內(nèi)容。應(yīng)用之間的切換有2種:一種是直接打開另一個應(yīng)用的HTML,基于后端路由進(jìn)行;另一種是HTML不變,直接打開另一個應(yīng)用的JS,基于前端路由進(jìn)行。頁面復(fù)用方面,2種方式都是基于React、Vue或Web Components格式的組件。
服務(wù)端渲染,是指基于React或Vue實現(xiàn)的現(xiàn)代Web應(yīng)用:在用戶第一次打開頁面時,直接由服務(wù)器運(yùn)行JS渲染好頁面內(nèi)容后返回,從而提升頁面首屏打開速度;在用戶后續(xù)與頁面交互時,直接由瀏覽器運(yùn)行JS渲染好頁面內(nèi)容,從而提升用戶行為反饋速度。應(yīng)用之間的切換有2種:一種是直接打開另一應(yīng)用的HTML,基于后端路由進(jìn)行;另一種是HTML不變,直接打開另一個應(yīng)用的JS,基于前端路由進(jìn)行,并且應(yīng)用的JS和前端路由都支持在服務(wù)器運(yùn)行。頁面復(fù)用方面,基于后端或前端路由的應(yīng)用切換,都可以使用React或Vue的組件格式,但是不能使用Web Components格式。如果應(yīng)用之間的框架不同,可以通過single-spa的Parcels實現(xiàn)跨React或Vue框架的可復(fù)用組件。
3類渲染方式中,運(yùn)營系統(tǒng)基于瀏覽器渲染,也就是圖中紅色高亮部分。如果改為服務(wù)器渲染,會破壞產(chǎn)品交互體驗。如果改為服務(wù)端渲染,會投入太多人力。因此,渲染方式不能變。
瀏覽器渲染方式中,按照應(yīng)用切換方式又可以細(xì)分為后端路由和前端路由。如果使用后端路由,會破壞產(chǎn)品交互體驗。所以使用前端路由最合適,也就是圖中綠色高亮部分。相對于現(xiàn)狀,只增加一層前端路由。
這個方向的解決方案,有2個特別流行:
1.國外開源的single-spa方案2.國內(nèi)基于single-spa封裝的qiankun方案
他們在多個方面存在差異,需要結(jié)合具體業(yè)務(wù)場景進(jìn)行技術(shù)取舍,沒有銀彈。
掛載應(yīng)用的前提是找到應(yīng)用。當(dāng)我們把一個大應(yīng)用拆分成多個小應(yīng)用之后,如何通過應(yīng)用名找到應(yīng)用動態(tài)變化的文件鏈接就變成難題。類似的問題,后端微服務(wù)架構(gòu)中也存在。每個服務(wù)名對應(yīng)一個IP列表,IP列表中的IP動態(tài)變化。后端的解決辦法是,將服務(wù)名和IP列表的對應(yīng)關(guān)系保存在配置中心,這個過程叫做服務(wù)注冊。當(dāng)需要調(diào)用服務(wù)時,從配置中心下載完整的服務(wù)列表。從中找出可用的IP,然后調(diào)用服務(wù),這個過程叫做服務(wù)發(fā)現(xiàn)。
這里的服務(wù)名就對應(yīng)著前端的應(yīng)用名,IP就對應(yīng)著應(yīng)用的文件鏈接。我們可以基于最新的瀏覽器標(biāo)準(zhǔn)Import maps和ES modules實現(xiàn)前端的服務(wù)注冊和發(fā)現(xiàn),但是這對瀏覽器版本有要求,而且需要修改腳手架的配置。所以single-spa方案的思路是,使用瀏覽器補(bǔ)丁es-module-shims,或者使用瀏覽器標(biāo)準(zhǔn)兼容的模塊加載器SystemJS。至于修改腳手架配置,對于存量項目給出主流腳手架的修改指南,對于新增項目提供官方的腳手架配置。由于我們是把一個大應(yīng)用拆成多個小應(yīng)用,而非把多個小應(yīng)用聚合成一個大應(yīng)用,所以只用修改一個大應(yīng)用的腳手架配置,剩下的克隆Git倉庫代碼即可,因此成本可接受。
如果是把多個小應(yīng)用聚合成一個大應(yīng)用,每個應(yīng)用都改腳手架配置,成本很高,所以有了qiankun基于HTML的方案。它的大致做法是,在代碼中指定應(yīng)用名對應(yīng)的HTML地址。當(dāng)需要加載應(yīng)用時,下載HTML,從中解析出文件鏈接。好處是幾乎不用修改腳手架配置,但也有一些問題:
1.需要在代碼中指定HTML的鏈接。我們有多套環(huán)境多條鏈路,且鏈路域名隨機(jī)生成,導(dǎo)致修改HTML的鏈接有些麻煩2.HTML本身會包含文件鏈接以外的信息,文件大小可能在幾KB甚至幾十KB,這會影響頁面加載耗時3.從HTML中解析出應(yīng)用入口的文件鏈接,需要引入額外的解析模塊,也會影響頁面加載耗時
由于移動設(shè)備的占比達(dá)到60%以上,期望引入微前端架構(gòu)后,頁面打開速度不因此變慢,避免頁面白屏明顯,所以Import maps + ES modules的服務(wù)注冊和發(fā)現(xiàn)方案更合適。
拆分出來的應(yīng)用,會引入一些相同的模塊。這些被依賴的公共模塊需要抽離出來,從而減少重復(fù)構(gòu)建和加載,降低代碼構(gòu)建和頁面加載的耗時。并且支持不同應(yīng)用使用同一模塊的不同版本,從而避免公共模塊的修改,導(dǎo)致所有相關(guān)應(yīng)用全部測試一遍。
對于應(yīng)用和模塊的依賴關(guān)系預(yù)先明確的應(yīng)用,可以在加載應(yīng)用時預(yù)先加載它依賴的模塊。這樣能夠解決依賴瀑布問題,優(yōu)化關(guān)鍵渲染路徑。此外,對于高頻應(yīng)用,也可以通過這種方式預(yù)先加載,不一定等到用戶點擊頁面內(nèi)的鏈接后才開始。
由于服務(wù)注冊和發(fā)現(xiàn)選擇了single-spa方案,而single-spa的推薦配置中沒有提到版本控制和預(yù)先加載,所以我們需要尋找相應(yīng)的解決方案。版本控制方面,Import maps標(biāo)準(zhǔn)的scopes能夠解決這一問題。預(yù)先加載方面,SystemJS的depcache能夠?qū)崿F(xiàn)。因此,使用SystemJS這一瀏覽器標(biāo)準(zhǔn)兼容的模塊加載器,能夠解決這2個問題。
拆分出來的應(yīng)用在運(yùn)行時,會共享同一個運(yùn)行環(huán)境,所以彼此之間可能產(chǎn)生影響,這就需要對應(yīng)用進(jìn)行隔離。隔離主要有兩種情況,同屏單應(yīng)用和同屏多應(yīng)用。
同屏單應(yīng)用主要是快照的思路。對于變量隔離,在應(yīng)用掛載前保存運(yùn)行環(huán)境的狀態(tài),在應(yīng)用卸載后恢復(fù)這個狀態(tài)。對于樣式隔離,在應(yīng)用卸載后移除或禁用引入的樣式就好。
同屏多應(yīng)用主要是沙盒的思路。對于變量隔離,通過new Function和Proxy為每個應(yīng)用創(chuàng)建一個獨(dú)立的運(yùn)行環(huán)境。對于樣式隔離,主要有3種方法:
1.給樣式名增加隨機(jī)字符串,確保樣式名唯一2.通過data attribute增加屬性選擇器,控制樣式影響范圍,實現(xiàn)類似已廢棄的瀏覽器標(biāo)準(zhǔn)Scoped CSS的效果3.通過Shadow DOM創(chuàng)建完全隔離的DOM節(jié)點,節(jié)點內(nèi)外互不影響
對于變量隔離,解決方案比較成熟,沒有太多要做的事情。對于樣式隔離,沒有成熟的解決方案,需要結(jié)合具體情況進(jìn)行取舍:
1.同屏單應(yīng)用時,基于single-spa的生命周期鉤子,實現(xiàn)應(yīng)用的樣式快照2.同屏多應(yīng)用時,組件庫樣式名固定,不區(qū)分版本,所以在多版本共存時會存在樣式污染。3種樣式隔離方法中,Shadow DOM不用改樣式名,而且能夠很好的處理CSS動畫,可以滿足需求
這樣多種方式組合起來,基于各自的優(yōu)缺點,能夠以較低的成本解決問題。
經(jīng)過一番比較和取舍,方案基本確定,以國外開源的single-spa方案為基礎(chǔ),結(jié)合業(yè)務(wù)場景進(jìn)行定制:
1.基于Import maps和SystemJS實現(xiàn)服務(wù)注冊和發(fā)現(xiàn)2.基于Import maps的scopes和depcache實現(xiàn)版本控制和預(yù)先加載3.流量分發(fā)就用框架無關(guān)的前端路由single-spa4.變量隔離基于成熟的沙盒方案實現(xiàn)5.樣式隔離基于快照和沙盒方案組合實現(xiàn)6.應(yīng)用間通信基于瀏覽器自定義事件實現(xiàn)
方案確定后,接下來是具體實踐。
京東物流遼寧省京東幫服資源招商
1400 閱讀年營收2萬億、凈利潤下滑至90億,大宗供應(yīng)鏈五巨頭業(yè)績出爐!
1051 閱讀兩大物流國企成立合資公司,意欲何為?
970 閱讀行業(yè)首創(chuàng)!52名卡友數(shù)字人集體亮相
912 閱讀深圳首發(fā)!順豐同城與肯德基推出無人車智能配送服務(wù)
783 閱讀破局與重生:傳統(tǒng)國際貨代如何通過數(shù)字化轉(zhuǎn)型實現(xiàn)戰(zhàn)略突圍
775 閱讀AI賦能車輪上的聲音 路歌第十一屆“5·2卡友節(jié)”圓滿舉辦
707 閱讀運(yùn)滿滿江浙滬上線“即時單”業(yè)務(wù),打造極速貨運(yùn)新體驗
741 閱讀物流企業(yè)銷售激勵背后的秘密
651 閱讀關(guān)稅大戰(zhàn)遇上全球供應(yīng)鏈:蘋果公司深度研究與戰(zhàn)略推演
632 閱讀