前端⼤規模構建演進實踐

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"作者:楊偉偉 ","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"掌門教育效能研發前端工程師","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"掌⻔教育⾃動化構建歷程","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在業內前端構建,⼀般分爲三種:","attrs":{}}]},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"⼿動觸發構建:這個階段⾮常原始,需要我們⾃⼰在本地進⾏ git pull/npm install/npm run build 等 等操作,也容易出現問題;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"虛擬機 Jenkins 集羣分佈式構建:通過 Master 將任務分配到對應的 Slave 機器上執⾏構建,能極⼤ 的均衡資源,利⽤性能,同時解放雙⼿;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"容器集羣構建:容器構建,鏡像發佈,可以進⼀步的節約資源;","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不過掌⻔教育在 2019 年之前,前端研發更多是在本地進⾏構建,再通過運維的腳本來進⾏部署,也容易 導致出現⽣產故障。所以我們收集反饋,結合實際情況,開發出 v1.0 構建模式,也取得了很好的成果。但 並沒有以此就認爲⾼枕⽆憂,也對很多痛點進⾏持續的優化,最後迭代出 v2.0 的⽅案。在這個過程中,前 端業務壯⼤,CI 構建經過 400+ 多應⽤,每週 2000+ 次構建,300+ 次的⽣產發佈的⼤考,持續的成 ⻓。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/c9/c91afc131ed8c9c9064583281ea862fa.png","alt":"在這裏插入圖片描述","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"v1.0 ⾯對的挑戰","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"v1.0 前端構建狀況,⼀種是通過 webhook 來觸發流⽔線構建,第⼆種是通過在 cd 上新建構建單來觸構 建。如果構建任務⽐較多,按照單臺機器的是遠遠不夠,在這種情況下就需要藉助 Jenkins 的 Master/Slave 的主從模式,來解決服務器的資源壓⼒。讓 Master 的服務器來進⾏調度資源,指定空閒 Slave 機器進⾏構建。當 Slave 機器上構建任務滿了,構建任務繼續在 Master 排隊池中繼續等待,等 Slave 空閒後,再進⾏分配。","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/99/991f1b57695fda65f11a699fca11573c.png","alt":"在這裏插入圖片描述","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"存在的挑戰","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"v1.0 不是最好的⽅案,同時暴露出第⼀次構建慢、錯誤⽇志反饋不明確等問題,另外⼀點就是 job 維護困 難。要解決這些問題,就需要重新開始,重新設計。⾸先就是 JOB 維護困難,v1.0 的任務模式是多個應⽤對應 1 個 job,這就導致⼏個問題,如果 job 發版 導致掛了,影響到全部。假如需要復⽤該 job,進⾏定製化開發也⽐較困難。其次第⼀次構建慢的問題,更多在資源調度上和⽆法復⽤ Workspace,根據之前的資源調度模式,當我們 把任務分配到 A 機器,該任務被執⾏成功,那麼下次的任務也會⾛⼊到這臺 A 機器。以此觀察,就會發 現⼤部分任務都會優先去搶佔 A 機器。這家就導致了⼏個問題:","attrs":{}}]},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"資源調度不均衡;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"npm 緩存越來越⼤;","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最後是⽆法復⽤ Workspace 模式,在 v1.0 情況下,不復⽤ Workspace 模式是會帶來以下優勢:保證 node_module ⽆污染問題,同時也避免了 npm run build 的各種因爲 node_module 包污染的問題,導 致的意外錯誤。所以在 v2.0 就需要應對污染的問題。同時也要考慮在復⽤ Workspace 後,如何最⼤化 的利⽤其特點,⽐如,從 node_module 緩存、npm install 跳過等。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"v2.0 優化⽅案","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"資源調度","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/67/672af85467c83b32452b550651ed8c67.png","alt":"在這裏插入圖片描述","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"⾸先需要對資源調度進⾏優化,那就需要重新設計,把⼀組機器分爲多個切⽚組,每個切⽚組調度順序不 同。當應⽤觸發構建時,分配對應的 key值:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":" 1 AppNodeKey = AppId%nodes \n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"再根據劃分的 key,尋找對應的機器組,如 [0,1,2,3,4],構建任務去尋找 0 號機,尋找對應的 AppId 的 Workspace ⽬錄地址去執⾏構建任務,假如任務被佔⽤(默認是 2 個任務,這樣可以優化資源不會被⼤ 量任務搶佔),會再尋找下⼀臺機器,這樣機器資源調度就會均衡化。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"構建 job 流⽔線化","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ca/cac1ba78c29fdd45c492d8e9793d2033.png","alt":"在這裏插入圖片描述","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們對不同的⼯程項⽬進⾏了模板化,⽐如 PC項⽬、H5 項⽬、遊戲項⽬、hybrid 項⽬等等,在模板基 礎上,我們⼜封裝出來打包流⽔線模板,這樣的好處是,我們可以⾃⼰去針對各個類型的⼯程模板做⼀些 定向的配置優化,⽐如說我們的遊戲類型項⽬,我們去做⼀個構建、打包,我們就可以在對應的開發組件 庫依賴這⼀塊,做⼀些對應的緩存、通知、報告等等。流⽔線同時也帶來了⼀些好處:","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第⼀,我們把構建任務進⾏了⽣命週期化,git cone、npm install、npm build,把這些階段全部進⾏拆 開,讓整個任務流程顆粒化,這樣的好處是,我們可以在每⼀個顆粒之間找到優化空間,⽐如是不是可以 不進⾏npm install,⽐如上傳製品倉庫的邏輯優化等等。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第⼆,可以指定DSL,我們可以實時監聽打包排隊的情況,在資源調度層⾯做⼀些優化。同時可以做⼀些 埋點進⾏採集數據,給後續進⾏深⼊分析。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第三,我們甚⾄可以在構建、打包過程中,做⼀些交互的相關操作,⽐如,我們打包⼀個 h5 項⽬,需要測試同學來進⾏審覈,只有測試審覈通過完之後,才進⼊到下⼀流程,下⼀個流程可能是進⾏ UAT 打包、 ⽣產打包等等。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第四,針對項⽬依賴拉取,最開始的時候,我們做的是全量的拉取,我們現在可以優化爲增量拉取,這 樣,服務器的壓⼒會減輕很多","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"錯誤治理","attrs":{}},{"type":"text","text":"不管是在本地還是 cd 平臺上進⾏構建,也容易出現各種意想不到的錯誤。⽐如開發的疏忽,流⽔線的 git commit 未經過驗證進⾏提交代碼,都可能在 npm run build、或者 npm install 這兩個階段報出不同的 錯誤,所以就需要對⽇志提示進⾏分級集,劃分爲兩種類型:","attrs":{}}]},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"warning 類型:沒有 package-lock.json 提示,不影響到任務構建;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"error 類型:導致 job 任務退出,⽐如依賴包未找到等; 4","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們對 npm install、npm run build,及構建的各個階段的觀察,可以把失敗歸納爲 4 個觸發 warning 或 error 類型:","attrs":{}}]},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"語法錯誤:代碼衝突...","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"程序異常:內存溢出…","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"Install 失敗:未找到安裝包…","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"配置錯誤:構建未按照規範輸出…","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"遵守“觀察⽇志-沉澱規則-修正反饋準確率”規則,來沉澱⽇志規則。","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/cc/cc2be5bcb9675c122e25aabb713be213.png","alt":"在這裏插入圖片描述","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"npm install 跳過","attrs":{}},{"type":"text","text":"在復⽤ Workspace 的情況下,已經 install 好的 node_modules,就沒必要進⾏⼆次重複 npm install, 就需要考慮只有依賴進⾏變更時,再重新 npm isntall。同時也容易帶來問題,node_modules 污染,我 們採取了多種⽅式來避免 node_modules 被污染掉。","attrs":{}}]},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"cache 定期檢查;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"切換 node 版本時,執⾏ npm rebuild,重新進⾏編譯⼀些需要依賴 gc++ 環境構建的包,如 node- sass;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"CD ⻚⾯增加⼿動清理 Workspace 選項;","attrs":{}}]}]}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"帶來的收益","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"⽬前,我們對⽐以前的 v1.0 ⽅案,整體有了 20%+ 構建速度的提升,這對我們團隊來說,也算是⼀個不 ⼩的正向激勵,說明我們之前努⼒的⽅向是正確的。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"遷移過程","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 v1.0 遷移到 v2.0,需要考慮如何進⾏平滑遷移,我們基於以下來進⾏遷移:1:每個應⽤建⽴獨⽴ v2.0 Job 任務,⽅便快速變更及排查問題;2:⻚⾯上⽀持快速回滾到 v1.0;3:選擇 git commit、node 版本等信息保持不變,⽆感;","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"總結","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"好的架構不是設計出來⽽是演進出來。在未來,構建任務可能會越來越多,項⽬也越來越複雜化,我們就 會考慮容器化⽅案,根據實際情況去考慮,容器構建,鏡像發佈,儘可能的節約資源。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/fb/fb82a9fc5078ff5c4fc1a3ae0f590996.png","alt":"在這裏插入圖片描述","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章