靜態資源部署問題的一些思考

對訪問靜態資源的一些思考

  1. 當我還是一個初入門的小前端時,對靜態資源的引用方式就是理解爲在HTML代碼裏面,通過一個link或者script標籤,寫一個相對或者絕對路徑,去拿到對應的靜態資源。比如下面這樣:
<link href="/static/kasol.css" />

沒有什麼毛病,一切都是那麼美好,打開瀏覽器查看network,發現狀態碼是200。
但是問題在於,用戶在訪問頁面的時候都要去加載一次這個資源,那其實是不太好的,前端的瓶頸在於帶寬,我們需要儘量節約請求數,所以很多時候,我們需要瀏覽器的緩存機制,可以利用http1.0中的expires或者1.1中的max-age,給響應頭寫上標記,這樣一來,我們下次再來訪問這個頁面的時候,查看這個請求,狀態碼應該還是200,但是這次是from cache,我們直接從瀏覽器的緩存那裏拿到了這個靜態資源文件。
或者還可以利用not modified 這個標記,這樣子當瀏覽器不確定這個資源是否真的過期時,就可以通過協商緩存機制來詢問服務器,假如文件並沒有被更新,將會告知瀏覽器繼續使用該資源,響應頭狀態碼變爲304。

  1. 當我的資源內容改變時,此時怎麼辦呢,要怎麼讓瀏覽器去獲取到最新的文件呢?也許你會說,在url後面加上後綴不就可以了麼?
<link href="/static/kasol.css?v=1.0" />

但是這種方式很低效,並且在有多個引用時就變得很繁瑣。而且,以這種遞增版本號作爲後綴的方式不是很科學,爲什麼?假如這個文件的內容實際上並沒有改變,但是你還是改動了這個version,那你又讓瀏覽器重新去發起請求抓取最新文件了,浪費了緩存,所以比較好的方法是,根據文件內容做一個摘要,把這個摘要作爲文件的後綴,可能就像下面那樣

<link href="/static/kasol.css?v=ask3oe4" />

這樣子的話,只有當文件內容確實改變時,我們的瀏覽器纔會發起請求抓取最新的資源。
或者還有人使用的是在獲取資源時,在後面加上時間戳,這樣確實可以每一次都獲取到最新的資源,但是這麼一來,就無法利用緩存了。

  1. 現在有點規模的公司,都會把靜態資源部署到CDN上,而不是直接丟到提供服務的機器上,CDN的好處不言而喻,但是當這些資源抽離出來被放到了CDN上後,又有一個新的問題了,就以上面的場景來說:舉個例子,比如某個時刻,後端的頁面結構發生了改變,同時,對應的css文件和js文件也發生了改變,這時候需要發佈上線了,那麼我們是先發前端應用還是先發後端應用呢?
  • 假如先發後端,那麼在後端部署完成,而前端沒有部署完的情況下,此時有用戶來訪問頁面,雖然請求想獲取的是最新的資源,但是此時前端的資源還是舊的,這時肯定就不對了,更糟糕的是,瀏覽器還把這個舊的資源緩存了,所以除非用戶主動強制刷新,或者有緩存更新機制,不然拿到的就是舊的資源了
  • 假如先發前端,再發後端,那麼同樣的,在部署的時間間隔內,用戶會拿到舊的頁面,配上新的靜態資源,這樣也是不對的。不過當後端部署完成後,拿到了新的頁面,此時就正常了。

其實以上說的可以看出,倆種方式都是有點問題的。

  1. 再說上述問題的答案之前,先提一個關鍵點,就是我們發佈前端資源的姿勢。大致可以分爲增量部署和覆蓋式部署。前者可以看成是在機器上多了一個版本的文件,而後者則是覆蓋掉原來的文件。我們可以根據前端應用的某個標誌。比如package.json的version來控制是否是增量發佈,比如你改動了kasol.css,但是不改version,他原先的版本號是1.0.0,那麼發佈的時候,那麼他就會在1.0.0這個文件夾下把原先的資源給覆蓋了,同理,假如改動了kasol.css,並且版本號升級爲了1.0.1,那麼發佈的時候,會在1.0.1這個文件夾下生成資源,不會去覆蓋1.0.0這個文件夾。

  2. 那麼比較好的做法是怎麼樣的呢?
    首先我們利用構建工具生產出來的靜態資源文件應該是這樣的姿勢

kasol.ae2sk2o.css

然後在頁面上,自然是去這樣引用

<link href="/static/kasol.ae2sk2o.css" />

區別在哪呢,可以看到,那個摘要的值從後綴移動到了文件的擴展名中。這樣當後端的頁面去獲取資源的時候,只要這個摘要不同,抓取的資源就是不一樣的。

  1. 但是第三點中提到的,不管先部署哪一種應用貌似都會有問題,不過現在看來。我們可以先部署前端靜態資源,再部署後端應用。爲什麼這麼說呢?舉個覆蓋式部署的例子:
    原先舊版本的頁面結構是這樣
<link href="http://xxx.cdn.com/path/kasol.ae2sk2o.css" />

CDN上的靜態資源是這樣

http://xxx.cdn.com/path/kasol.ae2sk2o.css

現在靜態資源改變了,變成了下面這樣

http://xxx.cdn.com/path/kasol.ksoplw.css

雖然後端沒有什麼邏輯更改,但是我們需要改變後端的引用:

<link href="http://xxx.cdn.com/path/kasol.ksoplw.css" />

可能有人會說,按照上述的邏輯,用戶在時間間隔內假如去訪問舊的頁面,由於後端的引用沒有更新,那麼抓取的還是http://xxx.cdn.com/path/kasol.ae2sk2o.css ,由於是覆蓋式部署,此時已經被新的覆蓋掉了,是不是應該404了?
其實仔細觀察上述靜態資源就會發現,除了文件擴展名中的部分hash值不同,其他都是一樣的。所以要是後端應用可以自動感知到新的hash並應用,那就好了。
所以我們可以這樣來處理,前端構建完靜態資源後,生成一個Map,也丟到CDN上去,本質上就是類似一個JSON對象,比如名字叫1.0.0.map,他大概長這樣。

{
   key:'kasol.css',value:'kasol.ae2sk2o.css'
}

當更新完靜態資源,map也會變化,比如這樣

{
   key:'kasol.css',value:'kasol.ksoplw.css'
}

這個map也是跟着版本號走的,對於不同的版本號,都有各自的map。
然後我們在後端應用部署一個SDK,他的作用就是輪詢去獲取對應版本前端資源的map對象,然後在模板中把靜態資源的hash值丟進去,這樣一來,實際上假如後端應用本身沒有邏輯更改,就不需要去發佈後端應用,只要發佈前端應用即可。等發佈完成之後,後端模板會自動去獲取最近的資源。
再來說說增量式發佈,這種情況大多是後端也改了一些DOM結構,所以此時後端應用也是需要發佈的。還是以先部署前端應用來說:首先kasol.ksoplw.cssb並不會把之前kasol.ae2sk2o.css給覆蓋掉,而是多了這一個帶hash物理文件。
接着在後端未部署完成的時間間隔內,用戶訪問舊的頁面,此時後端的SDK通過輪詢拿的還是舊版本的map,所以還是抓取舊版本的資源,即http://xxx.cdn.com/path/kasol.ae2sk2o.css,舊的頁面配上舊的靜態資源,沒什麼問題。然後當後端也部署完成後,後端SDK拿到新的map,將新的hash值放到頁面裏去,新的頁面結構會去抓取http://xxx.cdn.com/path/kasol.ksoplw.css,此時就可以擊穿緩存,去CDN上拿到新的靜態資源。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章