前端部署真的不簡單

現在大部分的中小型公司部署前端代碼都是比較簡單的,主要步驟如下:

首先,通過腳手架提供的命令npm run build打包前端代碼,生成dist文件夾;

最後,將dist文件夾丟給後臺開發人員放在他們的工程裏面,隨後臺一起部署;現在普遍是前後端分開部署,因此,利用nginx起一個 web 服務器,將dist文件夾放到指定的路徑下,配置下nginx訪問路徑,對於請求接口使用proxy_pass進行轉發,解決跨域的問題。

更加高端一點的操作,是利用CI/CD + Docker進行自動化部署。

但是,你是否真的想過前端部署真的就這麼簡單嗎?

這其實是一個非常嚴肅且複雜的問題,因爲這關係到線上生產環境的穩定

有一天,從自知乎上看到一篇張雲龍大佬在 2014 年寫的文章,非常有啓發,即使這篇文章距離現在有快 10 年了,但是其中的思想仍然熠熠生輝。

因爲寫的真的是太好了,爲了讓更多的人看到,所以大部分內容直接就照搬過來,爲了讓自己加深印象。如果想看原文,原文網址 [1] 在這裏。

那讓我們從原始的前端開發講起。

下圖是一個 index.html 頁面和它的樣式文件 a.css,無需編譯,本地預覽,丟到服務器,等待用戶訪問。

哇,前端這麼簡單,門檻好低啊。這也是前端有太多人湧入進來的原因。

接着,我們訪問頁面,看到效果,再查看一下網絡請求,200!不錯,太完美了!

那麼,研發完成。。。。了麼?

等等,這還沒完呢!

對於像 BAT 這種公司來說,那些變態的訪問量和性能指標,將會讓前端一點也不好玩。

看看那個 a.css 的請求,如果每次用戶訪問頁面都要加載,是不是很影響性能,很浪費帶寬啊,我們希望最好這樣:

利用304,讓瀏覽器使用本地緩存。

但,這樣也就夠了嗎?

不夠!

304叫協商緩存,這玩意還是要和服務器通信一次,我們的優化級別是變態級,所以必須徹底滅掉這個請求,要變成這樣:

強制瀏覽器使用本地緩存 (cache-control/expires),不要和服務器通信。

好了,請求方面的優化已經達到變態級別,那問題來了:你都不讓瀏覽器發資源請求了,這緩存咋更新

很好,相信有人想到了辦法:通過更新頁面中引用的資源路徑,讓瀏覽器主動放棄緩存,加載新資源

像這樣:

下次上線,把鏈接地址改成新的版本,這就更新資源了。

問題解決了麼?當然沒有,思考這種情況:

頁面引用了 3 個 css 文件,而某次上線只改了其中的a.css,如果所有鏈接都更新版本,就會導致b.cssc.css的緩存也失效,那豈不是又有浪費了?

不難發現,要解決這種問題,必須讓 url 的修改與文件內容關聯,也就是說,只有文件內容變化,纔會導致相應 url 的變更,從而實現文件級別的精確緩存控制

什麼東西與文件內容相關呢?

我們會很自然的聯想到利用數據摘要要算法對文件求摘要信息,摘要信息與文件內容一一對應,就有了一種可以精確到單個文件粒度的緩存控制依據了。

OK,那我們把 url 改成帶摘要信息的:

這回再有文件修改,就只更新那個文件對應的 url 了,想到這裏貌似很完美了。你覺得這就夠了麼?

圖樣圖森破!

現代互聯網企業,爲了進一步提升網站性能,會把靜態資源和動態網頁分集羣部署,靜態資源會被部署到CDN節點上,網頁中引用的資源也會變成對應的部署路徑:

好了,當我要更新靜態資源的時候,同時也會更新 html 中的引用吧,就好像這樣:

這次發佈,同時改了頁面結構和樣式,也更新了靜態資源對應的 url 地址。現在重點來了,現在要發佈代碼上線,親愛的前端研發同學,你來告訴我,咱們是先上線頁面,還是先上線靜態資源

這裏的靜態資源不僅僅包括 css 文件,也包括圖片,以及不怎麼經常變的資源。

  1. 先部署動態頁面,再部署靜態資源:在二者部署的時間間隔內,如果有用戶訪問頁面,就會在新的頁面結構中加載舊的資源,並且把這個舊版本的資源當做新版本緩存起來,其結果就是:用戶訪問到了一個樣式錯亂的頁面,除非手動刷新,否則在資源緩存過期之前,頁面會一直執行錯誤。

  2. 先部署靜態資源,再部署動態頁面:在部署時間間隔之內,有舊版本資源本地緩存的用戶訪問網站,由於請求的頁面是舊版本的,資源引用沒有改變,瀏覽器將直接使用本地緩存,這種情況下頁面展現正常;但沒有本地緩存或者緩存過期的用戶訪問網站,就會出現舊版本頁面加載新版本資源的情況,導致頁面執行錯誤,但當頁面完成部署,這部分用戶再次訪問頁面又會恢復正常了。

好的,上面一坨分析想說的就是:先部署誰都不成!都會導致部署過程中發生頁面錯亂的問題。

所以,訪問量不大的項目,可以讓研發同學苦逼一把,等到半夜偷偷上線,先上靜態資源,再部署頁面,看起來問題少一些。這也是很多公司的部署方案。

但是,大公司超變態,沒有這樣的絕對低峯期,只有相對低峯期。

所以,爲了穩定的服務,還得繼續追求極致啊!

這個奇葩問題,起源於資源的 覆蓋式發佈,用待發布資源覆蓋已發佈資源,就有這種問題。

解決它也好辦,就是實現 非覆蓋式發佈

看上圖,用文件的摘要信息來對資源文件進行重命名,把摘要信息放到資源文件發佈路徑中,這樣,內容有修改的資源就變成了一個新的文件發佈到線上,不會覆蓋已有的資源文件。上線過程中,先全量部署靜態資源,再灰度部署頁面,整個問題就比較完美的解決了。

因爲很多前端開發同學不怎麼接觸部署,對灰度部署不太熟悉,下面將介紹下什麼是灰度部署。

軟件開發一般都是一個版本一個版本的迭代。新版本上線前都會經過測試,但就算這樣,也不能保證上線了不出問題。

所以,在公司裏上線新版本代碼一般都是通過灰度系統。灰度系統可以把流量劃分成多份,一份走新版本代碼,一份走老版本代碼。

而且灰度系統支持設置流量的比例,比如可以把走新版本代碼的流程設置爲 5%,沒啥問題了再放到 10%,50%,最後放到 100% 全量。這樣可以把出現問題的影響降到最低。

不然一上來就全量,萬一出了線上問題,那就是大事故。

另外,灰度系統不止這一個用途,比如,產品不確定某些改動是不是有效的,就要做 AB 實驗,也就是要把流量分成兩份,一份走 A 版本代碼,一份走 B 版本代碼。

那這樣的灰度系統是怎麼實現的呢?其實很多都是用 nginx 實現的。

nginx 是一個反向代理的服務,用戶請求發給它,由它轉發給具體的應用服務器。

它的過程如下圖所示:

首先,需要對流量進行染色,即對這個用戶進行標註,讓這個用戶訪問服務 1,另外的用戶訪問服務 2。染色的方式有很多,可以通過cookie來完成。不同的用戶攜帶的cookie是不同的。第一染色的時候,所有的用戶都訪問服務 1。

然後,第二次訪問的時候,nginx根據用戶攜帶的cookie進行轉發到不同的服務,這樣就完成了灰度訪問。

好了,灰度部署就介紹到這裏,回到原文講的先全量部署靜態資源,再灰度部署頁面,這是什麼意思呢?

首先,部署靜態資源的時候,不要刪除原來的靜態資源,而是把新的靜態資源發覆制過去,因爲文件名用摘要算法重命名的,所以不會發生重名的問題。

其次,灰度部署動態頁面,也就是一部分用戶訪問老的頁面,一部分用戶訪問新的頁面。訪問老頁面的用戶請求的還是老資源,直接使用緩存。訪問新頁面的用戶訪問新資源,此時新資源已經部署完成,所以不會訪問老的資源,導致頁面出現錯誤。

最後,根據訪問情況,利用灰度系統,逐漸把訪問老頁面的用戶過渡到訪問新頁面上。

所以,大公司的靜態資源優化方案,基本上要實現這麼幾個東西:

  1. 配置超長時間的本地緩存:節省帶寬,提高性能

  2. 採用內容摘要作爲緩存更新依據:精確的緩存控制

  3. 靜態資源 CDN 部署:優化網絡請求

  4. 更資源發佈路徑實現非覆蓋式發佈:平滑升級

全套做下來,就是相對比較完整的靜態資源緩存控制方案了,而且,還要注意的是,靜態資源的緩存控制要求在前端所有靜態資源加載的位置都要做這樣的處理

是的,所有!

什麼 js、css 自不必說,還要包括 js、css 文件中引用的資源路徑,由於涉及到摘要信息,引用資源的摘要信息也會引起引用文件本身的內容改變,從而形成級聯的摘要變化,大概就是:

到這裏本文結束了,我們已經瞭解了前端部署中關於靜態資源緩存要面臨的優化和部署問題,新的問題又來了:這™讓工程師怎麼寫碼啊!!!

這又會扯出一堆有關模塊化開發、資源加載、請求合併、前端框架等等的工程問題。

媽媽,我再也不玩前端了。。。。

原文: https://juejin.cn/post/7316202725330796571 作者:小 p 參考資料

[1] https://www.zhihu.com/question/20790576

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/JPnkUlEyV5ZWqNpzLmpyVQ