【轉載請注明出處】: https://blog.csdn.net/huahao1989/article/details/107730210
整個 Web 系統架構在HTTP 協定之上, 利用 HTTP 的緩存機制不僅可以極大地減少伺服器負載, 更重要的是加速頁面的載入,以及減少使用者的流量消耗。 快速到達和易于通路是 Web 與生俱來的特性, 其緩存機制也早已被伺服器和浏覽器廠商廣泛地實作, 我們作為 Web 内容的作者何樂而不為呢?
HTTP 緩存簡介
談起 HTTP 緩存你首先想到的一定是磁盤緩存,以及 304 狀态碼。 這是浏覽器處理緩存的兩種情況:
- 浏覽器詢問伺服器緩存是否有效,伺服器傳回 304 訓示浏覽器使用緩存。
- 資源仍然處于有效期時,浏覽器會直接使用磁盤緩存(在重新整理時稍有不同)。
每個狀态的詳細說明如下:
1、Cache-Control
Cache-Control
在 HTTP 響應頭中,用于訓示代理和 UA 使用何種緩存政策。比如:
- no-cache為本次響應不可直接用于後續請求(在沒有向伺服器進行校驗的情況下)
- no-store為禁止緩存(不得存儲到非易失性媒體,如果有的話盡量移除,用于敏感資訊)
-
為僅 UA 可緩存private
-
為大家都可以緩存。public
當
Cache-Control
為可緩存時,同時可指定緩存時間(比如
public, max-age:86400
)。 這意味着在 1 天(60x60x24=86400)時間内,浏覽器都可以直接使用該緩存。 當然浏覽器也有權随時丢棄任何一項緩存,是以這裡可能有一緻性問題。
2、Etag
如果資源本身确實會随時發生改動,還用 Cache-Control 就會使使用者看到的頁面得不到更新。 但如果還希望利用 HTTP 緩存,這就需要有條件的(conditional)HTTP 請求。
HTTP協定規格說明定義ETag為“被請求變量的實體标記”,弱實體隻要内容語義沒變即可,強實體指位元組必須完全一緻,建議使用弱實體。
如果響應體包含Etag字段,則浏覽器在下次發送請求時會帶 If-None-Match 頭字段, 來詢問伺服器該版本是否仍然可用。如果伺服器發現該版本仍然是最新的, 就可以傳回 304 狀态碼訓示 UA 繼續使用緩存。
類似伺服器端傳回的格式:
ETag: W/"3ae83efccfc543bad6866e325cd8bfb9"
用戶端的查詢更新格式是這樣的:
If-None-Match:W/"3ae83efccfc543bad6866e325cd8bfb9"
如果ETag沒改變,則傳回狀态304。
3、Last-Modified
在浏覽器第一次請求某一個URL時,伺服器端的傳回狀态會是200,内容是請求的資源,同時有一個Last-Modified的屬性标記(HttpReponse Header)此檔案在服務期端最後被修改的時間,格式類似這樣:
Last-Modified:Tue, 24 Feb 2009 08:01:04 GMT
用戶端第二次請求此URL時,根據HTTP協定的規定,浏覽器會向伺服器傳送If-Modified-Since報頭(HttpRequest Header),詢問該時間之後檔案是否有被修改過:
If-Modified-Since:Tue, 24 Feb 2009 08:01:04 GMT
如果伺服器端的資源沒有變化,則自動傳回HTTP 304(NotChanged)狀态碼,内容為空,這樣就節省了傳輸資料量。當伺服器端代碼發生改變或者重新開機伺服器時,則重新發出資源,傳回和第一次請求時類似。進而保證不向用戶端重複發出資源,也保證當伺服器有變化時,用戶端能夠得到最新的資源。
注:如果If-Modified-Since的時間比伺服器目前時間(目前的請求時間request_time)還晚,會認為是個非法請求
4、Expires
給出的日期/時間後,被響應認為是過時。如Expires:Thu, 02 Apr 2009 05:14:08 GMT
需和Last-Modified結合使用。用于控制請求檔案的有效時間,當請求資料在有效期内時用戶端浏覽器從緩存請求資料而不是伺服器端。當緩存中資料失效或過期,才決定從伺服器更新資料。
5、Last-Modified和Expires
Last-Modified辨別能夠節省一點帶寬,但是還是逃不掉發一個HTTP請求出去,而且要和Expires一起用。而Expires辨別卻使得浏覽器幹脆連HTTP請求都不用發,比如當使用者F5或者點選Refresh按鈕的時候就算對于有Expires的URI,一樣也會發一個HTTP請求出去,是以,Last-Modified還是要用的,而且要和Expires一起用。
6、Etag和Expires
如果伺服器端同時設定了Etag和Expires時,Etag原理同樣,即與Last-Modified/Etag對應的HttpRequestHeader:If-Modified-Since和If-None-Match。我們可以看到這兩個Header的值和WebServer發出的Last-Modified, Etag值完全一樣;在完全比對If-Modified-Since和If-None-Match即檢查完修改時間和Etag之後,伺服器才能傳回304.
7、Last-Modified和Etag
分布式系統裡多台機器間檔案的last-modified必須保持一緻,以免負載均衡到不同機器導緻比對失敗
分布式系統盡量關閉掉Etag(每台機器生成的etag都會不一樣)
Last-Modified和ETags請求的http報頭一起使用,伺服器首先産生Last-Modified/Etag标記,伺服器可在稍後使用它來判斷頁面是否已經被修改,來決定檔案是否繼續緩存
過程如下:
- 用戶端請求一個頁面(A)。
- 伺服器傳回頁面A,并在給A加上一個Last-Modified/ETag。
- 用戶端展現該頁面,并将頁面連同Last-Modified/ETag一起緩存。
- 客戶再次請求頁面A,并将上次請求時伺服器傳回的Last-Modified/ETag一起傳遞給伺服器。
- 伺服器檢查該Last-Modified或ETag,并判斷出該頁面自上次用戶端請求之後還未被修改,直接傳回響應304和一個空的響應體。
注:
- Last-Modified和Etag頭都是由WebServer發出的HttpReponse Header,WebServer應該同時支援這兩種頭。
- WebServer發送完Last-Modified/Etag頭給用戶端後,用戶端會緩存這些頭;
- 用戶端再次發起相同頁面的請求時,将分别發送與Last-Modified/Etag對應的HttpRequestHeader:If-Modified-Since和If-None-Match。這兩個Header的值和WebServer發出的Last-Modified,Etag值完全一樣;
- 通過上述值到伺服器端檢查,判斷檔案是否繼續緩存;
8、關于 Cache-Control: max-age=秒 和 Expires
Expires = 時間,HTTP 1.0 版本,緩存的載止時間,允許用戶端在這個時間之前不去檢查(發請求)
max-age = 秒,HTTP 1.1版本,資源在本地緩存多少秒。
如果max-age和Expires同時存在,則被Cache-Control的max-age覆寫。
Expires 的一個缺點就是,傳回的到期時間是伺服器端的時間,這樣存在一個問題,如果用戶端的時間與伺服器的時間相差很大,那麼誤差就很大,是以在HTTP 1.1版開始,使用Cache-Control: max-age=秒替代。
Expires =max-age + “每次下載下傳時的目前的request時間”
是以一旦重新下載下傳的頁面後,expires就重新計算一次,但last-modified不會變化
9、浏覽器重新整理
正常重新加載
按下重新整理按鈕或快捷鍵(在 MacOS 中是 Cmd+R)會觸發浏覽器的“正常重新加載”(normal reload), 此時浏覽器會執行一次 Conditional GET。
Cache-Control
等緩存頭字段會被忽略,并且帶
If-None-Match
,
If-Modified-Since
等頭字段。 此時伺服器總會收到一次 HTTP GET 請求。 在 Chrome 中按下重新整理,浏覽器還會帶如下請求頭:
Cache-Control:max-age=0
注意:在位址欄重新輸入目前頁面位址并按下回車也會當做重新整理處理, 這意味着隻有從新标簽頁或超連結打開時,才能觀察到直接使用硬碟緩存的情況。
強制重新加載
在 Chrome 中按下 Cmd+Shift+R (MacOS)可以觸發強制重新加載(Hard Reload), 此時包括頁面本身在内的所有資源都不會使用緩存。 浏覽器直接發送 HTTP 請求且不帶任何條件請求字段。 在 Chrome 中強制重新整理,浏覽器還會帶如下請求頭:
Cache-Control: no-cache
Pragma: no-cache
如何讓緩存的靜态檔案失效
一般我們在頁面上引用很多js或者css檔案,一旦請求過并且緩存在浏覽器中的資源并沒有失效,這個時候發現我們有個bug需要修改或者有新的東西需要釋出,你要怎麼辦?有些人就說了,強制重新整理下浏覽器就好了,或者在請求的時候不傳回304,直接傳回新的資源内容,但是這樣并不好操作,一是使用者未必知道強制重新整理或者清理緩存,二是我們隻想在釋出新的内容之後第一次使用者的請求傳回新的内容并緩存,後面還是走緩存;三是我們一般都會使用CDN,每次釋出完之後還需要清理CDN緩存,很是麻煩。其實有一個最簡單的辦法就是在引用這些靜态資源的時候加一個版本号即可,類似
.../js/index.js?v=1.0
這樣的,如果修改了内容,那麼隻需要改一下版本号即可,浏覽器自然會擷取到新的内容。
歡迎關注 “後端老鳥” 公衆号,接下來會發一系列的專題文章,包括Java、Python、Linux、SpringBoot、SpringCloud、Dubbo、算法、技術團隊的管理等,還有各種腦圖和學習資料,NFC技術、搜尋技術、爬蟲技術、推薦技術、音視訊互動直播等,隻要有時間我就會整理分享,敬請期待,現成的筆記、腦圖和學習資料如果大家有需求也可以公衆号留言提前擷取。由于本人在所有團隊中基本都處于攻堅和探路的角色,搞過的東西多,遇到的坑多,解決的問題也很多,歡迎大家加公衆号進群一起交流學習。