今天,來看看大廠前端開發(fā)與部署方案!
最近有不少同學(xué)都問到我關(guān)于前端部署相關(guān)的問題,特別是針對大廠的部署方案。
在之前我有發(fā)過類似的文章,文章的內(nèi)容依據(jù) 張云龍 在知乎的回答:
回答地址:https://www.zhihu.com/question/20790576
我認為這個回答非常有價值,應(yīng)該可以幫助到很多對部署不是很了解的同學(xué),因此整理。
問題
首先把問題給大家貼出來,如果大家也有類似的困惑,那么本文章肯定會給你帶來一定的收獲
大公司里怎樣開發(fā)和部署前端代碼?
主要有以下問題:
- 開發(fā)時的和部署時類庫的引用和存放是一致還是不同?
- 模塊放在項目中還是放在 CDN 之類服務(wù)器?
- 渲染網(wǎng)頁用 Nginx 還是其他動態(tài)語言的 Web 服務(wù)器?
- 制作網(wǎng)頁的流程,是先有設(shè)計師的稿,還是先看模塊?
- 會選擇用自己寫的模塊還是從社區(qū)尋找模塊?
其他的思考
你平時在公司中是怎么部署項目的呢?
- 直接執(zhí)行 npm run build 打包項目
- 得到 dist 文件夾,放到服務(wù)器對應(yīng)位置
- 重新執(zhí)行 nginx -s reload 完成部署
這樣應(yīng)該沒有什么問題了。撐死再去解決下跨域的問題,一般的中小公司部署就算是完成了。
但是 部署真的有那么簡單嗎?那些大廠的部署也是通過以上方式完成的嗎??
讓我們一起來看看吧~~
基礎(chǔ)部署邏輯
當(dāng)我們回歸到最基本的前端開發(fā)時,就像看到了這個"可愛"的index.html頁面和它的樣式文件a.css,簡單地用文本編輯器書寫代碼,不需進行編譯,只要本地預(yù)覽確認無誤,然后將其放到服務(wù)器上等待用戶訪問。
前端開發(fā)就是"如此簡單",很輕松嘛!門檻也是相當(dāng)?shù)吐?!輕輕松松掌握有木有!!
圖片
然后我們訪問頁面,看到效果,再查看一下網(wǎng)絡(luò)請求,200!不錯,太?完美了!那么,研發(fā)完成。。。。了么?
等等,這還沒完呢!對于大公司來說,那些變態(tài)的訪問量和性能指標,將會讓前端一點也不“好玩”。
看看那個a.css的請求吧,如果每次用戶訪問頁面都要加載,是不是很影響性能,很浪費帶寬啊,我們希望最好這樣:
圖片
利用304,讓瀏覽器使用本地緩存。
但,這樣也就夠了嗎?不成!
304叫協(xié)商緩存,這玩意還是要和服務(wù)器通信一次,我們的優(yōu)化級別是變態(tài)級,所以必須徹底滅掉這個請求,變成這樣:
圖片
強制瀏覽器使用本地緩存(cache-control/expires),不要和服務(wù)器通信。好了,請求方面的優(yōu)化已經(jīng)達到變態(tài)級別,那問題來了:你都不讓瀏覽器發(fā)資源請求了,這緩存咋更新?
很好,相信有人想到了辦法:通過更新頁面中引用的資源路徑,讓瀏覽器主動放棄緩存,加載新資源。好像這樣:
圖片
下次上線,把鏈接地址改成新的版本,就更新資源了不是。OK,問題解決了么?!
當(dāng)然沒有!大公司的變態(tài)又來了,思考這種情況:
圖片
頁面引用了3個css,而某次上線只改了其中的a.css,如果所有鏈接都更新版本,就會導(dǎo)致b.css,c.css的緩存也失效,那豈不是又有浪費了?!
重新開啟變態(tài)模式,我們不難發(fā)現(xiàn),要解決這種問題,必須讓url的修改與文件內(nèi)容關(guān)聯(lián),也就是說,只有文件內(nèi)容變化,才會導(dǎo)致相應(yīng)url的變更,從而實現(xiàn)文件級別的精確緩存控制。
什么東西與文件內(nèi)容相關(guān)呢?我們會很自然的聯(lián)想到利用 數(shù)據(jù)摘要要算法 對文件求摘要信息,摘要信息與文件內(nèi)容一一對應(yīng),就有了一種可以精確到單個文件粒度的緩存控制依據(jù)了。好了,我們把url改成帶摘要信息的:
圖片
這回再有文件修改,就只更新那個文件對應(yīng)的url了,想到這里貌似很完美了。你覺得這就夠了么?大公司告訴你:圖樣圖森破!
唉~~~~,讓我喘口氣
現(xiàn)代互聯(lián)網(wǎng)企業(yè),為了進一步提升網(wǎng)站性能,會把靜態(tài)資源和動態(tài)網(wǎng)頁分集群部署,靜態(tài)資源會被部署到CDN節(jié)點上,網(wǎng)頁中引用的資源也會變成對應(yīng)的部署路徑:
圖片
圖片
這次發(fā)布,同時改了頁面結(jié)構(gòu)和樣式,也更新了靜態(tài)資源對應(yīng)的url地址,現(xiàn)在要發(fā)布代碼上線,親愛的前端研發(fā)同學(xué),你來告訴我,咱們是先上線頁面,還是先上線靜態(tài)資源?
- 先部署頁面,再部署資源:在二者部署的時間間隔內(nèi),如果有用戶訪問頁面,就會在新的頁面結(jié)構(gòu)中加載舊的資源,并且把這個舊版本的資源當(dāng)做新版本緩存起來,其結(jié)果就是:用戶訪問到了一個樣式錯亂的頁面,除非手動刷新,否則在資源緩存過期之前,頁面會一直執(zhí)行錯誤。
- 先部署資源,再部署頁面:在部署時間間隔之內(nèi),有舊版本資源本地緩存的用戶訪問網(wǎng)站,由于請求的頁面是舊版本的,資源引用沒有改變,瀏覽器將直接使用本地緩存,這種情況下頁面展現(xiàn)正常;但沒有本地緩存或者緩存過期的用戶訪問網(wǎng)站,就會出現(xiàn)舊版本頁面加載新版本資源的情況,導(dǎo)致頁面執(zhí)行錯誤,但當(dāng)頁面完成部署,這部分用戶再次訪問頁面又會恢復(fù)正常了。
好的,上面一坨分析想說的就是:先部署誰都不成!都會導(dǎo)致部署過程中發(fā)生頁面錯亂的問題。所以,訪問量不大的項目,可以讓研發(fā)同學(xué)苦逼一把,等到半夜偷偷上線,先上靜態(tài)資源,再部署頁面,看起來問題少一些。
但是,大公司超變態(tài),沒有這樣的“絕對低峰期”,只有“相對低峰期”。So,為了穩(wěn)定的服務(wù),還得繼續(xù)追求極致?。?/p>
這個奇葩問題,起源于資源的 覆蓋式發(fā)布,用 待發(fā)布資源 覆蓋 已發(fā)布資源,就有這種問題。解決它也好辦,就是實現(xiàn) 非覆蓋式發(fā)布。
圖片
看上圖,用文件的摘要信息來對資源文件進行重命名,把摘要信息放到資源文件發(fā)布路徑中,這樣,內(nèi)容有修改的資源就變成了一個新的文件發(fā)布到線上,不會覆蓋已有的資源文件。上線過程中,先全量部署靜態(tài)資源,再灰度部署頁面,整個問題就比較完美的解決了。
什么是灰度?
軟件開發(fā)通常是逐個版本不斷迭代的過程。盡管新版本在上線前經(jīng)過了測試,但即使如此,也無法確保沒有問題。
因此,在公司里,上線新版本代碼通常通過灰度系統(tǒng)?;叶认到y(tǒng)能夠?qū)⒘髁糠譃槎鄠€部分,其中一部分流向新版本代碼,另一部分流向舊版本代碼。
圖片
而且,灰度系統(tǒng)可以設(shè)置流量的比例。例如,可以將走新版本代碼的流量設(shè)置為 5%,確認沒有問題后再逐步增加到 10%,50%,最終到達 100% 的全量。這樣做可以將出現(xiàn)問題的影響降到最低。
否則,如果一開始就直接使用全量流量,一旦出現(xiàn)線上問題,就可能引發(fā)重大事故。
此外,灰度系統(tǒng)不僅僅用于此,例如,當(dāng)產(chǎn)品不確定某些更改是否有效時,需要進行 A/B 實驗,將流量分為兩部分,一部分走 A 版本代碼,另一部分走 B 版本代碼。
那么這樣的灰度系統(tǒng)是如何實現(xiàn)的呢?實際上,很多都是通過 nginx 實現(xiàn)的。
nginx 是一個反向代理的服務(wù),用戶的請求發(fā)送給它,然后由它轉(zhuǎn)發(fā)給具體的應(yīng)用服務(wù)器。
圖片
它的工作流程如下圖所示:
圖片
首先,需要對流量進行染色,即對每個用戶進行標注,讓某些用戶訪問服務(wù)1,而其他用戶訪問服務(wù)2。染色的方法有很多種,可以通過cookie來實現(xiàn)。不同的用戶攜帶不同的cookie。一開始所有用戶都訪問服務(wù)1。
然后,在用戶進行第二次訪問時,nginx 根據(jù)用戶攜帶的cookie將其轉(zhuǎn)發(fā)到不同的服務(wù),從而實現(xiàn)灰度訪問。
好了,灰度部署的介紹就到這里。回到原文提到的先全量部署靜態(tài)資源,再灰度部署頁面,這是什么意思呢?
當(dāng)進行靜態(tài)資源的部署時,不必刪除原有的靜態(tài)資源,而是復(fù)制新的靜態(tài)資源過去。由于文件名采用了摘要算法重命名,因此不會出現(xiàn)重名的問題。
在灰度部署動態(tài)頁面方面,部分用戶訪問舊頁面,而另一部分用戶訪問新頁面。對于訪問舊頁面的用戶,其請求仍指向舊資源并直接使用緩存。對于訪問新頁面的用戶,他們訪問的是已經(jīng)完成部署的新資源,避免了訪問舊資源導(dǎo)致頁面錯誤的情況。
最后,根據(jù)用戶訪問情況,通過灰度系統(tǒng)逐步將訪問舊頁面的用戶過渡到新頁面上。
部署方案總結(jié)
所以,大公司的靜態(tài)資源優(yōu)化方案,基本上要實現(xiàn)這么幾個東西:
- 配置超長時間的本地緩存
- 節(jié)省帶寬,提高性能采用內(nèi)容摘要作為緩存更新依據(jù)
- 精確的緩存控制靜態(tài)資源CDN部署
- 優(yōu)化網(wǎng)絡(luò)請求更資源發(fā)布路徑實現(xiàn)非覆蓋式發(fā)布
- 平滑升級
全套做下來,就是相對比較完整的靜態(tài)資源緩存控制方案了,而且,還要注意的是,靜態(tài)資源的緩存控制要求在前端所有靜態(tài)資源加載的位置都要做這樣的處理。是的,所有!什么js、css自不必說,還要包括js、css文件中引用的資源路徑,由于涉及到摘要信息,引用資源的摘要信息也會引起引用文件本身的內(nèi)容改變,從而形成級聯(lián)的摘要變化,大概示意圖就是:
圖片
好了,目前我們快速的學(xué)習(xí)了一下前端工程中關(guān)于靜態(tài)資源緩存要面臨的優(yōu)化和部署問題,新的問題又來了:這?讓工程師怎么寫碼?。。?!
要解釋優(yōu)化與工程的結(jié)合處理思路,又會扯出一堆有關(guān)模塊化開發(fā)、資源加載、請求合并、前端框架等等的工程問題,以上只是開了個頭,解決方案才是精髓,但要說的太多太多,有空再慢慢展開吧。
總之,前端性能優(yōu)化絕逼是一個工程問題!
以上不是我YY的,可以觀察 百度 或者 facebook 的頁面以及靜態(tài)資源源代碼,查看它們的資源引用路徑處理,以及網(wǎng)絡(luò)請中靜態(tài)資源的緩存控制部分。再次贊嘆facebook的前端工程建設(shè)水平,跪舔了。
建議前端工程師多多關(guān)注前端工程領(lǐng)域,也許有人會覺得自己的產(chǎn)品很小,不用這么變態(tài),但很有可能說不定某天你就需要做出這樣的改變了。而且,如果我們能把事情做得更極致,為什么不去做呢?
另外,也不要覺得這些是運維或者后端工程師要解決的問題。如果由其他角色來解決,大家總是把自己不關(guān)心的問題丟給別人,那么前端工程師的開發(fā)過程將受到極大的限制,這種情況甚至在某些大公司都不少見!
10.29 日更新
在評論中, @陳鋼@fleuria @林翔 提到了rails,剛剛?cè)タ戳艘幌?,確實是完成了以上所說的優(yōu)化細節(jié),對整個靜態(tài)資源的管理上的思考于本答案描述的一致。很遺憾我直到今天才了解到rails中的assets pipeline。這里向以上3位同學(xué)道歉,原諒我的無知。
不過整篇回答沒有講解到具體的解決方案實現(xiàn)思路,只是介紹了前端在工程化方向的思考,答案本身是可用的,了解rails的人也可以把此答案當(dāng)做是對rails中assets pipeline設(shè)計原理的分析。
rails通過把靜態(tài)資源變成erb模板文件,然后加入<%= asset_path 'image.png' %>,上線前預(yù)編譯完成處理,不得不承認,fis的實現(xiàn)思路跟這個幾乎完全一樣,但我們當(dāng)初確實不知道有rails的這套方案存在。
10.31 日更新
用 FIS3(https://fis.baidu.com/) 包裝了一個小工具,完整實現(xiàn)整個回答所說的最佳部署方案,并提供了源碼對照,可以感受一下項目源碼和部署代碼的對照。
- 源碼項目:fouber/static-resource-digest-project · GitHub
- 部署項目:fouber/static-resource-digest-project-release · GitHub
- 部署項目可以理解為線上發(fā)布后的結(jié)果,可以在部署項目里查看所有資源引用的md5化處理。
這個示例也可以用于和assets pipeline做比較。fis沒有assets的目錄規(guī)范約束,而且可以以獨立工具的方式組合各種前端開發(fā)語言(coffee、less、sass/scss、stylus、markdown、jade、ejs、handlebars等等你能想到的),并與其他后端開發(fā)語言結(jié)合。
assets pipeline的設(shè)計思想值得獨立成工具用于前端工程,fis就當(dāng)做這樣的一個選擇吧。