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