1. 程式人生 > >以 Okhttp3原始碼 為例 ------ 圖解 快取機制 的原理和實現(上)

以 Okhttp3原始碼 為例 ------ 圖解 快取機制 的原理和實現(上)

快取機制一直以來是一個不可忽視的重要模組,廣泛地被運用到 網頁端和移動端。對於伺服器而言,客戶端的快取很大程度上緩解了它的壓力,更是為使用者帶來了產品快速響應的體驗,擁有很多好處。既然是網路請求,必然與HTTP協議聯絡緊密,不論你是否有這之類的經驗,此篇將會從基礎開始總結,共同學習快取機制。

一. 初識快取機制原理

1. 為何需要快取?

  • 快取減少了冗餘的資料傳輸,節省了網路費用。
  • 快取緩解了網路瓶頸的問題,不需要更多的網路頻寬就能更快的載入頁面。
  • 快取降低了對原始伺服器的要求,伺服器可以更快的響應。

2. 快取的原理

這裡寫圖片描述

如上圖片所示,這就是瀏覽器的快取原理圖

,以流程圖的形式描繪出了大致的快取機制。看過幾遍後,便以為快取機制也不過如此,移動端的快取更是簡單,但實際並非如此,何時採取快取?快取何時失效?快取失效後的做法?等等,有許多值得思考的地方,不過有些部分瀏覽器已經代替實現了,所以,先來了解瀏覽器的快取原理

(1)首先在有快取的前提下,判斷快取是否過期,因為快取相對而言會有一個存在的有效時間,如過期的話需要進一步判斷是否向伺服器傳送請求。

(2)接著就是判斷 Etag ,它是關於快取的一個欄位,每次請求都會存在的一個識別符號,將文字雜湊編碼來標識當前文字的狀態。首先會判斷這個Etag是否存在,如果存在便會向伺服器傳送請求,在請求時會攜帶引數,引數If-None-Match

會將Etag標記上一起傳送給伺服器。伺服器再決策Etag是否過期,根據返回的響應碼來決定從快取讀取還是請求響應。

(3)但是Etag 這個欄位並不是必須存在的,當它不存在時,會再次判斷 Last-Modified欄位是否存在,這個欄位表示響應中資源最後一次修改的時間,說白了就是伺服器最新一次修改檔案的時間。如果存在的話,如同Etag一樣會向伺服器傳送請求,只是攜帶引數是會用If-Modified-Since去標識Last-Modified欄位,一起傳送給服務。在伺服器決策時,會將Last-Modified與伺服器修改檔案的時間進行比較,若無變化則直接從快取中讀取,否則請求響應,接收新的資料。

以上內容就是瀏覽器的快取機制,瞭解下來並非簡單地判斷快取過期後,就去訪問伺服器,還要再次進行一系列的判斷,真正確定快取與伺服器上內容不同時,需要更新時,才是去訪問伺服器請求最新資料。

這裡,還需要強調一點,雖然快取已經過期了,但是並非快取與伺服器的內容不同,比如服務端的資料並未做出任何更改,說明此時快取的依舊是最新資料!所以還需要更詳細的判斷再來決定是否需要請求伺服器更新資料,所以,避免了不必要的請求,這種快取機制很大程度上減輕了伺服器的壓力!

3. 快取相關的欄位

以下欄位都是在HTTP協議中的重要欄位。

(1)Expires:實體主體的過期時間。

此欄位最初出現於 HTTP 1.0協議,指定快取內容的失效時間(如果該文字內容支援快取),使用的是一個絕對值。(格林威治時間GMT標準)

(2)Cache-Control:控制快取的行為。

此欄位與Expires含義相同,那為何要存在兩個含義相同的欄位呢?上面有提到,Expires是一個絕對值,伺服器同客戶端校驗的時候,有可能出現偏差,因為客戶端的時間可以隨意進行修改。即我們可以人為快進客戶端時間,則伺服器收到該時間後判斷當前快取已失效,可實際上快取並未失效,所以這個欄位就會出現一些問題。在HTTP 1.1協議出現了Cache-Control欄位,它使用的是一個相對值,指令的引數有:

  • no-cache :無快取指令,即每次請求直接從伺服器獲取。”Cache-Control”: “no-cache
  • max-age :代表快取的有效時間,如果快取只是用來和伺服器做驗證,可是設定更有效的”Cache-Control”:”max-age=0”。
  • only-if-cached :先使用用快取的資料,如果客戶端有快取,會立即顯示客戶端的快取,這樣你的應用程式可以在等待最新的資料下載的時候顯示一些東西, 重定向request到本地快取資源,新增”Cache-Control”:”only-if-cached”。
  • max-stale :即使快取已過期,也可先展示出來。有時候過期的response比沒有response更好,設定最長過期時間來允許過期的response響應: int maxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks stale “Cache-Control”:”max-stale=” + maxStale。

(3)Last-Modified:資源的最後修改日期時間。

響應中資源最後一次修改的時間,用於判斷伺服器和客戶端資源是否一致的重要欄位

(4)ETag:資源的匹配資訊。

也是用於判斷伺服器和客戶端資源是否一致的重要欄位響應中資源的校驗值,為何會存在相同意義的欄位?因為如果你在伺服器端修改資源後,Last-Modified會改變,可是此時客戶端與伺服器端資源是否一定不同呢?也許你只是多加了一個空格,此時Last-Modified的改變意味著快取已失效,可這樣請求伺服器獲取到的資料卻是相同的。所以 HTTP協議推出了ETag 它將伺服器返回的 response整個編碼處理加密得到的一個值,在伺服器上某個時段是唯一標識的,將此值與客戶端快取中的ETag進行比較,可避免Last-Modified漏掉的問題。主旨在於比較兩者的內容是否發生變化,而不是單純的比較時間。

(5)Date:建立報文的時間。

伺服器建立報文時的時間。

(6)If-Modified-Since:比較資源的更新時間。

(判斷快取是否失效時標識在請求頭的標識量)客戶端存取的該資源最後一次修改的時間,同Last-Modified

(7)If-None-Match:比較實體標記。

(判斷快取是否失效時標識在請求頭的標識量)客戶端存取的該資源的檢驗值,同ETag

4. 舉例熟悉HTTP 欄位

這裡寫圖片描述
這裡寫圖片描述

以上檔案是騰訊網的一個js檔案,從右側可以得知:

  • Cache-Control:它支援快取,快取時間為259200秒,也就是三天。
  • Date:表示資源傳送的時間,指的是伺服器的時間,與當時請求時間並非相同!
  • Expires:資源過期的時間,用Date加上快取有效時間 max-age而得。
  • Last-Modified :最後一次修改的時間。
  • Etag:改js檔案被編碼加密後得到的一個標識值。

二. 瞭解 Okhttp3網路框架 快取功能

以上講解的部分,已經初始了快取機制的原理,接下來先通過一個小例子來見識 Okhttp3網路框架的快取機制。

1. 例子測試快取功能

   public class CacheHttp {

    public static void main(String args[]) throws IOException {


        int maxCacheSize = 10 * 1024 * 1024; //快取大小的限制:10M

        //建立cache 類,需要兩個引數(檔案目錄,檔案大小)
        Cache cache = new Cache(new File("/Users/gym/source"), maxCacheSize);

        OkHttpClient client = new OkHttpClient.Builder().cache(cache).build();

        Request request = new Request.Builder().url("http://www.qq.com/").
                cacheControl(new CacheControl.Builder().maxStale(365, TimeUnit.DAYS).build()).
                build();

        Response response = client.newCall(request).execute();


        String body1 = response.body().string();
        System.out.println("network response " + response.networkResponse());
        System.out.println("cache response " + response.cacheResponse());

        System.out.println("**************************");

        Response response1 = client.newCall(request).execute();

        String body2 = response1.body().string();
        System.out.println("network response " + response1.networkResponse());
        System.out.println("cache response " + response1.cacheResponse());


    }
}

關於以上程式碼需要注意的是:response請求資源後的相應有兩個相關方法,networkResponse()cacheResponse()。從字面意義上理解,一個是從網路請求中獲取資源,另一個是從快取中獲取資源。如果該資源是從伺服器獲取的,networkResponse()返回值不會為null,即cacheResponse()返回值為null;如果是從快取中獲取的,networkResponse()返回值為null,cacheResponse()返回值不為null。

這裡寫圖片描述

以上截圖就是測試後的結果:因為騰訊網是預設有快取的,所以我在第一次請求網路時,電腦中並無快取,資源從伺服器上獲取,cacheResponse()返回值為null。第二次重複請求騰訊網,自然是從快取中獲取資源。

2. 剖析快取檔案

從以上的小例子可得知Okhttp3網路框架的快取功能,再更詳細瞭解它的快取,我在程式碼中指定了將快取放到資料夾中,在兩次請求網路過後,資料夾多出了三個檔案,如下圖所示:
這裡寫圖片描述

(1)7f4c79817fabaeaa0e909754cfe655e7.0 檔案

這裡寫圖片描述

瞭解了HTTP協議中的欄位後,發現這個內容很是熟悉,是請求騰訊網後返回的response的一些資料。比如說請求的url、請求方式、HTTP協議、返回碼,下面的就是騰訊網響應頭的資料,不妨來看看哪些與快取相關?

  • Cache-Control:騰訊網支援的快取有效時間為60秒。
  • Date:請求資源時的伺服器時間(注意!該時間與客戶端的時間是不對應的)。
  • Expires:快取失效時間,即Date加上快取有效時間max-age

(2)7f4c79817fabaeaa0e909754cfe655e7.1 檔案

瞭解了一些基本資料後,騰訊網具體資源展示是在第二個檔案,由於全部都是二進位制資料,這裡就不截圖展示了。

其實觀察前兩個檔名,很相似卻又有些不一樣,其實這是根據騰訊網的url加密後所得。

(3)journal 檔案

這裡寫圖片描述

此檔案用於 Okhttp 快取讀取目錄時會用到的,可以檢視當前客戶端請求網路的次數和具體呼叫請求的地方。看第一行資料,這個也是DiskLruCache所要用到的目錄結構。

第一篇是介紹總結一下快取機制有關的原理和Ohttp3網路框架有關的功能,先鋪墊一下基礎。第二篇正式從解析原始碼。

這裡正式感謝nate老師對此的講解,我才得以總結學習,thx~

希望對你們有幫助 :)