HTTP緩存及其使用

(之前覺得HTTP緩存是個簡單的事,項目中遇到後才發覺關於緩存實踐有挺深的學問)html

0. What  

這裏的緩存指的是http標準中定義的緩存技術(如Cache-Control),主要由服務端設置和處理(固然還要由客戶端旳瀏覽器配合)而不須要前端開發者參與。當作了恰當的參數設置後客戶端發起請求過程當中緩存會由瀏覽器和服務端間自動進行處理完成,而不用用戶參與。前端

當前HTML5 API中有LocalStorage、SessionStorage技術也被用於「緩存」,然而這類本質上是種相似於DB的本地存儲,由開發者進行控制。這種不是咱們這裏所要討論的緩存。web

 

整個 Web 系統架構在 HTTP 協議 之上, 利用 HTTP 的緩存機制不只能夠極大地減小服務器負載, 更重要的是加速頁面的載入以及減小用戶的流量消耗。 HTTP緩存機制也早已普遍地被服務器廠商(如 Tomcat、Apache、Virgo)和瀏覽器廠商(如Tomcat、Apache、Virgo)實現。此外,一些服務器端框架(如 Django、Express.js)也實現了 HTTP 緩存機制。chrome

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則是使用協商緩存。

總體過程可理解爲:

  1. 發送請求前瀏覽器先檢查本地是否有緩存,沒有則直接向服務器請求資源;
  2. 若本地有緩存且還沒有過時(根據請求頭的Expires和Cache-Control)則直接取緩存,若過時則向服務端發送請求;
  3. 服務端收到請求後判斷是否仍讓客戶端使用緩存(根據請求頭的Last-Modified和Etag,例如文件沒變化,服務端可以讓瀏覽器直接用緩存而不用從新發文件給用戶)、如果則返回304告訴客戶端直接取緩存
  4. 若以上兩個緩存都沒命中,則瀏覽器再請求服務器獲取最新資源,服務器返回資源的同時設置一些上述緩存參數

(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會覆蓋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);
            }
        }
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

相關文章
相關標籤/搜索