前端部署真的不簡單
現(xiàn)在大部分的中小型公司部署前端代碼都是比較簡單的,主要步驟如下:
首先,通過腳手架提供的命令npm run build打包前端代碼,生成dist文件夾;
最后,將dist文件夾丟給后臺開發(fā)人員放在他們的工程里面,隨后臺一起部署;現(xiàn)在普遍是前后端分開部署,因此,利用nginx起一個web服務(wù)器,將dist文件夾放到指定的路徑下,配置下nginx訪問路徑,對于請求接口使用proxy_pass進行轉(zhuǎn)發(fā),解決跨域的問題。
更加高端一點的操作,是利用CI/CD + Docker進行自動化部署。
但是,你是否真的想過前端部署真的就這么簡單嗎?
這其實是一個非常嚴(yán)肅且復(fù)雜的問題,因為這關(guān)系到線上生產(chǎn)環(huán)境的穩(wěn)定。
有一天,從自知乎上看到一篇張云龍大佬在2014年寫的文章,非常有啟發(fā),即使這篇文章距離現(xiàn)在有快10年了,但是其中的思想仍然熠熠生輝。
因為寫的真的是太好了,為了讓更多的人看到,所以大部分內(nèi)容直接就照搬過來,為了讓自己加深印象。如果想看原文,原文網(wǎng)址[1]在這里。
那讓我們從原始的前端開發(fā)講起。
下圖是一個 index.html 頁面和它的樣式文件 a.css,無需編譯,本地預(yù)覽,丟到服務(wù)器,等待用戶訪問。
哇,前端這么簡單,門檻好低啊。這也是前端有太多人涌入進來的原因。
接著,我們訪問頁面,看到效果,再查看一下網(wǎng)絡(luò)請求,200!不錯,太完美了!
那么,研發(fā)完成。。。。了么?
等等,這還沒完呢!
對于像 BAT 這種公司來說,那些變態(tài)的訪問量和性能指標(biāo),將會讓前端一點也不好玩。
看看那個 a.css 的請求,如果每次用戶訪問頁面都要加載,是不是很影響性能,很浪費帶寬啊,我們希望最好這樣:
利用304,讓瀏覽器使用本地緩存。
但,這樣也就夠了嗎?
不夠!
304叫協(xié)商緩存,這玩意還是要和服務(wù)器通信一次,我們的優(yōu)化級別是變態(tài)級,所以必須徹底滅掉這個請求,要變成這樣:
強制瀏覽器使用本地緩存(cache-control/expires),不要和服務(wù)器通信。
好了,請求方面的優(yōu)化已經(jīng)達到變態(tài)級別,那問題來了:你都不讓瀏覽器發(fā)資源請求了,這緩存咋更新?
很好,相信有人想到了辦法:通過更新頁面中引用的資源路徑,讓瀏覽器主動放棄緩存,加載新資源。
像這樣:
下次上線,把鏈接地址改成新的版本,這就更新資源了。
問題解決了么?當(dāng)然沒有,思考這種情況:
頁面引用了3個 css 文件,而某次上線只改了其中的a.css,如果所有鏈接都更新版本,就會導(dǎo)致b.css,c.css的緩存也失效,那豈不是又有浪費了?
不難發(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ù)了。
OK,那我們把 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)的部署路徑:
好了,當(dāng)我要更新靜態(tài)資源的時候,同時也會更新 html 中的引用吧,就好像這樣:
這次發(fā)布,同時改了頁面結(jié)構(gòu)和樣式,也更新了靜態(tài)資源對應(yīng)的url地址。現(xiàn)在重點來了,現(xiàn)在要發(fā)布代碼上線,親愛的前端研發(fā)同學(xué),你來告訴我,咱們是先上線頁面,還是先上線靜態(tài)資源?
這里的靜態(tài)資源不僅僅包括css文件,也包括圖片,以及不怎么經(jīng)常變的資源。
-
先部署動態(tài)頁面,再部署靜態(tài)資源:在二者部署的時間間隔內(nèi),如果有用戶訪問頁面,就會在新的頁面結(jié)構(gòu)中加載舊的資源,并且把這個舊版本的資源當(dāng)做新版本緩存起來,其結(jié)果就是:用戶訪問到了一個樣式錯亂的頁面,除非手動刷新,否則在資源緩存過期之前,頁面會一直執(zhí)行錯誤。
-
先部署靜態(tài)資源,再部署動態(tài)頁面:在部署時間間隔之內(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),沒有這樣的絕對低峰期,只有相對低峰期。
所以,為了穩(wěn)定的服務(wù),還得繼續(xù)追求極致啊!
這個奇葩問題,起源于資源的 覆蓋式發(fā)布,用待發(fā)布資源覆蓋已發(fā)布資源,就有這種問題。
解決它也好辦,就是實現(xiàn) 非覆蓋式發(fā)布。
看上圖,用文件的摘要信息來對資源文件進行重命名,把摘要信息放到資源文件發(fā)布路徑中,這樣,內(nèi)容有修改的資源就變成了一個新的文件發(fā)布到線上,不會覆蓋已有的資源文件。上線過程中,先全量部署靜態(tài)資源,再灰度部署頁面,整個問題就比較完美的解決了。
因為很多前端開發(fā)同學(xué)不怎么接觸部署,對灰度部署不太熟悉,下面將介紹下什么是灰度部署。
軟件開發(fā)一般都是一個版本一個版本的迭代。新版本上線前都會經(jīng)過測試,但就算這樣,也不能保證上線了不出問題。
所以,在公司里上線新版本代碼一般都是通過灰度系統(tǒng)。灰度系統(tǒng)可以把流量劃分成多份,一份走新版本代碼,一份走老版本代碼。
而且灰度系統(tǒng)支持設(shè)置流量的比例,比如可以把走新版本代碼的流程設(shè)置為 5%,沒啥問題了再放到 10%,50%,最后放到 100% 全量。這樣可以把出現(xiàn)問題的影響降到最低。
不然一上來就全量,萬一出了線上問題,那就是大事故。
另外,灰度系統(tǒng)不止這一個用途,比如,產(chǎn)品不確定某些改動是不是有效的,就要做 AB 實驗,也就是要把流量分成兩份,一份走 A 版本代碼,一份走 B 版本代碼。
那這樣的灰度系統(tǒng)是怎么實現(xiàn)的呢?其實很多都是用 nginx 實現(xiàn)的。
nginx 是一個反向代理的服務(wù),用戶請求發(fā)給它,由它轉(zhuǎn)發(fā)給具體的應(yīng)用服務(wù)器。
它的過程如下圖所示:
首先,需要對流量進行染色,即對這個用戶進行標(biāo)注,讓這個用戶訪問服務(wù)1,另外的用戶訪問服務(wù)2。染色的方式有很多,可以通過cookie來完成。不同的用戶攜帶的cookie是不同的。第一染色的時候,所有的用戶都訪問服務(wù)1。
然后,第二次訪問的時候,nginx根據(jù)用戶攜帶的cookie進行轉(zhuǎn)發(fā)到不同的服務(wù),這樣就完成了灰度訪問。
好了,灰度部署就介紹到這里,回到原文講的先全量部署靜態(tài)資源,再灰度部署頁面,這是什么意思呢?
首先,部署靜態(tài)資源的時候,不要刪除原來的靜態(tài)資源,而是把新的靜態(tài)資源發(fā)復(fù)制過去,因為文件名用摘要算法重命名的,所以不會發(fā)生重名的問題。
其次,灰度部署動態(tài)頁面,也就是一部分用戶訪問老的頁面,一部分用戶訪問新的頁面。訪問老頁面的用戶請求的還是老資源,直接使用緩存。訪問新頁面的用戶訪問新資源,此時新資源已經(jīng)部署完成,所以不會訪問老的資源,導(dǎo)致頁面出現(xiàn)錯誤。
最后,根據(jù)訪問情況,利用灰度系統(tǒng),逐漸把訪問老頁面的用戶過渡到訪問新頁面上。
所以,大公司的靜態(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)的摘要變化,大概就是:
到這里本文結(jié)束了,我們已經(jīng)了解了前端部署中關(guān)于靜態(tài)資源緩存要面臨的優(yōu)化和部署問題,新的問題又來了:這?讓工程師怎么寫碼啊!!!
這又會扯出一堆有關(guān)模塊化開發(fā)、資源加載、請求合并、前端框架等等的工程問題。
媽媽,我再也不玩前端了。。。。
原文: https://juejin.cn/post/7316202725330796571 作者:小p參考資料
https://www.zhihu.com/question/20790576
