前端大規(guī)模構(gòu)建演進(jìn)實(shí)踐
掌⻔教育⾃動(dòng)化構(gòu)建歷程
在業(yè)內(nèi)前端構(gòu)建,⼀般分為三種:
- ⼿動(dòng)觸發(fā)構(gòu)建:這個(gè)階段⾮常原始,需要我們⾃⼰在本地進(jìn)⾏ git pull/npm install/npm run build 等 等操作,也容易出現(xiàn)問題;
- 虛擬機(jī) Jenkins 集群分布式構(gòu)建:通過 Master 將任務(wù)分配到對(duì)應(yīng)的 Slave 機(jī)器上執(zhí)⾏構(gòu)建,能極⼤ 的均衡資源,利⽤性能,同時(shí)解放雙⼿;
- 容器集群構(gòu)建:容器構(gòu)建,鏡像發(fā)布,可以進(jìn)⼀步的節(jié)約資源;
不過掌⻔教育在 2019 年之前,前端研發(fā)更多是在本地進(jìn)⾏構(gòu)建,再通過運(yùn)維的腳本來進(jìn)⾏部署,也容易 導(dǎo)致出現(xiàn)⽣產(chǎn)故障。所以我們收集反饋,結(jié)合實(shí)際情況,開發(fā)出 v1.0 構(gòu)建模式,也取得了很好的成果。但 并沒有以此就認(rèn)為⾼枕⽆憂,也對(duì)很多痛點(diǎn)進(jìn)⾏持續(xù)的優(yōu)化,最后迭代出 v2.0 的⽅案。在這個(gè)過程中,前 端業(yè)務(wù)壯⼤,CI 構(gòu)建經(jīng)過 400+ 多應(yīng)⽤,每周 2000+ 次構(gòu)建,300+ 次的⽣產(chǎn)發(fā)布的⼤考,持續(xù)的成 ⻓。
v1.0 ⾯對(duì)的挑戰(zhàn)
v1.0 前端構(gòu)建狀況,⼀種是通過 webhook 來觸發(fā)流⽔線構(gòu)建,第⼆種是通過在 cd 上新建構(gòu)建單來觸構(gòu) 建。如果構(gòu)建任務(wù)⽐較多,按照單臺(tái)機(jī)器的是遠(yuǎn)遠(yuǎn)不夠,在這種情況下就需要借助 Jenkins 的 Master/Slave 的主從模式,來解決服務(wù)器的資源壓⼒。讓 Master 的服務(wù)器來進(jìn)⾏調(diào)度資源,指定空閑 Slave 機(jī)器進(jìn)⾏構(gòu)建。當(dāng) Slave 機(jī)器上構(gòu)建任務(wù)滿了,構(gòu)建任務(wù)繼續(xù)在 Master 排隊(duì)池中繼續(xù)等待,等 Slave 空閑后,再進(jìn)⾏分配。
存在的挑戰(zhàn)
v1.0 不是最好的⽅案,同時(shí)暴露出第⼀次構(gòu)建慢、錯(cuò)誤⽇志反饋不明確等問題,另外⼀點(diǎn)就是 job 維護(hù)困 難。要解決這些問題,就需要重新開始,重新設(shè)計(jì)。
⾸先就是 JOB 維護(hù)困難,v1.0 的任務(wù)模式是多個(gè)應(yīng)⽤對(duì)應(yīng) 1 個(gè) job,這就導(dǎo)致⼏個(gè)問題,如果 job 發(fā)版 導(dǎo)致掛了,影響到全部。假如需要復(fù)⽤該 job,進(jìn)⾏定制化開發(fā)也⽐較困難。
其次第⼀次構(gòu)建慢的問題,更多在資源調(diào)度上和⽆法復(fù)⽤ Workspace,根據(jù)之前的資源調(diào)度模式,當(dāng)我們 把任務(wù)分配到 A 機(jī)器,該任務(wù)被執(zhí)⾏成功,那么下次的任務(wù)也會(huì)⾛⼊到這臺(tái) A 機(jī)器。以此觀察,就會(huì)發(fā) 現(xiàn)⼤部分任務(wù)都會(huì)優(yōu)先去搶占 A 機(jī)器。這家就導(dǎo)致了⼏個(gè)問題:
- 資源調(diào)度不均衡;
- npm 緩存越來越⼤;
最后是⽆法復(fù)⽤ Workspace 模式,在 v1.0 情況下,不復(fù)⽤ Workspace 模式是會(huì)帶來以下優(yōu)勢(shì):保證 node_module ⽆污染問題,同時(shí)也避免了 npm run build 的各種因?yàn)?node_module 包污染的問題,導(dǎo) 致的意外錯(cuò)誤。所以在 v2.0 就需要應(yīng)對(duì)污染的問題。同時(shí)也要考慮在復(fù)⽤ Workspace 后,如何最⼤化 的利⽤其特點(diǎn),⽐如,從 node_module 緩存、npm install 跳過等。
v2.0 優(yōu)化⽅案
資源調(diào)度
⾸先需要對(duì)資源調(diào)度進(jìn)⾏優(yōu)化,那就需要重新設(shè)計(jì),把⼀組機(jī)器分為多個(gè)切⽚組,每個(gè)切⽚組調(diào)度順序不 同。當(dāng)應(yīng)⽤觸發(fā)構(gòu)建時(shí),分配對(duì)應(yīng)的 key值:
- 1 AppNodeKey = AppId%nodes
再根據(jù)劃分的 key,尋找對(duì)應(yīng)的機(jī)器組,如 [0,1,2,3,4],構(gòu)建任務(wù)去尋找 0 號(hào)機(jī),尋找對(duì)應(yīng)的 AppId 的 Workspace ⽬錄地址去執(zhí)⾏構(gòu)建任務(wù),假如任務(wù)被占⽤(默認(rèn)是 2 個(gè)任務(wù),這樣可以優(yōu)化資源不會(huì)被⼤ 量任務(wù)搶占),會(huì)再尋找下⼀臺(tái)機(jī)器,這樣機(jī)器資源調(diào)度就會(huì)均衡化。
構(gòu)建 job 流⽔線化
我們對(duì)不同的⼯程項(xiàng)⽬進(jìn)⾏了模板化,⽐如 PC項(xiàng)⽬、H5 項(xiàng)⽬、游戲項(xiàng)⽬、hybrid 項(xiàng)⽬等等,在模板基 礎(chǔ)上,我們⼜封裝出來打包流⽔線模板,這樣的好處是,我們可以⾃⼰去針對(duì)各個(gè)類型的⼯程模板做⼀些 定向的配置優(yōu)化,⽐如說我們的游戲類型項(xiàng)⽬,我們?nèi)プ?#12032;個(gè)構(gòu)建、打包,我們就可以在對(duì)應(yīng)的開發(fā)組件 庫依賴這⼀塊,做⼀些對(duì)應(yīng)的緩存、通知、報(bào)告等等。
流⽔線同時(shí)也帶來了⼀些好處:
- 第⼀,我們把構(gòu)建任務(wù)進(jìn)⾏了⽣命周期化,git cone、npm install、npm build,把這些階段全部進(jìn)⾏拆 開,讓整個(gè)任務(wù)流程顆粒化,這樣的好處是,我們可以在每⼀個(gè)顆粒之間找到優(yōu)化空間,⽐如是不是可以 不進(jìn)⾏npm install,⽐如上傳制品倉庫的邏輯優(yōu)化等等。
- 第⼆,可以指定DSL,我們可以實(shí)時(shí)監(jiān)聽打包排隊(duì)的情況,在資源調(diào)度層⾯做⼀些優(yōu)化。同時(shí)可以做⼀些 埋點(diǎn)進(jìn)⾏采集數(shù)據(jù),給后續(xù)進(jìn)⾏深⼊分析。
- 第三,我們甚⾄可以在構(gòu)建、打包過程中,做⼀些交互的相關(guān)操作,⽐如,我們打包⼀個(gè) h5 項(xiàng)⽬,需要測(cè)試同學(xué)來進(jìn)⾏審核,只有測(cè)試審核通過完之后,才進(jìn)⼊到下⼀流程,下⼀個(gè)流程可能是進(jìn)⾏ UAT 打包、 ⽣產(chǎn)打包等等。
- 第四,針對(duì)項(xiàng)⽬依賴?yán)。铋_始的時(shí)候,我們做的是全量的拉取,我們現(xiàn)在可以優(yōu)化為增量拉取,這 樣,服務(wù)器的壓⼒會(huì)減輕很多
錯(cuò)誤治理
不管是在本地還是 cd 平臺(tái)上進(jìn)⾏構(gòu)建,也容易出現(xiàn)各種意想不到的錯(cuò)誤。⽐如開發(fā)的疏忽,流⽔線的 git commit 未經(jīng)過驗(yàn)證進(jìn)⾏提交代碼,都可能在 npm run build、或者 npm install 這兩個(gè)階段報(bào)出不同的 錯(cuò)誤,所以就需要對(duì)⽇志提示進(jìn)⾏分級(jí)集,劃分為兩種類型:
- warning 類型:沒有 package-lock.json 提示,不影響到任務(wù)構(gòu)建;
- error 類型:導(dǎo)致 job 任務(wù)退出,⽐如依賴包未找到等; 4
我們對(duì) npm install、npm run build,及構(gòu)建的各個(gè)階段的觀察,可以把失敗歸納為 4 個(gè)觸發(fā) warning 或 error 類型:
- 語法錯(cuò)誤:代碼沖突...
- 程序異常:內(nèi)存溢出…
- Install 失敗:未找到安裝包…
- 配置錯(cuò)誤:構(gòu)建未按照規(guī)范輸出…
遵守“觀察⽇志-沉淀規(guī)則-修正反饋準(zhǔn)確率”規(guī)則,來沉淀⽇志規(guī)則。
npm install 跳過
在復(fù)⽤ Workspace 的情況下,已經(jīng) install 好的 node_modules,就沒必要進(jìn)⾏⼆次重復(fù) npm install, 就需要考慮只有依賴進(jìn)⾏變更時(shí),再重新 npm isntall。同時(shí)也容易帶來問題,node_modules 污染,我 們采取了多種⽅式來避免 node_modules 被污染掉。
- cache 定期檢查;
- 切換 node 版本時(shí),執(zhí)⾏ npm rebuild,重新進(jìn)⾏編譯⼀些需要依賴 gc++ 環(huán)境構(gòu)建的包,如 node- sass;
- CD ⻚⾯增加⼿動(dòng)清理 Workspace 選項(xiàng);
帶來的收益
⽬前,我們對(duì)⽐以前的 v1.0 ⽅案,整體有了 20%+ 構(gòu)建速度的提升,這對(duì)我們團(tuán)隊(duì)來說,也算是⼀個(gè)不 ⼩的正向激勵(lì),說明我們之前努⼒的⽅向是正確的。
遷移過程
在 v1.0 遷移到 v2.0,需要考慮如何進(jìn)⾏平滑遷移,我們基于以下來進(jìn)⾏遷移:
1:每個(gè)應(yīng)⽤建⽴獨(dú)⽴ v2.0 Job 任務(wù),⽅便快速變更及排查問題;
2:⻚⾯上⽀持快速回滾到 v1.0;
3:選擇 git commit、node 版本等信息保持不變,⽆感;
總結(jié)
好的架構(gòu)不是設(shè)計(jì)出來⽽是演進(jìn)出來。在未來,構(gòu)建任務(wù)可能會(huì)越來越多,項(xiàng)⽬也越來越復(fù)雜化,我們就 會(huì)考慮容器化⽅案,根據(jù)實(shí)際情況去考慮,容器構(gòu)建,鏡像發(fā)布,盡可能的節(jié)約資源。