App網絡請求接口緩存

本次博文並不貼具體實現代碼,只講方案和流程,因爲涉及的SQL、SP查詢和文件緩存都是一些基本操作,只是額外結合了一點Http協議的東西,具體還請結合自身項目框架實現。

爲了提高App的網絡請求響應速度和減輕服務器的請求壓力,比如某些接口的數據更新的並不頻繁,沒必要每次都去服務器請求數據下來,接口緩存是一個非常棒的解決方案,那麼App內的接口緩存機制如何實現呢?首先,這個緩存機制要滿足:

1、接口的正常請求和資源的正常獲取

2、如再次請求時,資源未發生更新,則不必再次返回資源

3、如再次請求時,資源已有更新,則按常規請求返回

可以看出這個緩存機制的核心是請求資源有無發生更新,那麼我們如何知道存在服務器的資源是否有無更新呢?這就得搬出Http協議的Cache-Control了,瀏覽器對於網頁的緩存就是通過它來實現的,我們App要實現同樣的功能也少不了它的參與。

Cache-Control驗證

衆所周知,網頁的緩存是由HTTP消息頭中的“Cache-Control”來控制的,常見的取值有private、no-cache、max-age、must-revalidate等,默認爲private。其作用根據不同的重新瀏覽方式分爲以下幾種情況。

Cache-directive 說明
public 所有內容都將被緩存(客戶端和代理服務器都可緩存)
private 內容只緩存到私有緩存中(僅客戶端可以緩存,代理服務器不可緩存)
no-cache 必須先與服務器確認返回的響應是否被更改,然後才能使用該響應來滿足後續對同一個網址的請求。因此,如果存在合適的驗證令牌 (ETag),no-cache 會發起往返通信來驗證緩存的響應,如果資源未被更改,可以避免下載。
no-store 所有內容都不會被緩存到緩存或 Internet 臨時文件中
must-revalidation/proxy-revalidation 如果緩存的內容失效,請求必須發送到服務器/代理以進行重新驗證
max-age=xxx (xxx is numeric) 緩存的內容將在 xxx 秒後失效, 這個選項只在HTTP 1.1可用, 並如果和Last-Modified一起使用時, 優先級較高

以上六種屬性全部是針對瀏覽器緩存而設置的,而今天要實現的App接口緩存只需要用到no-cache做處理。no-cache並不是不使用緩存的意思,只是每次使用前需要跟服務器端進行驗證,詢問請求的資源是否更改過。

那麼當Cache-Control爲no-cache時,我們如何與服務器驗證呢?

1、Last-Modified

服務端在返回資源時,會將該資源的最後更改時間通過Last-Modified字段返回給客戶端。客戶端下次請求時通過If-Modified-Since(If-Unmodified-Since不作討論)帶上Last-Modified,服務端檢查該時間是否與服務器的最後修改時間一致:如果一致,則返回304狀態碼,不返回資源;如果不一致則返回200和修改後的資源,並帶上新的時間。

比如我請求一個文件,把響應頭打印出看看。

thread {
        val url = URL("https://www.olading.com/static/com/download/xxx")
        val connection = url.openConnection() as HttpURLConnection
        connection.requestMethod = "GET"
        connection.connect()
        connection.headerFields.forEach { t, u ->
            println("$t : $u")
        }
    }.run()
Accept-Ranges : [bytes]
null : [HTTP/1.1 200 OK]
Cache-Control : [max-age=30,no-cache]
Server : [nginx/1.15.1]
Connection : [keep-alive]
Last-Modified : [Thu, 04 Jul 2019 01:56:53 GMT]
Content-Length : [10477293]
Date : [Thu, 22 Aug 2019 08:03:36 GMT]
Content-Type : [application/octet-stream]

請求成功,返回200並且附帶了請求的資源和Last-Modified字段。

接着我們再次發起請求並在請求頭帶上Last-Modified值。

connection.setRequestProperty("If-Modified-Since","Thu, 04 Jul 2019 01:56:53 GMT")
null : [HTTP/1.1 304 Not Modified]
Cache-Control : [max-age=30,no-cache]
Server : [nginx/1.15.1]
Connection : [keep-alive]
Last-Modified : [Thu, 04 Jul 2019 01:56:53 GMT]
Date : [Thu, 22 Aug 2019 08:16:28 GMT]

這次返回了304並且沒有附帶資源,這種情況下我們就可以針對不同場景使用本地緩存或者不作處理了。

2、ETag

只是以修改時間來判斷還是有缺陷,比如文件的最後修改時間變了,但內容沒變。對於這樣的情況,ETag提供了更加有效的驗證方式。
服務器通過某個算法對資源進行計算,取得一串值(類似於文件的md5值),之後將該值通過ETag返回給客戶端,客戶端下次請求時通過If-None-Match(If-Match不作討論)帶上該值,服務器對該值進行對比校驗:如果一致則返回304,否則返回200和最新的資源。

接着上面的那個請求,第一次響應返回了200、ETag和資源。

Accept-Ranges : [bytes]
null : [HTTP/1.1 200 OK]
Cache-Control : [max-age=30,no-cache]
Server : [nginx/1.15.1]
ETag : ["5d1d5ce5-9fdeed"]
Connection : [keep-alive]
Content-Length : [10477293]
Date : [Thu, 22 Aug 2019 09:07:54 GMT]
Content-Type : [application/octet-stream]

在請求頭帶上ETag再來一次試試。

connection.setRequestProperty("If-None-Match","\"5d1d5ce5-9fdeed\"")
null : [HTTP/1.1 304 Not Modified]
Cache-Control : [max-age=30,no-cache]
Server : [nginx/1.15.1]
ETag : ["5d1d5ce5-9fdeed"]
Connection : [keep-alive]
Date : [Thu, 22 Aug 2019 09:10:10 GMT]

這樣就不怕最後修改時間變了,但是內容沒變時對資源的重複獲取。

對於ETag和Last-Modified的使用我比較推薦ETag,具體使用哪種驗證方式可根據自身方便使用選擇。

App接口緩存流程

這個流程看起來蠻簡單的,但是其中隱藏的問題可不少。比如,如果並不是所有的接口需要緩存,那麼如何區分需要緩存的接口呢?區分的地方又在哪裏?我又如何找到對應接口的緩存?

1、對於每次請求返回的ETag或Last-Modified,可以使用數據庫存儲或者選擇使用SP存儲,使用URl作爲標識,每次請求時先去查詢對應接口的ETag或Last-Modified。

2、在Header設置ETag或Last-Modified,推薦在一個統一的地方設置,比如攔截器,如果需要針對部分接口採取緩存,可以在Application類中創建一個緩存URL列表,在攔截器中判斷請求的URL是否在這個列表添加Header。

攔截器內部邏輯

3、請求成功後的數據,對於ETag和Last-Modified還是使用SQL或SP存儲。真正的數據則需要針對304和200做不同處理。

返回200:

將數據進行本地持久化處理,採用文件存儲,儲存在統一的緩存文件夾,文件名稱可以採用URL+後綴的形式。

返回304:

使用本地存儲數據,進入緩存文件夾打開URL+後綴的文件,讀入數據並返回。

總結

爲什麼我只講方案不貼具體代碼呢,因爲不好搞(狗頭.jpg),其實上面的緩存步驟,又可以分爲分散處理和統一處理,分散處理你只需在你需要進行緩存的請求回調中進行上面的緩存操作。統一處理則在封裝的接口回調中區分需要緩存的接口進行統一操作。因爲每個人的項目框架和結構都不相同,很難講一個例子,涵蓋所有情況,如果我只是把我項目中的情況拿出來講,可能對你根本沒什麼幫助,所以還不如講一個大概方案,再結合個人實際選擇符合自己需求的最優解。而對於那些根本沒頭緒的人來說,相信本次例子也是一個很不錯的參考。

如果你喜歡我的分享的話,還請收藏、點贊、多多留言~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章