前言
不論是在移動(dòng)端還是前端,web緩存(Http緩存)都是很重要的一部分。在移動(dòng)端,對(duì)于用戶流量控制的優(yōu)化,web緩存就起到了決定性的作用。最開(kāi)始接觸這塊是在使用OkHttp的攔截器修改Http請(qǐng)求頭進(jìn)行web緩存,后來(lái)仔細(xì)研究了一下這塊。而Web緩存就是客戶端和服務(wù)端之間通過(guò)一種約束,通過(guò)新鮮度和校驗(yàn)來(lái)給客戶端提供緩存資源。
Web緩存的好處
- 緩存可以減少手機(jī)流量的消耗
- 可以更快的響應(yīng)用戶的操作,提升用戶體驗(yàn)度
- 降低資源服務(wù)器的壓力,減少并發(fā)量
Web緩存的分類(lèi)
-
手機(jī)文件緩存
由于移動(dòng)端不像前端,前端開(kāi)發(fā)中瀏覽器都默認(rèn)實(shí)現(xiàn)了Web緩存,所以我們可以通過(guò)請(qǐng)求頭的控制來(lái)讓瀏覽器自動(dòng)為我們進(jìn)行緩存。但是移動(dòng)端沒(méi)有這種默認(rèn)實(shí)現(xiàn)Web緩存的容器,所以需要我們自己手動(dòng)通過(guò)文件緩存網(wǎng)絡(luò)請(qǐng)求過(guò)來(lái)的報(bào)文。Okhttp或者結(jié)合Retrofit(一樣)都是可以通過(guò)OkhttpClient添加一個(gè)構(gòu)造好的Cache實(shí)例,其中Cache實(shí)例我們需要指定文件路徑和大小。具體可以看我這個(gè)代碼Demo。
-
CDN緩存(內(nèi)容分發(fā)網(wǎng)關(guān)緩存)
實(shí)際上是網(wǎng)關(guān)緩存的一種,而網(wǎng)關(guān)緩存又叫反向代理緩存(提前接受客戶端發(fā)來(lái)的原始請(qǐng)求,然后進(jìn)行按需請(qǐng)求分配)。CDN緩存一般是由網(wǎng)站管理員自己部署,為了讓他們的網(wǎng)站更容易擴(kuò)展并獲得更好的性能。通常情況下,瀏覽器先向CDN網(wǎng)關(guān)發(fā)起Web請(qǐng)求,網(wǎng)關(guān)服務(wù)器后面對(duì)應(yīng)著一臺(tái)或多臺(tái)負(fù)載均衡源服務(wù)器,會(huì)根據(jù)它們的負(fù)載請(qǐng)求,動(dòng)態(tài)將請(qǐng)求轉(zhuǎn)發(fā)到合適的源服務(wù)器上。從瀏覽器角度來(lái)看,整個(gè)CDN就是一個(gè)源服務(wù)器,從這個(gè)層面來(lái)說(shuō),瀏覽器和服務(wù)器之間的緩存機(jī)制,在這種架構(gòu)下同樣適用。
-
代理服務(wù)器緩存
代理服務(wù)器是一種處在客戶端和服務(wù)端中間的服務(wù)器,處在兩者之間的網(wǎng)絡(luò)之中。代理服務(wù)器以共享緩存的方式保存著報(bào)文副本,可以減少客戶端到原始服務(wù)器的長(zhǎng)距離請(qǐng)求。自然訪問(wèn)同一種報(bào)文副本的用戶越多,代理服務(wù)器的利用率越高,緩存的命中率也越高。
-
數(shù)據(jù)庫(kù)緩存
這是后臺(tái)開(kāi)發(fā)的時(shí)候,緩存從數(shù)據(jù)庫(kù)中查找的數(shù)據(jù),減少數(shù)據(jù)庫(kù)的并發(fā)(比如NoSQL)。
共有緩存和私有緩存
- 共有緩存(Cache-Control: public):這種緩存常見(jiàn)的就是代理緩存,意思就是一個(gè)用戶群體都可以訪問(wèn)這個(gè)緩存服務(wù)器,這個(gè)緩存服務(wù)器緩存了所有群體的報(bào)文緩存
- 私有緩存(Cache-Control: private):這種緩存常見(jiàn)的就是文件緩存(本地緩存),只允許一個(gè)用戶個(gè)體去訪問(wèn)。
Web緩存的層次結(jié)構(gòu)
一般來(lái)說(shuō),安卓中的圖片多級(jí)緩存和這個(gè)概念不大相同,這里的多級(jí)緩存的意思是在客戶端和服務(wù)端之間部署多級(jí)緩存。常見(jiàn)部署如下:
- 手機(jī)文件緩存(瀏覽器緩存): 一級(jí)緩存
- 小型的局域網(wǎng)內(nèi)緩存:二級(jí)緩存,小型的,代價(jià)小,容量小的緩存代理
- 大型的廣域網(wǎng)內(nèi)緩存:三級(jí)緩存 ,大型的,代價(jià)高,容量大的緩存代理
Web緩存的流程
先上張圖:

- 在客戶端發(fā)起一次請(qǐng)求的時(shí)候,首先一層一層的緩存會(huì)根據(jù)算法判斷緩存中是否存在文檔副本,如果不存在的話就會(huì)像資源服務(wù)器或者父代理緩存服務(wù)器(上一級(jí)緩存服務(wù)器)繼續(xù)請(qǐng)求客戶端需要的資源。
- 如果資源副本存在的話就會(huì)根據(jù)資源的元數(shù)據(jù)(記錄資源在緩存中保存了多長(zhǎng)時(shí)間以及它被用了多少次)來(lái)計(jì)算資源的新鮮度,如果資源新鮮的話就直接返回給客戶端。
- 如果不新鮮的話,會(huì)判斷資源在緩存中的副本和在資源服務(wù)器里面的文本副本是否一致,如果一致表示資源未發(fā)生改變,那么仍然還是由緩存返回。
- 如果資源發(fā)生改變,那么由資源服務(wù)器來(lái)返回資源給客戶端,并且用這份資源存入緩存中。
大致流程就是這樣,請(qǐng)求-檢驗(yàn)-在驗(yàn)證,下面對(duì)于過(guò)程的細(xì)節(jié)詳細(xì)說(shuō)說(shuō):
文檔過(guò)期的判斷
緩存數(shù)據(jù)是否過(guò)期是由數(shù)據(jù)的使用期和過(guò)期日期共同決定的。
-
過(guò)期時(shí)期(新鮮生存期)
過(guò)期時(shí)期可以通過(guò)Cache-Control的max-age和max-stale或者Expires來(lái)設(shè)置。其中,Cache-Control中指定的是相對(duì)時(shí)間,而Expries指定的是數(shù)據(jù)過(guò)期的絕對(duì)時(shí)間。但是資源服務(wù)器的時(shí)間經(jīng)常會(huì)出現(xiàn)與客戶端不同步的情況(我就遇到過(guò)這種坑。。)所以用相對(duì)時(shí)間更加合理,并且Cache-Control的優(yōu)先級(jí)要高于Expries。
| Cache-Control: max-stale = <s> | 在指定的秒數(shù)里面緩存可以提供過(guò)期了的數(shù)據(jù) |
|---|---|
| Cache-Control: max-age = <s> | 在指定的秒數(shù)里面,緩存里面的數(shù)據(jù)不會(huì)過(guò)期 |
| Cache-Control: min-fresh = <s> | 至少在未來(lái)數(shù)秒內(nèi)數(shù)據(jù)要保持新鮮 |
| Cache-Control: private or public | 私有緩存或者共有緩存 |
| Cache-Control: no-cache | 提供數(shù)據(jù)之前必去要去和資源服務(wù)器的數(shù)據(jù)進(jìn)行比對(duì)判斷數(shù)據(jù)是否更新 |
| Cache-Control: no-store | 禁止一切緩存 |
| Cache-Control: must-revalidate | 告訴緩存,必須嚴(yán)格遵循服務(wù)器的規(guī)定,驗(yàn)證之后才能提供過(guò)期的數(shù)據(jù) |
| Cache-Contero: only-if-cached | 只有當(dāng)緩存中有副本的時(shí)候,客戶端才能獲取一份數(shù)據(jù)副本 |
需要注意的是
- no-cache 不是不使用緩存而是必須比對(duì)了之后才能提供,no-store才是完全禁止緩存
- 優(yōu)先級(jí):no-staore>no-cache>must-revalidate>max-age>Exprise
在移動(dòng)端的開(kāi)發(fā)中,如何控制我們的數(shù)據(jù)緩存過(guò)期時(shí)間需要移動(dòng)端和后臺(tái)共同商量之后得出一套方案,比如像數(shù)據(jù)不停變換更新的并且數(shù)據(jù)量不大的,應(yīng)該禁用緩存以保證數(shù)據(jù)的及時(shí)性,像周刊,課表這種長(zhǎng)期不變的數(shù)據(jù)應(yīng)該添加上合理的緩存時(shí)間。
-
使用時(shí)間
所謂使用時(shí)間就是數(shù)據(jù)從資源服務(wù)器發(fā)出之后,到被客戶端收到,一共經(jīng)歷了多久。但是使用時(shí)期并不容易計(jì)算。因?yàn)榉?wù)器,代理,客戶端之間時(shí)間不同步,網(wǎng)絡(luò)延時(shí),網(wǎng)絡(luò)傳輸耗時(shí)等等都會(huì)造成使用時(shí)期難以計(jì)算。
1.直接通過(guò)Date首部進(jìn)行計(jì)算
? 這種計(jì)算方式我就被坑過(guò),之前直接設(shè)置max-age=3600(一分鐘)發(fā)現(xiàn)一點(diǎn)緩存效果都沒(méi)有。我發(fā)現(xiàn)響應(yīng)頭的Date首部與現(xiàn)在時(shí)間不同,慢了好幾天。首先要理解Date首部,Date首部是服務(wù)器發(fā)出相應(yīng)的時(shí)間,并且是服務(wù)器的絕對(duì)時(shí)間,并且Date首部只能是資源服務(wù)器設(shè)置,代理和緩存都不能修改,時(shí)間的描述格式由RFC822定義,所以如果時(shí)間不同步的話,直接用客戶端時(shí)間減去Date來(lái)計(jì)算使用期是行不通的。
2.通過(guò)Age首部進(jìn)行計(jì)算
Age首部只適用于HTTP/1.1的設(shè)備,Age表示數(shù)據(jù)從產(chǎn)生到現(xiàn)在經(jīng)過(guò)了多長(zhǎng)時(shí)間,是個(gè)相對(duì)時(shí)間
通過(guò)Age首部,我們可以累加資源在各個(gè)代理中存在的時(shí)間,加上數(shù)據(jù)從資源服務(wù)器到緩存的網(wǎng)絡(luò)傳輸時(shí)間(一般用緩存到服務(wù)器,服務(wù)器到緩存雙向的傳輸時(shí)間來(lái)保守估計(jì))。
所以我們可以看出,完整的使用時(shí)期計(jì)算應(yīng)該是如下圖:

可以看出其實(shí)是忽略了客戶端到緩存的網(wǎng)絡(luò)傳輸時(shí)間,多算了緩存到資源服務(wù)器的網(wǎng)絡(luò)傳輸時(shí)間。
再驗(yàn)證
如果第一次緩存未命中的話,就會(huì)進(jìn)行服務(wù)器再驗(yàn)證。用來(lái)判斷服務(wù)器的資源是否發(fā)生改變。
If-Modified-Since與Last-Modified(基于Date的再驗(yàn)證)
If-Modified-Since是請(qǐng)求字段,值為一個(gè)絕對(duì)時(shí)間,用于通知服務(wù)器在這個(gè)時(shí)間之前,默認(rèn)資源是未被修改的(不去進(jìn)行再驗(yàn)證),如果超過(guò)了這個(gè)時(shí)間,就會(huì)將這個(gè)時(shí)間和資源最后修改的時(shí)間進(jìn)行對(duì)比,如果小于資源最后修改的時(shí)間,那么Last-Modified響應(yīng)首部會(huì)返回資源最后被修改的時(shí)間,并且返回200.如果大于最后修改的時(shí)間,那么Last-Modified響應(yīng)首部會(huì)返回304表示資源未被修改。
If-None-Match和ETag(基于實(shí)體標(biāo)簽的再驗(yàn)證)
ETag可以看作是資源的版本號(hào),并且在強(qiáng)驗(yàn)證器下,資源發(fā)生細(xì)微的變化都會(huì)導(dǎo)致ETag的變化,在弱驗(yàn)證器下,資源發(fā)生細(xì)微的變化都不會(huì)導(dǎo)致ETag的變化,這個(gè)“細(xì)微”度量標(biāo)準(zhǔn)是由后臺(tái)開(kāi)發(fā)人員來(lái)確定的??蛻舳丝梢愿鶕?jù)If-Modified-Since來(lái)添加你現(xiàn)在所擁有的ETag版本號(hào),發(fā)送給服務(wù)器,然后服務(wù)器進(jìn)行比較。如果版本號(hào)不同,那么就會(huì)返回200,并且將最新的ETag值添加到ETag響應(yīng)頭里面。如果相同,返回304。
兩者之間并沒(méi)有優(yōu)先級(jí)之分,如果同時(shí)存在,那么客戶端和服務(wù)端應(yīng)該將兩者綜合起來(lái)考量。
未經(jīng)博主同意,不得轉(zhuǎn)載該篇文章