HTTP缓存及其使用
(以前以為HTTP緩存是個簡單的事,項目中遇到后才發覺關于緩存實踐有挺深的學問)
0. What??
這里的緩存指的是http標準中定義的緩存技術(如Cache-Control),主要由服務端設置和處理(當然還要由客戶端旳瀏覽器配合)而不需要前端開發者參與。當做了恰當的參數設置后客戶端發起請求過程中緩存會由瀏覽器和服務端間自動進行處理完成,而不用用戶參與。
當前HTML5 API中有LocalStorage、SessionStorage技術也被用于“緩存”,然而這類本質上是種類似于DB的本地存儲,由開發者進行控制。這種不是我們這里所要討論的緩存。
?
整個 Web 系統架構在 HTTP 協議 之上, 利用 HTTP 的緩存機制不僅可以極大地減少服務器負載, 更重要的是加速頁面的載入以及減少用戶的流量消耗。 HTTP緩存機制也早已廣泛地被服務器廠商(如 Tomcat、Apache、Virgo)和瀏覽器廠商(如Tomcat、Apache、Virgo)實現。此外,一些服務器端框架(如 Django、Express.js)也實現了 HTTP 緩存機制。
1. 兩類緩存
瀏覽器和服務器之間使用的緩存策略可以分為強緩存、協商緩存兩種,通常一起搭配使用。
強緩存:用戶發送的請求,直接從用戶客戶端緩存讀取,不發送到服務端,無與服務端交互
協商緩存:用戶發送的請求,發送到服務端,由服務端根據參數判斷是否讓客戶端從客戶端緩存讀取。協商緩存無法減少請求開銷,但可減少返回的正文大小
相同點:最終都是從客戶端緩存讀取而不用服務端發送請求的數據回來;
不同點:客戶端的請求是否發送到服務端,即是否與服務端交互
適用性:強緩存適用于很少變化的資源,可以把過期時間設長;協商緩存適用于經常變化的資源。
?
2. 相關參數
與強緩存和協商緩存相關的HTTP Header參數如下:(注:HTTP標準中Header名首字母大寫)
Date:用于記錄此次緩存時服務器的時間
Cache-Control、Expires、Pragma:用于強緩存的判斷
Last-Modified、 If-Modified-Since、Etag、 If-None-Match:用于協商緩存判斷,以實現有條件的HTTP請求
后面用到時詳細介紹。
?
3. 整體過程
整體過程如下,若(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的標準。屬性設置:
- max-age: 設置普通緩存的最大有效時間(單位為s)。max-age會覆蓋掉Expires
- s-maxage: 只用于共享緩存如CDN緩存(單位為s)。與max-age 的區別:max-age用于普通緩存而s-maxage用于代理緩存。s-maxage會覆蓋max-age 和 Expires設置
- public:響應會被緩存,且在多用戶(如多個瀏覽器)間共享。未指定public或private則默認是public
- private: 響應只作為私有緩存,不能在用戶間共享。如果要求HTTP認證,響應會自動設置為private
- no-cache: 指定不緩存響應,表明資源不進行緩存。設置了no-cache之后并不代表瀏覽器不緩存,而是在緩存前要向服務器確認資源是否被更改。故有時只設置no-cache防止緩存不夠保險,還可加上private指令,將過期時間設為過去的時間
- no-store: 絕對禁止緩存,每次請求資源都會從服務端重新獲取
- must-revalidate: 如果頁面過期,則去服務器進行獲取。不常用
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存在的一些問題:
- 某些服務器不能精確得到資源的最后修改時間,這樣就無法通過最后修改時間判斷資源是否更新
- 如果資源修改非常頻繁,在秒以下的時間內進行修改,而Last-modified只能精確到秒
- 一些資源的最后修改時間改變了,但是內容沒改變,使用ETag就認為資源還是沒有修改的
分布式系統中盡量不用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.
?
4. 緩存參數設置總結
- 謹慎地使用過期時間,最好配合 MD5 一起使用。參數設置過長會導致客戶端看到的一直是強緩存的舊數據,得不到及時更新。CDN資源、圖片等常過期時間通常較長。
- 總是啟用條件請求,比如 Etag 或 Last-Modified。
- 文件服務采用 Last-Modified,動態內容采用 Etag。
- 分離經常變化的部分,也會提高緩存的命中率。
?
5. 用戶行為對瀏覽器緩存的影響
| 用戶操作 | Cache-Control/Expires | Last-Modified/Etag |
| 地址欄回車 | 有效 | 有效 |
| 頁面鏈接跳轉 | 有效 | 有效 |
| 新開窗口 | 有效 | 有效 |
| 前進、后退 | 有效 | 有效 |
| F5刷新 | 無效 | 有效 |
| Ctrl + F5刷新 | 無效 | 無效 |
?
6. 實踐
Java Web中設置緩存
// 設置緩存。強緩存適合不頻繁變更的文件,協商緩存用于變化頻繁的文件。我們的業務場景中資源很少變化,故以強緩存為主、協商緩存幾乎觸發不到。 {// 以下設置強緩存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會覆蓋expiresresponse.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);}} View Code?
?
?
?
7. 參考資料
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
?
轉載于:https://www.cnblogs.com/z-sm/p/10246261.html
總結
以上是生活随笔為你收集整理的HTTP缓存及其使用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java 打包jar文件以在没有安装J
- 下一篇: java中的null类型---有关nul