前端高級進階:網站的緩存控制策略最佳實踐及注意事項

這是山月關於高級前端進階暨前端工程系列文章的第 M 篇文章 (M 隨便打的,畢竟也不知道能寫多少篇),關於前 M-1 篇文章,能夠從個人 github repo shfshanyue/blog 中找到,若是點進去的話能夠捎帶~點個贊~,若是沒有點進去的話,那就給這篇文章點個贊。。今天的文章開始了javascript

本篇文章地址在 前端工程化系列,歡迎訂閱。html

  1. 前端高級進階:javascript 代碼是如何被壓縮
  2. 前端高級進階:如何更好地優化打包資源
  3. 前端高級進階:網站的緩存控制策略最佳實踐及注意事項

對於一個網站來說,性能關乎用戶體驗,你在更短的時間內打開網站,你將會留住更多的用戶。若是你的頁面十秒才能打開,那再好的用戶交互也是徒然。前端

緩存控制是網站性能優化中至爲常見及重要的一環,好的緩存控制,除了使網站在性能方面有所提高,在財務方面也有重要提高: 更好的緩存策略意味着更少的請求,更少的流量,更少的峯值帶寬,從而節省一大筆服務器或者 CDN 的費用。java

緩存控制策略就是 http caching 的策略,化繁爲簡,最有效的策略每每是很簡單的。在最簡單的粗略下,你對 http cache 只須要了解一個 Cache-Control 的頭部。react

一個較好的緩存策略只須要兩部分,而它們只須要經過 Cache-Control 控制:webpack

  1. 帶指紋資源: 永久緩存
  2. 非帶指紋資源: 每次進行新鮮度校驗

做圖以下:nginx

緩存控制策略

帶指紋資源: 永久緩存

Cache-Control: max-age=31536000

天下武功,無堅不摧,惟快不破。資源請求最快的方式就是不向服務器發起請求,經過以上響應頭能夠對資源設置永久緩存。git

  1. 靜態資源帶有 hash 值,即指紋
  2. 對資源設置一年過時時間,即 31536000,通常認爲是永久緩存
  3. 在永久緩存期間瀏覽器不須要向服務器發送請求

那爲何帶有 hash 值的資源能夠永久緩存呢?github

由於該文件的內容發生變化時,會生成一個帶有新的 hash 值的 URL。 前端將會發起一個新的 URL 的請求。web

非帶指紋資源: 每次進行新鮮度校驗

Cache-Control: no-cache
  1. 因爲不帶有指紋,每次都須要校驗資源的新鮮度。(從緩存中取到資源,多是過時資源)
  2. 若是校驗爲最新資源,則從瀏覽器的緩存中加載資源

index.html 爲不帶有指紋資源,若是把它置於緩存中,則如何保證服務器刷新數據時,被瀏覽器能夠獲取到新鮮的資源?

所以,使用 Cache-Control: no-cache 時,客戶端每次對服務器進行新鮮度校驗。

PS: no-cache 與 no-store 的區別是什麼?

即便每次校驗新鮮度,也不須要每次都從服務器下載資源: 若是瀏覽器/CDN上緩存經校驗沒有過時。這被稱爲協商緩存,此時 http 狀態碼返回 304,指 Not Modified,即沒有變動。

幸運的是,關於協商緩存,你無需管理,也無需配置, nginx 或者一些 OSS 都會自動配置協商緩存。

而對於協商緩存,也有它們本身的算法,協商緩存的背後基於響應頭 Last-Modified/ETag。瀏覽器每次請求資源時,會攜帶上次服務器響應的 ETag/Last-Modified 做爲標誌,與服務端此時的 ETag/Last-Modified 做比較,來判斷內容更改。

http 響應頭中的 ETag 值是如何生成的?

而在操做系統底層,Last-Modified 每每經過文件系統(file system)中的 mtime 屬性生成。而 ETag 提供比 Last-Modified 更精細的檢驗粒度,由文件內容的 hash 或者 mtime/size 生成。固然,這是後話。

必定要爲你的資源添加 Cache-Control 響應頭

我會常常接觸到一些網站,他們的資源文件並無 Cache-Control 這個響應頭。究其緣由,在於緩存策略配置這個工做的職責不清,有時候它須要協調前端和運維。

那若是不添加 Cache-Control 這個響應頭會怎麼樣?

是否是每次都會自動去服務器校驗新鮮度,很惋惜,不是。 此時會對資源進行強制緩存,而對不帶有指紋信息的資源頗有可能獲取到過時資源。 若是過時資源存在於瀏覽器上,還能夠經過強制刷新瀏覽器來獲取最新資源。可是若是過時資源存在於 CDN 的邊緣節點上,CDN 的刷新就會複雜不少,並且有可能須要多人協做解決。

那默認的強制緩存時間是多少

首先要明確兩個響應頭表明的含義:

  1. Date: 指源服務器響應報文生成的時間,差很少與發請求的時間等價
  2. Last-Modified: 指靜態資源上次修改的時間,取決於 mtime

LM factor 算法認爲當請求服務器時,若是沒有設置 Cache-Control,若是距離上次的 Last-Modified 越遠,則生成的強制緩存時間越長。

用公式表示以下,其中 factor 介於 0 與 1 之間:

MaxAge = (Date - LastModified) * factor

Bundle Splitting:儘可能減小資源變動

得益於單頁應用與前端工程化的發展,通過打包後,基本上全部資源都是帶有指紋信息的,這意味着全部的資源都是可以設置永久緩存。打包策略以下圖所示:

緩存控制策略

但僅僅如此了嗎?

若是你全部的 js 資源都打包成一個文件,它確實有永久緩存的優點。可是當有一行文件進行修改時,這一個大包的指紋信息發生改變,永久緩存失效。

因此咱們如今須要作到的是:當修改文件後,形成最小範圍的緩存失效。webpack 等打包工具雖然在 optimization 上內置了不少性能優化,但它不會幫你作這件事,這件事情須要本身動手。

緩存控制策略

此時咱們能夠對資源進行分層次緩存的打包方案,這是一個建議方案:

  1. webpack-runtime: 應用中的 webpack 的版本比較穩定,分離出來,保證長久的永久緩存
  2. react/react-dom: react 的版本更新頻次也較低
  3. vendor: 經常使用的第三方模塊打包在一塊兒,如 lodashclassnames 基本上每一個頁面都會引用到,可是它們的更新頻率會更高一些。另外對低頻次使用的第三方模塊不要打進來
  4. pageA: A 頁面,當 A 頁面的組件發生變動後,它的緩存將會失效
  5. pageB: B 頁面
  6. echarts: 不經常使用且過大的第三方模塊單獨打包
  7. mathjax: 不經常使用且過大的第三方模塊單獨打包
  8. jspdf: 不經常使用且過大的第三方模塊單獨打包

隨着 http2 的發展,特別是多路複用,初始頁面的靜態資源不受資源數量的影響。所以爲了更好的緩存效果以及按需加載,也有不少方案建議把全部的第三方模塊進行單模塊打包。

小結

緩存控制策略

與我交流

掃碼添加個人機器人微信,將會自動(自動拉人程序正在研發中)把你拉入前端高級進階學習羣

我是山月,能夠加我微信 shanyue94 與我交流,備註交流。另外能夠關注個人公衆號【全棧成長之路】

若是你對全棧面試,前端工程化,graphql,devops,我的服務器運維以及微服務感興趣的話,能夠關注我

相關文章
相關標籤/搜索