整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          http緩存機制

          http緩存機制

          .1 緩存位置

          • Service Worker

          運行在瀏覽器背后的獨立線程,一般可以用來實現緩存功能。使用 Service Worker的話,傳輸協議必為HTTPS

          • Memory Cache

          內存中的緩存,讀取高效,緩存的持續性段,會隨著進程的結束而釋放,例如關閉頁面,內存中的緩存就被釋放了內存緩存中有一塊重要的緩存資源是preloader相關指令(例如<link rel="prefetch">)下載的資源。它可以一邊解析js/css文件,一邊網絡請求下一個資源。

          • Disk Cache

          存儲在硬盤中的緩存,讀取速度慢點,絕大部分的緩存都來自Disk Cache,在HTTP 的協議頭中設置(?)。

          • PushCache

          是 HTTP/2 中的內容,當以上三種緩存都沒有命中時,它才會被使用。它只在會話(Session)中存在,一旦會話結束就被釋放,并且緩存時間也很短暫,在Chrome瀏覽器中只有5分鐘左右,同時它也并非嚴格執行HTTP頭中的緩存指令。

          1.2緩存分類

          根據是否需要向服務器重新發起 http 請求分為強緩存協商緩存兩種類型。強緩存的優先級要高于協商緩存。

          • 強緩存

          強緩存機制下瀏覽器首先查找本地緩存,如果命中則不會向服務器發起請求。此時返回 200 狀態碼,并帶有 from disk cache 或 from memory cache 字樣。強緩存可以通過設置兩種 HTTP Header 實現:Expires 和 Cache-Control。

          Expires

          是 HTTP/1.0 的產物,受限于本地時間


          Cache-Control

          Cache-Control在 HTTP/1.1中,Cache-Control 是最重要的規則,主要用于控制網頁緩存。

          1. public

          響應可被任何中間節點緩存,如 Browser <-- proxy <-- Server,中間的 proxy 可以緩存資源,比如下次再請求同一資源時 proxy 直接把自己緩存的內容給 Browser 而不再向 Server 要。

          2. private

          所有內容只有客戶端可以緩存,Cache-Control 的默認取值。中間節點不允許緩存,當下次 Browser 再次請求時 proxy 會做好請求轉發而不是自作主張給自己緩存的數據。

          協商緩存可以通過設置兩種 HTTP Header 實現:Last-Modified 和 ETag。

          3.no-cache

          客戶端緩存內容,是否使用緩存則需要經過協商緩存來驗證決定。表示不使用 Cache-Control 的緩存控制方式做前置驗證,而是使用 Etag 或者 Last-Modified 字段來控制緩存。

          4. no-store

          所有內容都不會被緩存,既不使用強緩存,也不使用協商緩存

          5. max-age

          max-age=xxx(xxx is numeric) 表示緩存內容將在 xxx 秒后失效must-revalidate如果超過了 max-age 的時間,瀏覽器必須向服務器發送請求,驗證資源是否還有效。

          6. s-maxage

          設置共享緩存,比如can。會覆蓋max-age和expires;只用于共享緩存(如CDN緩存)

          7. no-transform

          不得對資源進行轉換和轉變。例如,不得對圖像格式進行轉換。

          Cache-Control 優先級高于 Expires

          • 協商緩存

          協商緩存就是強緩存失效后,瀏覽器攜帶緩存標識向服務器發起請求,由服務器根據緩存標識決定是否使用緩存的過程,主要有以下兩種情況:

          > 協商緩存生效,返回 304 和 Not Modified。

          > 協商緩存失效,返回 200 和請求結果。

          協商緩存可以通過設置兩種 HTTP Header 實現:Last-Modified 和 ETag。

          • Last-Modified 和 If-Modified-Since

          瀏覽器在第一次訪問資源時,服務器返回資源的同時,在 response header 中添加 Last-Modified 的 header,值是這個資源在服務器上的最后修改時間,瀏覽器接收后緩存文件和header。

          瀏覽器下一次請求這個資源,瀏覽器檢測到有 Last-Modified 這個 header,于是添加If-Modified-Since 這個 header,值就是 Last-Modified 中的值。服務器再次收到這個資源請求,會根據 If-Modified-Since 中的值與服務器中這個資源的最后修改時間對比,`如果沒有變化,返回 304 和空的響應體,瀏覽器直接從緩存讀取。如果 If-Modified-Since 的時間小于服務器中這個資源的最后修改時間,說明文件有更新,則返回新的資源文件和 200。


          • ETag 和 If-None-MatchEtag

          是服務器響應請求時返回當前資源文件的一個唯一標識(由服務器生成),只要資源有變化,Etag 就會重新生成。瀏覽器在下一次加載資源向服務器發送請求時,會將上一次返回的 Etag 值放到 request header 里的 If-None-Match 里,服務器只需要比較客戶端傳來的 If-None-Match 跟自己服務器上該資源的 ETag 是否一致,就能很好地判斷資源相對客戶端而言是否被修改過了。如果服務器發現 ETag 匹配不上,那么直接以常規 GET 200 回包形式將新的資源(當然也包括了新的 ETag)發給客戶端。如果 ETag 是一致的,則返回 304 指示客戶端直接使用本地緩存即可。

          • Last-Modified 和 ETag 兩者對比

          1. 在精確度上,Etag 要優于 Last-Modified。Last-Modified 的時間單位是秒,如果某個文件在 1 秒內改變了多次,那么他們的 Last-Modified 其實并沒有體現出來修改。但是 Etag 每次都會改變確保了精度。如果是負載均衡的服務器,各個服務器生成的 Last-Modified 也有可能不一致。

          2. 在性能上,Etag 要遜于 Last-Modified,畢竟 Last-Modified 只需要記錄時間,而 Eta g需要服務器通過算法來計算出一個 hash 值。

          3. 在優先級上,服務器校驗優先考慮 Etag。

          1.3 緩存機制

          強緩存優先級高于協商緩存,若強緩存(Expires 和 Cache-Control)生效則直接使用緩存,若不生效則進行協商緩存(Last-Modified / If-Modified-Since 和 Etag / If-None-Match)。協商緩存由服務器決定是否使用緩存,若協商緩存失效,那么代表該請求的緩存失效,返回 200,重新返回資源和緩存標識,再存入瀏覽器緩存中;生效則返回 304,繼續使用緩存。

          那如果什么緩存策略都沒設置,瀏覽器會怎么處理呢?對于這種情況,瀏覽器會采用一個啟發式的算法,通常會取響應頭中的 Date 減去 Last-Modified 值的 10% 作為緩存時間

          一、HTTP緩存

          http請求做為影響前端性能極為重要的一環,因為請求受網絡影響很大,如果網絡很慢的情況下,頁面很可能會空白很久。對于首次進入網站的用戶可能要通過優化接口性能和接口數量來解決。但是,對于重復進入頁面的用戶,除了瀏覽器緩存,http緩存可以很大程度對已經加載過的頁面進行優化。



          1.緩存位置



          從緩存位置上來看,分為4種,從上往下依次檢查是否命中,如果但都沒有命中則重新發起請求。

          Service Worker 是運行在瀏覽器背后的獨立線程,一般可以用來實現緩存功能。使用 Service Worker的話,傳輸協議必須為 HTTPS。

          Memory Cache 也就是內存中的緩存,主要包含的是當前中頁面中已經抓取到的資源,例如頁面上已經下載的樣式、腳本、圖片等。讀取內存中的數據肯定比磁盤快,內存緩存雖然讀取高效,可是緩存持續性很短,會隨著進程的釋放而釋放。 一旦我們關閉 Tab 頁面,內存中的緩存也就被釋放了。

          內存緩存中有一塊重要的緩存資源是preloader相關指令(例如)下載的資源。它可以一邊解析js/css文件,一邊網絡請求下一個資源。

          Disk Cache 也就是存儲在硬盤中的緩存,讀取速度慢點,但是什么都能存儲到磁盤中,比之 Memory Cache 勝在容量和存儲時效性上。

          絕大部分的緩存都來自Disk Cache,在HTTP 的協議頭中設置。

          Push Cache(推送緩存)是 HTTP/2 中的內容,當以上三種緩存都沒有命中時,它才會被使用。它只在會話(Session)中存在,一旦會話結束就被釋放,并且緩存時間也很短暫,在Chrome瀏覽器中只有5分鐘左右,同時它也并非嚴格執行HTTP頭中的緩存指令。

          2.用戶操作對緩存的影響




          下面主要說一下前端優化能入手的地方,也就是強緩存和協商緩存,并且緩存策略都是通過設置 HTTP Header 來實現的。



          3.強緩存

          瀏覽器在第一次訪問接口后的response headers里會攜帶一些字段,這些字段決定關于這個請求的緩存情況,

          與強緩存相關的header字段有兩個:

          1、expires:過氣網紅,這是http1.0時的規范;它的值為一個絕對時間的GMT格式的時間字符串,如Mon, 10 Jun 2015 21:31:12 GMT,如果發送請求的時間在expires之前,那么本地緩存始終有效,否則就會發送請求到服務器來獲取資源

          2、cache-control:新星:max-age=number,這是http1.1時出現的header信息,主要是利用該字段的max-age值來進行判斷,它是一個相對值;資源第一次的請求時間和Cache-Control設定的有效期,計算出一個資源過期時間,再拿這個過期時間跟當前的請求時間比較,如果請求時間在過期時間之前,就能命中緩存,否則就不行;

          no-cache:不使用本地緩存。需要使用協商緩存,先與服務器確認返回的響應是否被更改,如果之前的響應中存在ETag,那么請求的時候會與服務端驗證,如果資源未被更改,則可以避免重新下載。

          no-store:直接禁止游覽器緩存數據,每次用戶請求該資源,都會向服務器發送一個請求,每次都會下載完整的資源。

          public:可以被所有的用戶緩存,包括終端用戶和CDN等中間代理服務器。

          private:只能被終端用戶的瀏覽器緩存,不允許CDN等中繼緩存服務器對其緩存。

          注意:如果cache-control與expires同時存在的話,cache-control的優先級高于expires

          強緩存時段命中,會直接從緩存中返回數據,返回值200;這一時間段,不管接口內容有沒有變化都不會進行請求更新。

          4.協商緩存

          當沒有強緩存時,會向服務端尋求幫助,也就是問一下服務端有沒有更改,向接口判斷是否有緩存。如果命中協商緩存則返回304狀態碼,并且從本地返回緩存內容。如果沒有命中,則重新發起請求。

          協商緩存需要跟服務端通過特殊標示連接,即第一次請求的響應頭帶上某個字段(Last-Modified或者Etag),則后續請求則會帶上對應的請求字段(If-Modified-Since或者If-None-Match),若響應頭沒有Last-Modified或者Etag字段,則請求頭也不會有對應的字段。

          具體過程如下:

          Last-Modified/If-Modified-Since

          1.瀏覽器第一次跟服務器請求一個資源,respone的header里加上Last-Modified:表示這個資源在服務器上的最后修改時間

          2.瀏覽器再次跟服務器請求這個資源時,在request的header上加上If-Modified-Since的header:上一次請求時返回的Last-Modified的值

          3.服務器再次收到資源請求時,會判斷最后修改時間是否有變化,如果沒有變化則返回304 Not Modified,但是不會返回資源內容;如果有變化,就正常返回資源內容,Last-Modified會被修改為最新的值。如果沒有變化,服務器返回304 Not Modified,Last-Modified不會修改,response header中不會再添加Last-Modified的header

          4.瀏覽器收到304的響應后,就會從緩存中加載資源

          Etag/If-None-Match

          由服務器生成的每個資源的唯一標識字符串,只要資源有變化就這個值就會改變;其判斷過程與Last-Modified/If-Modified-Since類似,與Last-Modified不一樣的是,當服務器返回304 Not Modified的響應時,由于ETag重新生成過,response header中還會把這個ETag返回,即使這個ETag跟之前的沒有變化。

          1.一些文件也許會周期性的更改,但是他的內容并不改變(僅僅改變的修改時間),這個時候我們并不希望客戶端認為這個文件被修改了,而重新GET;

          2.某些文件修改非常頻繁,比如在秒以下的時間內進行修改,(比方說1s內修改了N次),If-Modified-Since能檢查到的粒度是s級的,這種修改無法判斷(或者說UNIX記錄MTIME只能精確到秒);

          3.某些服務器不能精確的得到文件的最后修改時間。

          Last-Modified與ETag是可以一起使用的,服務器會優先驗證ETag,一致的情況下,才會繼續比對Last-Modified,最后才決定是否返回304。

          二、瀏覽器本地存儲

          瀏覽器本地緩存最常用的是cookie、localStroage、sessionStroage、webSql、indexDB。

          1.cookie使用

          cookie的用法很簡單,可以通過服務端設置,js也可以通過documnet.cookie="名稱=值;"(不要忘記以;分割)來設置。

          cookie的值字符串可以用encodeURIComponent()來保證它不包含任何逗號、分號或空格(cookie值中禁止使用這些值).

          cookie一般用做為登陸態保存、密碼、個人信息等關鍵信息保存使用,所以為了安全也是遵守同源策略原則的。

          可以通過下面參數具體設置:

          ;path=path (例如 '/', '/mydir') 如果沒有定義,默認為當前文檔位置的路徑。

          ;domain=domain (例如 'example.com', 'subdomain.example.com') 如果沒有定義,默認為當前文檔位置的路徑的域名部分。與早期規范相反的是,在域名前面加 . 符將會被忽視,因為瀏覽器也許會拒絕設置這樣的cookie。如果指定了一個域,那么子域也包含在內。

          ;max-age=max-age-in-seconds (例如一年為606024*365)

          ;expires=date-in-GMTString-format 如果沒有定義,cookie會在對話結束時過期這個值的格式參見Date.toUTCString()

          ;secure (cookie只通過https協議傳輸)

          ;HttpOnly 限制web頁面程序的browser端script程序讀取cookie

          缺點

          容量有限制,不能超過4kb

          在請求頭上帶著數據安全性差

          2.localStorage和sessionStorage使用

          html5新增本地存儲,localStorage生命周期是永久,除非主動清除localStorage信息,否則這些信息將永遠存在。存放數據大小為一般為5MB,sessionStorage僅在當前會話下有效,關閉頁面或瀏覽器后被清除。而且它僅在客戶端(即瀏覽器)中保存,不參與和服務器的通信。也是遵守同源策略原則的

          // 1、保存數據到本地 
          // 第一個參數是保存的變量名,第二個是賦給變量的值 
          localStorage.setItem('key', 'value'); 
          //復雜類型儲存需要**利用JSON.stringify**將對象轉換成字符串; 
          //利用**JSON.parse**將字符串轉換成對象 
          // 2、從本地存儲獲取數據 
          localStorage.getItem('key'); 
          // 3、從本地存儲刪除某個已保存的數據 
          localStorage.removeItem('key'); 
          // 4、清除所有保存的數據 
          localStorage.clear(); 
          

          3. Web SQL

          WebSQL是前端的一個獨立模塊,是web存儲方式的一種,我們調試的時候會經常看到,只是一般很少使用。并且,當前只有谷歌支持,ie和火狐均不支持。

          主要方法:

          1.openDatabase:這個方法使用現有的數據庫或者新建的數據庫創建一個數據庫對象。

          2.transaction:這個方法讓我們能夠控制一個事務,以及基于這種情況執行提交或者回滾。

          3.executeSql:這個方法用于執行實際的 SQL 查詢。

          4.indexDB

          IndexedDB 就是瀏覽器提供的本地數據庫,它可以被網頁腳本創建和操作。IndexedDB 允許儲存大量數據,提供查找接口,還能建立索引。這些都是 LocalStorage 所不具備的。就數據庫類型而言,IndexedDB 不屬于關系型數據庫(不支持 SQL 查詢語句),更接近 NoSQL 數據庫。

          推薦租用天下數據香港服務器;位于天下數據香港自建機房,帶寬充足,都采用BGP、CN2線路,不僅解決了南北互通的問題,也極大提升了香港服務器在國內的訪問速度,平均Ping值在20ms以內,穩定性好、訪問速度快。天下數據香港自建機房速度、所有機器含IPMI自主管理,自動重啟,免費重裝!詳詢客服。

          看過的大部分討論緩存的文章會直接從 HTTP 協議頭中的緩存字段開始,例如 Cache-Control, ETag, max-age 等。但偶爾也會聽到別人討論 memory cache, disk cache 等。那這兩種分類體系究竟有何關聯?是否有交叉?

          實際上,HTTP 協議頭的那些字段,都屬于 disk cache 的范疇,是幾個緩存位置的其中之一。因此本著從全局到局部的原則,我們應當先從緩存位置開始討論。等講到 disk cache 時,才會詳細講述這些協議頭的字段及其作用。

          我們可以在 Chrome 的開發者工具中,Network -> Size 一列看到一個請求最終的處理方式:如果是大小 (多少 K, 多少 M 等) 就表示是網絡請求,否則會列出 from memory cache, from disk cachefrom ServiceWorker

          它們的優先級是:(由上到下尋找,找到即返回;找不到則繼續)

          1. Service Worker
          2. Memory Cache
          3. Disk Cache
          4. 網絡請求

          memory cache

          memory cache 是內存中的緩存,(與之相對 disk cache 就是硬盤上的緩存)。按照操作系統的常理:先讀內存,再讀硬盤。disk cache 將在后面介紹 (因為它的優先級更低一些),這里先討論 memory cache。

          幾乎所有的網絡請求資源都會被瀏覽器自動加入到 memory cache 中。但是也正因為數量很大但是瀏覽器占用的內存不能無限擴大這樣兩個因素,memory cache 注定只能是個“短期存儲”。常規情況下,瀏覽器的 TAB 關閉后該次瀏覽的 memory cache 便告失效 (為了給其他 TAB 騰出位置)。而如果極端情況下 (例如一個頁面的緩存就占用了超級多的內存),那可能在 TAB 沒關閉之前,排在前面的緩存就已經失效了。

          剛才提過,幾乎所有的請求資源 都能進入 memory cache,這里細分一下主要有兩塊:

          1. preloader。熟悉瀏覽器處理流程的同學們應該了解,在瀏覽器打開網頁的過程中,會先請求 HTML 然后解析。之后如果瀏覽器發現了 js, css 等需要解析和執行的資源時,它會使用 CPU 資源對它們進行解析和執行。在古老的年代(大約 2007 年以前),“請求 js/css - 解析執行 - 請求下一個 js/css - 解析執行下一個 js/css” 這樣的“串行”操作模式在每次打開頁面之前進行著。很明顯在解析執行的時候,網絡請求是空閑的,這就有了發揮的空間:我們能不能一邊解析執行 js/css,一邊去請求下一個(或下一批)資源呢?
            這就是 preloader 要做的事情。不過 preloader 沒有一個官方標準,所以每個瀏覽器的處理都略有區別。例如有些瀏覽器還會下載 css 中的
            @import 內容或者 <video>poster等。而這些被 preloader 請求過來的資源就會被放入 memory cache 中,供之后的解析執行操作使用。
          2. preload (雖然看上去和剛才的 preloader 就差了倆字母)。實際上這個大家應該更加熟悉一些,例如 <link rel="preload">。這些顯式指定的預加載資源,也會被放入 memory cache 中。

          memory cache 機制保證了一個頁面中如果有兩個相同的請求 (例如兩個 src 相同的 <img>,兩個 href 相同的 <link>) 都實際只會被請求最多一次,避免浪費。

          不過在匹配緩存時,除了匹配完全相同的 URL 之外,還會比對他們的類型,CORS 中的域名規則等。因此一個作為腳本 (script) 類型被緩存的資源是不能用在圖片 (image) 類型的請求中的,即便他們 src 相等。

          在從 memory cache 獲取緩存內容時,瀏覽器會忽視例如 max-age=0, no-cache 等頭部配置。例如頁面上存在幾個相同 src 的圖片,即便它們可能被設置為不緩存,但依然會從 memory cache 中讀取。這是因為 memory cache 只是短期使用,大部分情況生命周期只有一次瀏覽而已。而 max-age=0 在語義上普遍被解讀為“不要在下次瀏覽時使用”,所以和 memory cache 并不沖突。

          但如果站長是真心不想讓一個資源進入緩存,就連短期也不行,那就需要使用 no-store。存在這個頭部配置的話,即便是 memory cache 也不會存儲,自然也不會從中讀取了

          disk cache

          disk cache 也叫 HTTP cache,顧名思義是存儲在硬盤上的緩存,因此它是持久存儲的,是實際存在于文件系統中的。而且它允許相同的資源在跨會話,甚至跨站點的情況下使用,例如兩個站點都使用了同一張圖片。

          disk cache 會嚴格根據 HTTP 頭信息中的各類字段來判定哪些資源可以緩存,哪些資源不可以緩存;哪些資源是仍然可用的,哪些資源是過時需要重新請求的。當命中緩存之后,瀏覽器會從硬盤中讀取資源,雖然比起從內存中讀取慢了一些,但比起網絡請求還是快了不少的。絕大部分的緩存都來自 disk cache。

          關于 HTTP 的協議頭中的緩存字段,我們會在稍后進行詳細討論。

          凡是持久性存儲都會面臨容量增長的問題,disk cache 也不例外。在瀏覽器自動清理時,會有神秘的算法去把“最老的”或者“最可能過時的”資源刪除,因此是一個一個刪除的。不過每個瀏覽器識別“最老的”和“最可能過時的”資源的算法不盡相同,可能也是它們差異性的體現。

          Service Worker

          上述的緩存策略以及緩存/讀取/失效的動作都是由瀏覽器內部判斷 & 進行的,我們只能設置響應頭的某些字段來告訴瀏覽器,而不能自己操作。舉個生活中去銀行存/取錢的例子來說,你只能告訴銀行職員,我要存/取多少錢,然后把由他們會經過一系列的記錄和手續之后,把錢放到金庫中去,或者從金庫中取出錢來交給你。

          但 Service Worker 的出現,給予了我們另外一種更加靈活,更加直接的操作方式。依然以存/取錢為例,我們現在可以繞開銀行職員,自己走到金庫前(當然是有別于上述金庫的一個單獨的小金庫),自己把錢放進去或者取出來。因此我們可以選擇放哪些錢(緩存哪些文件),什么情況把錢取出來(路由匹配規則),取哪些錢出來(緩存匹配并返回)。

          Service Worker 能夠操作的緩存是有別于瀏覽器內部的 memory cache 或者 disk cache 的。我們可以從 Chrome 的 F12 中,Application -> Cache Storage 找到這個單獨的“小金庫”。除了位置不同之外,這個緩存是永久性的,即關閉 TAB 或者瀏覽器,下次打開依然還在(而 memory cache 不是)。有兩種情況會導致這個緩存中的資源被清除:手動調用 API cache.delete(resource) 或者容量超過限制,被瀏覽器全部清空。

          如果 Service Worker 沒能命中緩存,一般情況會使用 fetch() 方法繼續獲取資源。這時候,瀏覽器就去 memory cache 或者 disk cache 進行下一次找緩存的工作了。注意:經過 Service Worker 的 fetch() 方法獲取的資源,即便它并沒有命中 Service Worker 緩存,甚至實際走了網絡請求,也會標注為 from ServiceWorker。這個情況在后面的第三個示例中有所體現。

          請求網絡

          如果一個請求在上述 3 個位置都沒有找到緩存,那么瀏覽器會正式發送網絡請求去獲取內容。之后容易想到,為了提升之后請求的緩存命中率,自然要把這個資源添加到緩存中去。具體來說:

          1. 根據 Service Worker 中的 handler 決定是否存入 Cache Storage (額外的緩存位置)。
          2. 根據 HTTP 頭部的相關字段(Cache-control, Pragma 等)決定是否存入 disk cache
          3. memory cache 保存一份資源 的引用,以備下次使用。

          按失效策略分類

          memory cache 是瀏覽器為了加快讀取緩存速度而進行的自身的優化行為,不受開發者控制,也不受 HTTP 協議頭的約束,算是一個黑盒。Service Worker 是由開發者編寫的額外的腳本,且緩存位置獨立,出現也較晚,使用還不算太廣泛。所以我們平時最為熟悉的其實是 disk cache,也叫 HTTP cache (因為不像 memory cache,它遵守 HTTP 協議頭中的字段)。平時所說的強制緩存,對比緩存,以及 Cache-Control 等,也都歸于此類。

          強制緩存

          強制緩存的含義是,當客戶端請求后,會先訪問緩存數據庫看緩存是否存在。如果存在則直接返回;不存在則請求真的服務器,響應后再寫入緩存數據庫。

          強制緩存直接減少請求數,是提升最大的緩存策略。 它的優化覆蓋了文章開頭提到過的請求數據的全部三個步驟。如果考慮使用緩存來優化網頁性能的話,強制緩存應該是首先被考慮的。

          可以造成強制緩存的字段是 Cache-controlExpires

          Expires

          這是 HTTP 1.0 的字段,表示緩存到期時間,是一個絕對的時間 (當前時間+緩存時間),如

          Expires: Thu, 10 Nov 2017 08:45:11 GMT

          在響應消息頭中,設置這個字段之后,就可以告訴瀏覽器,在未過期之前不需要再次請求。

          但是,這個字段設置時有兩個缺點:

          1. 由于是絕對時間,用戶可能會將客戶端本地的時間進行修改,而導致瀏覽器判斷緩存失效,重新請求該資源。此外,即使不考慮自信修改,時差或者誤差等因素也可能造成客戶端與服務端的時間不一致,致使緩存失效。
          2. 寫法太復雜了。表示時間的字符串多個空格,少個字母,都會導致非法屬性從而設置失效。

          Cache-control

          已知Expires的缺點之后,在HTTP/1.1中,增加了一個字段Cache-control,該字段表示資源緩存的最大有效時間,在該時間內,客戶端不需要向服務器發送請求

          這兩者的區別就是前者是絕對時間,而后者是相對時間。如下:

          Cache-control: max-age=2592000

          下面列舉一些 Cache-control 字段常用的值:(完整的列表可以查看 MDN)

          • max-age:即最大有效時間,在上面的例子中我們可以看到
          • must-revalidate:如果超過了 max-age 的時間,瀏覽器必須向服務器發送請求,驗證資源是否還有效。
          • no-cache:雖然字面意思是“不要緩存”,但實際上還是要求客戶端緩存內容的,只是是否使用這個內容由后續的對比來決定。
          • no-store: 真正意義上的“不要緩存”。所有內容都不走緩存,包括強制和對比。
          • public:所有的內容都可以被緩存 (包括客戶端和代理服務器, 如 CDN)
          • private:所有的內容只有客戶端才可以緩存,代理服務器不能緩存。默認值。

          這些值可以混合使用,例如 Cache-control:public, max-age=2592000

          這里有一個疑問:max-age=0no-cache 等價嗎?從規范的字面意思來說,max-age 到期是 應該 重新驗證,而 no-cache 是 必須 重新驗證。但實際情況以瀏覽器實現為準,大部分情況他們倆的行為還是一致的。

          順帶一提,在 HTTP/1.1 之前,如果想使用 no-cache,通常是使用 Pragma 字段,如 Pragma: no-cache(這也是 Pragma 字段唯一的取值)。但是這個字段只是瀏覽器約定俗成的實現,并沒有確切規范,因此缺乏可靠性。它應該只作為一個兼容字段出現,在當前的網絡環境下其實用處已經很小。

          總結一下,自從 HTTP/1.1 開始,Expires 逐漸被 Cache-control 取代。Cache-control 是一個相對時間,即使客戶端時間發生改變,相對時間也不會隨之改變,這樣可以保持服務器和客戶端的時間一致性。而且 Cache-control 的可配置性比較強大。

          Cache-control 的優先級高于 Expires,為了兼容 HTTP/1.0 和 HTTP/1.1,實際項目中兩個字段我們都會設置。

          協商緩存

          當強制緩存失效(超過規定時間)時,就需要使用對比緩存,由服務器決定緩存內容是否失效。

          流程上說,瀏覽器先請求緩存數據庫,返回一個緩存標識。之后瀏覽器拿這個標識和服務器通訊。如果緩存未失效,則返回 HTTP 狀態碼 304 表示繼續使用,于是客戶端繼續使用緩存;如果失效,則返回新的數據和緩存規則,瀏覽器響應數據后,再把規則寫入到緩存數據庫。

          但如果是 304 的話,返回的僅僅是一個狀態碼而已,并沒有實際的文件內容,因此 在響應體體積上的節省是它的優化點。它的優化覆蓋了文章開頭提到過的請求數據的三個步驟中的最后一個:“響應”。通過減少響應體體積,來縮短網絡傳輸時間。所以和強制緩存相比提升幅度較小,但總比沒有緩存好。

          對比緩存是可以和強制緩存一起使用的,作為在強制緩存失效后的一種后備方案。實際項目中他們也的確經常一同出現。

          對比緩存有 2 組字段(不是兩個):

          Last-Modified & If-Modified-Since

          1. 服務器通過 Last-Modified 字段告知客戶端,資源最后一次被修改的時間,例如
            Last-Modified: Mon, 10 Nov 2018 09:10:11 GMT
          2. 瀏覽器將這個值和內容一起記錄在緩存數據庫中。
          3. 下一次請求相同資源時時,瀏覽器從自己的緩存中找出“不確定是否過期的”緩存。因此在請求頭中將上次的 Last-Modified 的值寫入到請求頭的 If-Modified-Since 字段
          4. 服務器會將 If-Modified-Since 的值與 Last-Modified 字段進行對比。如果相等,則表示未修改,響應 304;反之,則表示修改了,響應 200 狀態碼,并返回數據。

          但是他還是有一定缺陷的:

          • 如果資源更新的速度是秒以下單位,那么該緩存是不能被使用的,因為它的時間單位最低是秒。
          • 如果文件是通過服務器動態生成的,那么該方法的更新時間永遠是生成的時間,盡管文件可能沒有變化,所以起不到緩存的作用。

          Etag & If-None-Match

          為了解決上述問題,出現了一組新的字段 EtagIf-None-Match

          Etag 存儲的是文件的特殊標識(一般都是 hash 生成的),服務器存儲著文件的 Etag 字段。之后的流程和 Last-Modified 一致,只是 Last-Modified 字段和它所表示的更新時間改變成了 Etag 字段和它所表示的文件 hash,把 If-Modified-Since 變成了 If-None-Match。服務器同樣進行比較,命中返回 304, 不命中返回新資源和 200。

          Etag 的優先級高于 Last-Modified

          緩存小結

          當瀏覽器要請求資源時

          1. 調用 Service Worker 的 fetch 事件響應
          2. 查看 memory cache
          3. 查看 disk cache。這里又細分:
            1. 如果有強制緩存且未失效,則使用強制緩存,不請求服務器。這時的狀態碼全部是 200
            2. 如果有強制緩存但已失效,使用對比緩存,比較后確定 304 還是 200


          1. 發送網絡請求,等待網絡響應
          2. 把響應內容存入 disk cache (如果 HTTP 頭信息配置可以存的話)
          3. 把響應內容 的引用 存入 memory cache (無視 HTTP 頭信息的配置)
          4. 把響應內容存入 Service Worker 的 Cache Storage


            • 同步請求方面,瀏覽器會自動把當次 HTML 中的資源存入到緩存 (memory cache),這樣碰到相同 src 的圖片就會自動讀取緩存(但不會在 Network 中顯示出來)
            • 異步請求方面,瀏覽器同樣是不發請求而直接讀取緩存返回。但同樣不會在 Network 中顯示。

          總體來說,如上面原理所述,no-cache 從語義上表示下次請求不要直接使用緩存而需要比對,并不對本次請求進行限制。因此瀏覽器在處理當前頁面時,可以放心使用緩存。

          當把服務器響應設置為 Cache-Control: no-store 時,情況發生了變化,三種資源都被請求了 2 次。而圖片因為還多一次異步請求,總計 3 次。(紅框中的都是那一次異步請求)


          這同樣說明:

            • 如之前原理所述,雖然 memory cache 是無視 HTTP 頭信息的,但是 no-store 是特別的。在這個設置下,memory cache 也不得不每次都請求資源。
            • 異步請求和同步遵循相同的規則,在 no-store 情況下,依然是每次都發送請求,不進行任何緩存。

          瀏覽器的行為

          所謂瀏覽器的行為,指的就是用戶在瀏覽器如何操作時,會觸發怎樣的緩存策略。主要有 3 種:

          • 打開網頁,地址欄輸入地址: 查找 disk cache 中是否有匹配。如有則使用;如沒有則發送網絡請求。
          • 普通刷新 (F5):因為 TAB 并沒有關閉,因此 memory cache 是可用的,會被優先使用(如果匹配的話)。其次才是 disk cache。
          • 強制刷新 (Ctrl + F5):瀏覽器不使用緩存,因此發送的請求頭部均帶有 Cache-control: no-cache(為了兼容,還帶了 Pragma: no-cache)。服務器直接返回 200 和最新內容。

          緩存的應用模式

          了解了緩存的原理,我們可能更加關心如何在實際項目中使用它們,才能更好的讓用戶縮短加載時間,節約流量等。這里有幾個常用的模式,供大家參考

          模式 1:不常變化的資源

          Cache-Control: max-age=31536000

          通常在處理這類資源資源時,給它們的 Cache-Control 配置一個很大的 max-age=31536000 (一年),這樣瀏覽器之后請求相同的 URL 會命中強制緩存。而為了解決更新的問題,就需要在文件名(或者路徑)中添加 hash, 版本號等動態字符,之后更改動態字符,達到更改引用 URL 的目的,從而讓之前的強制緩存失效 (其實并未立即失效,只是不再使用了而已)。

          在線提供的類庫 (如 jquery-3.3.1.min.js, lodash.min.js 等) 均采用這個模式。如果配置中還增加 public 的話,CDN 也可以緩存起來,效果拔群。

          這個模式的一個變體是在引用 URL 后面添加參數 (例如 ?v=xxx 或者 ?_=xxx),這樣就不必在文件名或者路徑中包含動態參數,滿足某些完美主義者的喜好。在項目每次構建時,更新額外的參數 (例如設置為構建時的當前時間),則能保證每次構建后總能讓瀏覽器請求最新的內容。

          特別注意: 在處理 Service Worker 時,對待 sw-register.js(注冊 Service Worker) 和 serviceWorker.js (Service Worker 本身) 需要格外的謹慎。如果這兩個文件也使用這種模式,你必須多多考慮日后可能的更新及對策。

          模式 2:經常變化的資源

          Cache-Control: no-cache

          這里的資源不單單指靜態資源,也可能是網頁資源,例如博客文章。這類資源的特點是:URL 不能變化,但內容可以(且經常)變化。我們可以設置 Cache-Control: no-cache 來迫使瀏覽器每次請求都必須找服務器驗證資源是否有效。

          既然提到了驗證,就必須 ETag 或者 Last-Modified 出場。這些字段都會由專門處理靜態資源的常用類庫(例如 koa-static)自動添加,無需開發者過多關心。

          也正如上文中提到協商緩存那樣,這種模式下,節省的并不是請求數,而是請求體的大小。所以它的優化效果不如模式 1 來的顯著。

          模式 3:非常危險的模式 1 和 2 的結合 (反例)

          Cache-Control: max-age=600, must-revalidate

          不知道是否有開發者從模式 1 和 2 獲得一些啟發:模式 2 中,設置了 no-cache,相當于 max-age=0, must-revalidate。我的應用時效性沒有那么強,但又不想做過于長久的強制緩存,我能不能配置例如 max-age=600, must-revalidate 這樣折中的設置呢?

          表面上看這很美好:資源可以緩存 10 分鐘,10 分鐘內讀取緩存,10 分鐘后和服務器進行一次驗證,集兩種模式之大成,但實際線上暗存風險。因為上面提過,瀏覽器的緩存有自動清理機制,開發者并不能控制。

          舉個例子:當我們有 3 種資源: index.html, index.js, index.css。我們對這 3 者進行上述配置之后,假設在某次訪問時,index.js 已經被緩存清理而不存在,但 index.html, index.css 仍然存在于緩存中。這時候瀏覽器會向服務器請求新的 index.js,然后配上老的 index.html, index.css 展現給用戶。這其中的風險顯而易見:不同版本的資源組合在一起,報錯是極有可能的結局。

          除了自動清理引發問題,不同資源的請求時間不同也能導致問題。例如 A 頁面請求的是 A.jsall.css,而 B 頁面是 B.jsall.css。如果我們以 A -> B 的順序訪問頁面,勢必導致 all.css 的緩存時間早于 B.js。那么以后訪問 B 頁面就同樣存在資源版本失配的隱患。


          這個問題涉及幾個小點

          • 'max-age=600' 和 'max-age=600,must-revalidate' 有什么區別?

          結論是沒有區別。在列出 max-age 了之后,must-revalidate 是否列出效果相同,瀏覽器都會在超過 max-age 之后進行校驗,驗證緩存是否可用。

          在 HTTP 的規范中,只闡述了 must-revalidate 的作用,卻沒有闡述不列出 must-revalidate 時,瀏覽器應該如何解決緩存過期的問題,因此這其實是瀏覽器實現時的自主決策。(可能有少數瀏覽器選擇在源站點無法訪問時繼續使用過期緩存,但這取決于瀏覽器自身)

          • 那 'max-age=600' 是不是也會引發問題?

          是的。問題的出現和是否列出 'must-revalidate' 無關,依然會存在 JS CSS等文件版本失配的問題。因此常規的網站在不同頁面需要使用不同的 JS CSS 文件時,如果要使用 max-age 做強緩存,不要設置一個太短的時間。

          • 那這類比較短的 max-age 到底能用在哪里呢?

          既然版本存在失配的問題,那么要避開這個問題,就有兩種方法。

          第一,整站都使用相同的 JS 和 CSS,即合并后的文件。這個比較適合小型站點,否則可能過于冗余,影響性能。(不過可能還是會因為瀏覽器自身的清理策略被清理,依然有隱患)

          第二,資源是獨立使用的,并不需要和其他文件配合生效。例如 RSS 就歸在此類。


          主站蜘蛛池模板: 亚洲精品日韩一区二区小说| 中文字幕在线一区二区在线 | 熟女性饥渴一区二区三区| 国产短视频精品一区二区三区| 亚洲日韩AV一区二区三区中文 | 国产一区二区三区在线2021 | 国产精品成人免费一区二区| 国产精品美女一区二区三区| 国产精品主播一区二区| 国产一区二区三区乱码网站| 久99精品视频在线观看婷亚洲片国产一区一级在线 | 精品日韩一区二区| 亚洲国产激情一区二区三区| 国产经典一区二区三区蜜芽| 精品伦精品一区二区三区视频 | 国产精品va无码一区二区| 精品无码一区在线观看| а天堂中文最新一区二区三区| 中文字幕一区在线观看视频| 无码人妻精品一区二区三区久久 | 无码人妻久久一区二区三区| 国产在线步兵一区二区三区| 97精品一区二区视频在线观看| jazzjazz国产精品一区二区| 国产成人精品视频一区| 美女福利视频一区二区| 精品国产毛片一区二区无码| 亚洲香蕉久久一区二区 | 亚洲AV无码一区二区三区网址| 久久精品综合一区二区三区| 亚洲制服中文字幕第一区| 久久久综合亚洲色一区二区三区 | 日韩精品无码一区二区三区免费 | 激情内射亚洲一区二区三区| 亚洲av无码一区二区三区天堂| 中文字幕日韩一区二区三区不| 中文字幕无线码一区二区 | 波多野结衣一区二区三区aV高清 | 亚洲国产精品一区二区久久hs| 国产亚洲情侣一区二区无码AV| 国模吧一区二区三区|