(之前覺得HTTP緩存是個簡單的事,項目中遇到後才發覺關於緩存實踐有挺深的學問)html
這裏的緩存指的是http標準中定義的緩存技術(如Cache-Control),主要由服務端設置和處理(固然還要由客戶端旳瀏覽器配合)而不須要前端開發者參與。當作了恰當的參數設置後客戶端發起請求過程當中緩存會由瀏覽器和服務端間自動進行處理完成,而不用用戶參與。前端
當前HTML5 API中有LocalStorage、SessionStorage技術也被用於「緩存」,然而這類本質上是種相似於DB的本地存儲,由開發者進行控制。這種不是咱們這裏所要討論的緩存。web
整個 Web 系統架構在 HTTP 協議 之上, 利用 HTTP 的緩存機制不只能夠極大地減小服務器負載, 更重要的是加速頁面的載入以及減小用戶的流量消耗。 HTTP緩存機制也早已普遍地被服務器廠商(如 Tomcat、Apache、Virgo)和瀏覽器廠商(如Tomcat、Apache、Virgo)實現。此外,一些服務器端框架(如 Django、Express.js)也實現了 HTTP 緩存機制。chrome
瀏覽器和服務器之間使用的緩存策略能夠分爲強緩存、協商緩存兩種,一般一塊兒搭配使用。瀏覽器
強緩存:用戶發送的請求,直接從用戶客戶端緩存讀取,不發送到服務端,無與服務端交互緩存
協商緩存:用戶發送的請求,發送到服務端,由服務端根據參數判斷是否讓客戶端從客戶端緩存讀取。協商緩存沒法減小請求開銷,但可減小返回的正文大小服務器
相同點:最終都是從客戶端緩存讀取而不用服務端發送請求的數據回來;架構
不一樣點:客戶端的請求是否發送到服務端,便是否與服務端交互負載均衡
適用性:強緩存適用於不多變化的資源,能夠把過時時間設長;協商緩存適用於常常變化的資源。框架
與強緩存和協商緩存相關的HTTP Header參數以下:(注:HTTP標準中Header名首字母大寫)
Date:用於記錄這次緩存時服務器的時間
Cache-Control、Expires、Pragma:用於強緩存的判斷
Last-Modified、 If-Modified-Since、Etag、 If-None-Match:用於協商緩存判斷,以實現有條件的HTTP請求
後面用到時詳細介紹。
總體過程以下,若(b)是N則是使用強緩存、若(c)爲Y則是使用協商緩存。
總體過程可理解爲:
(a)瀏覽器判斷是否有緩存
瀏覽器會在系統某個位置專門存放緩存信息。經過檢查該位置是否有對應請求的緩存信息來判斷是否有緩存(對於Chrome 66以前的版本也可在chrome://cache 查看請求及對應的緩存信息)。
緩存信息包括請求的響應頭及對應的緩存內容。一個緩存示例以下:
(b)判斷緩存是否過時
客戶端檢查到本地有緩存的話會判斷緩存是否過時。
緩存信息中包含被緩存的請求的響應頭。裏面包含Date、Cache-Control、Expires、Pragma字段(可能不是每一個都有)用於判斷緩存是否過時。下面介紹各字段的做用再說明判斷方法。
各字段旳做用:
Date:指明這次緩存的時間(服務器時間)
Expires:指明緩存過時的絕對時間(服務器時間),如 Thu, 28 Sep 2017 06:38:37GMT 。http 1.0的標準。存在的問題:客戶端服務端時間不一致可能致使緩存效果不符合指望。
Cache-Control:指明緩存過時策略,可當作是Expires的補充,使用相對時間。http 1.1的標準。屬性設置:
Cache-Control的設置規則:
Pragma:只有 Pragma: no-cach 一種用法,與Cache-Control:no-cache的做用同樣。出現緣由:http 1.0沒有實現no cache的功能,所以用Pagama使no cache功能應用到1.0。
判斷緩存是否過時的規則:
若Cache-Control中有max-age或s-maxage則用它們加上date做爲過時絕對時間,不然直接用expires指定的時間做爲過時絕對時間。將絕對時間與當前時間比較是否過時。若未過時則直接使用緩存(此時就是前面說的強緩存)。
(c)向服務器詢問是否使用緩存
若客戶端判斷緩存已過時,則向服務端發送請求。服務端根據Last-Modified/If-Modified-Since、Etag/If-None-Match字段(也可能不是每一個都有)判斷是否讓客戶端用緩存。
下面一樣先介紹各字段做用再說明判斷方法。
各字段做用:(這幾個字段一般用於有條件的HTTP請求)
Last-Modified:代表請求的服務端資源上次的修改時間(服務器時間)
If-Modified-Since:客戶端保留的資源上次的修改時間
Etag:服務端根據資源內容生成的一段標識(不惟一,一般爲文件的md5或者hash值或版本號等,只要保證寫入和驗證時的方法一致便可)
If-None-Match:客戶端保留的上次獲取到的的Etag
判斷規則(這部分一般服務器實現或服務端代碼本身實現):
瀏覽器向服務端發送請求時,若上一次的緩存中有Last-Modified或Etag字段則在request header中加入If-Modified-Since(對應Last-Modified)或If-None-Match字段(對應If-None-Match),以詢問服務端資源是否被修改過。服務端判斷資源是否修改(對於If-Modified-Since服務端看自該時間後資源是否修改、對於If-None-Match服務端比較資源目前的Etag是否與所收到的同樣):若未修改則服務端返回304,瀏覽器使用緩存;不然瀏覽器再次請求資源,狀態碼爲200、資源爲服務器最新資源。Etag處理流程示意圖:
一般狀況下,若同時發送If-None-Match、If-Modified-Since字段,服務器只要比較Etag的內容便可(即Etag的優先級高於另者),固然具體處理方式,看服務器的約定規則。
注:
使用ETag能夠解決Last-modified存在的一些問題:
分佈式系統中儘可能不用Etag,由於每臺機器生成的Etag都同樣。(啥意思???)
分佈式系統裏多臺機器間資源的Last-Modified必須一致,以避免負載均衡不一樣致使對比失敗
至此,結合上述參數的整個請求處理過程以下:
Chrome瀏覽器從本地緩存中取資源時,發起的請求中會有"from memory cache" 或 "from disk cache" 標識(最先只有from cache,從某個版本起改成此二者),示例:
Chrome from memory cache與from disk cache的區別:
Chrome employs two caches — an on-disk cache and a very fast in-memory cache. The lifetime of an in-memory cache is attached to the lifetime of a render process, which roughly corresponds to a tab. Requests that are answered from the in-memory cache are invisible to the web request API. If a request handler changes its behavior (for example, the behavior according to which requests are blocked), a simple page refresh might not respect this changed behavior. To make sure the behavior change goes through, call handlerBehaviorChanged() to flush the in-memory cache. But don't do it often; flushing the cache is a very expensive operation. You don't need to call handlerBehaviorChanged() after registering or unregistering an event listener.
用戶操做 | Cache-Control/Expires | Last-Modified/Etag |
地址欄回車 | 有效 | 有效 |
頁面連接跳轉 | 有效 | 有效 |
新開窗口 | 有效 | 有效 |
前進、後退 | 有效 | 有效 |
F5刷新 | 無效 | 有效 |
Ctrl + F5刷新 | 無效 | 無效 |
// 設置緩存。強緩存適合不頻繁變動的文件,協商緩存用於變化頻繁的文件。咱們的業務場景中資源不多變化,故以強緩存爲主、協商緩存幾乎觸發不到。 { // 如下設置強緩存 int cacheTimeSecond = 5 * 3600;// 強緩存時間 Date curDate = new Date(); Date newLastModifyDate = PersistentStorageUtilFactory.getPersistStorageUtil() .getObjectMetaData(bucketName, objKey).getLastModified(); response.setHeader("Date", curDate.toGMTString()); response.setHeader("Cache-Control", "private, max-age=" + cacheTimeSecond);// second。時間太長的話可能致使前端緩存的資源與服務端資源不一致。max-age會覆蓋expires response.setDateHeader("expries", curDate.getTime() + cacheTimeSecond * 1000); // 如下設置協商緩存 String newEtagStr = String.valueOf(newLastModifyDate.hashCode()); SimpleDateFormat sdf = new SimpleDateFormat(); String oldModifiedTimeStr = request.getHeader("If-Modified-Since"); String oldEtagStr = request.getHeader("If-None-Match"); boolean isModifiedTimeNotChanged = null != oldModifiedTimeStr && sdf.parse(oldModifiedTimeStr).equals(newLastModifyDate); boolean isContentNotChanged = null != oldEtagStr && oldEtagStr.equals(newEtagStr); if (isModifiedTimeNotChanged || isContentNotChanged) {// 服務器資源沒有更新。返回304 response.setStatus(HttpStatus.SC_NOT_MODIFIED); return; } else { response.setHeader("Last-Modified", newLastModifyDate.toGMTString()); response.setHeader("Etag", newEtagStr); } }
https://blog.csdn.net/u014590757/article/details/80140654
http://www.alloyteam.com/2016/03/discussion-on-web-caching/
https://excaliburhan.com/post/things-you-should-know-about-browser-cache.html