實踐這一次,完全搞懂瀏覽器緩存機制

前言

[實踐系列] 主要是讓咱們經過實踐去加深對一些原理的理解。css

實踐系列-前端路由 html

實踐系列-Babel原理 前端

實踐系列-Promises/A+規範 node

有興趣的同窗能夠關注 實踐系列 。 求star求follow~ git

若是以爲本身已經掌握瀏覽器緩存機制知識的同窗,能夠直接看實踐部分哈~github

目錄

1. DNS 緩存   // 雖然說跟標題關係不大,瞭解一下也不錯
 2. CDN 緩存   // 雖然說跟標題關係不大,瞭解一下也不錯
 3. 瀏覽器緩存 // 本文將重點介紹並實踐

DNS 緩存

什麼是DNS

全稱 Domain Name System ,即域名系統。web

萬維網上做爲域名和IP地址相互映射的一個分佈式數據庫,可以使用戶更方便的訪問互聯網,而不用去記住可以被機器直接讀取的IP數串。DNS協議運行在UDP協議之上,使用端口號53。

DNS解析

簡單的說,經過域名,最終獲得該域名對應的IP地址的過程叫作域名解析(或主機名解析)。數據庫

www.dnscache.com (域名)  - DNS解析 -> 11.222.33.444 (IP地址)

DNS緩存

有dns的地方,就有緩存。瀏覽器、操做系統、Local DNS、根域名服務器,它們都會對DNS結果作必定程度的緩存。npm

DNS查詢過程以下:json

  1. 首先搜索瀏覽器自身的DNS緩存,若是存在,則域名解析到此完成。
  2. 若是瀏覽器自身的緩存裏面沒有找到對應的條目,那麼會嘗試讀取操做系統的hosts文件看是否存在對應的映射關係,若是存在,則域名解析到此完成。
  3. 若是本地hosts文件不存在映射關係,則查找本地DNS服務器(ISP服務器,或者本身手動設置的DNS服務器),若是存在,域名到此解析完成。
  4. 若是本地DNS服務器還沒找到的話,它就會向根服務器發出請求,進行遞歸查詢。

戳此處詳細瞭解DNS解析過程

CDN 緩存

什麼是CDN

全稱 Content Delivery Network,即內容分發網絡。

摘錄一個形象的比喻,來理解CDN是什麼。

10年前,尚未火車票代售點一說,12306.cn更是無從提及。那時候火車票還只能在火車站的售票大廳購買,而我所在的小縣城並不通火車,火車票都要去市裏的火車站購買,而從我家到縣城再到市裏,來回就是4個小時車程,簡直就是浪費生命。後來就行了,小縣城裏出現了火車票代售點,甚至鄉鎮上也有了代售點,能夠直接在代售點購買火車票,方便了很多,全市人民不再用在一個點苦逼的排隊買票了。

簡單的理解CDN就是這些代售點(緩存服務器)的承包商,他爲買票者提供了便利,幫助他們在最近的地方(最近的CDN節點)用最短的時間(最短的請求時間)買到票(拿到資源),這樣去火車站售票大廳排隊的人也就少了。也就減輕了售票大廳的壓力(起到分流做用,減輕服務器負載壓力)。

用戶在瀏覽網站的時候,CDN會選擇一個離用戶最近的CDN邊緣節點來響應用戶的請求,這樣海南移動用戶的請求就不會千里迢迢跑到北京電信機房的服務器(假設源站部署在北京電信機房)上了。

CDN緩存

關於CDN緩存,在瀏覽器本地緩存失效後,瀏覽器會向CDN邊緣節點發起請求。相似瀏覽器緩存,CDN邊緣節點也存在着一套緩存機制。CDN邊緣節點緩存策略因服務商不一樣而不一樣,但通常都會遵循http標準協議,經過http響應頭中的

Cache-control: max-age   //後面會提到

的字段來設置CDN邊緣節點數據緩存時間。

當瀏覽器向CDN節點請求數據時,CDN節點會判斷緩存數據是否過時,若緩存數據並無過時,則直接將緩存數據返回給客戶端;不然,CDN節點就會向服務器發出回源請求,從服務器拉取最新數據,更新本地緩存,並將最新數據返回給客戶端。 CDN服務商通常會提供基於文件後綴、目錄多個維度來指定CDN緩存時間,爲用戶提供更精細化的緩存管理。

CDN 優點

  1. CDN節點解決了跨運營商和跨地域訪問的問題,訪問延時大大下降。
  2. 大部分請求在CDN邊緣節點完成,CDN起到了分流做用,減輕了源服務器的負載。

    戳此處詳細瞭解CDN工做過程

瀏覽器緩存(http緩存)

對着這張圖先發呆30秒~
image

什麼是瀏覽器緩存

image

簡單來講,瀏覽器緩存其實就是瀏覽器保存經過HTTP獲取的全部資源,是瀏覽器將網絡資源存儲在本地的一種行爲。

緩存的資源去哪裏了?

你可能會有疑問,瀏覽器存儲了資源,那它把資源存儲在哪裏呢?

memory cache

MemoryCache顧名思義,就是將資源緩存到內存中,等待下次訪問時不須要從新下載資源,而直接從內存中獲取。Webkit早已支持memoryCache。
目前Webkit資源分紅兩類,一類是主資源,好比HTML頁面,或者下載項,一類是派生資源,好比HTML頁面中內嵌的圖片或者腳本連接,分別對應代碼中兩個類:MainResourceLoader和SubresourceLoader。雖然Webkit支持memoryCache,可是也只是針對派生資源,它對應的類爲CachedResource,用於保存原始數據(好比CSS,JS等),以及解碼過的圖片數據。

disk cache

DiskCache顧名思義,就是將資源緩存到磁盤中,等待下次訪問時不須要從新下載資源,而直接從磁盤中獲取,它的直接操做對象爲CurlCacheManager。
  • |memory cache | disk cache
相同點 只能存儲一些派生類資源文件 只能存儲一些派生類資源文件
不一樣點 退出進程時數據會被清除 退出進程時數據不會被清除
存儲資源 通常腳本、字體、圖片會存在內存當中 通常非腳本會存在內存當中,如css等

由於CSS文件加載一次就可渲染出來,咱們不會頻繁讀取它,因此它不適合緩存到內存中,可是js之類的腳本卻隨時可能會執行,若是腳本在磁盤當中,咱們在執行腳本的時候須要從磁盤取到內存中來,這樣IO開銷就很大了,有可能致使瀏覽器失去響應。

三級緩存原理 (訪問緩存優先級)

  1. 先在內存中查找,若是有,直接加載。
  2. 若是內存中不存在,則在硬盤中查找,若是有直接加載。
  3. 若是硬盤中也沒有,那麼就進行網絡請求。
  4. 請求獲取的資源緩存到硬盤和內存。

瀏覽器緩存的分類

  1. 強緩存
  2. 協商緩存

瀏覽器再向服務器請求資源時,首先判斷是否命中強緩存,再判斷是否命中協商緩存!

瀏覽器緩存的優勢

1.減小了冗餘的數據傳輸

2.減小了服務器的負擔,大大提高了網站的性能

3.加快了客戶端加載網頁的速度

強緩存

瀏覽器在加載資源時,會先根據本地緩存資源的 header 中的信息判斷是否命中強緩存,若是命中則直接使用緩存中的資源不會再向服務器發送請求。

這裏的 header 中的信息指的是 expires 和 cahe-control.

Expires

該字段是 http1.0 時的規範,它的值爲一個絕對時間的 GMT 格式的時間字符串,好比 Expires:Mon,18 Oct 2066 23:59:59 GMT。這個時間表明着這個資源的失效時間,在此時間以前,即命中緩存。這種方式有一個明顯的缺點,因爲失效時間是一個絕對時間,因此當服務器與客戶端時間誤差較大時,就會致使緩存混亂。

Cache-Control

Cache-Control 是 http1.1 時出現的 header 信息,主要是利用該字段的 max-age 值來進行判斷,它是一個相對時間,例如 Cache-Control:max-age=3600,表明着資源的有效期是 3600 秒。cache-control 除了該字段外,還有下面幾個比較經常使用的設置值:

no-cache:須要進行協商緩存,發送請求到服務器確認是否使用緩存。

no-store:禁止使用緩存,每一次都要從新請求數據。

public:能夠被全部的用戶緩存,包括終端用戶和 CDN 等中間代理服務器。

private:只能被終端用戶的瀏覽器緩存,不容許 CDN 等中繼緩存服務器對其緩存。

Cache-Control 與 Expires 能夠在服務端配置同時啓用,同時啓用的時候 Cache-Control 優先級高。

協商緩存

當強緩存沒有命中的時候,瀏覽器會發送一個請求到服務器,服務器根據 header 中的部分信息來判斷是否命中緩存。若是命中,則返回 304 ,告訴瀏覽器資源未更新,可以使用本地的緩存。

這裏的 header 中的信息指的是 Last-Modify/If-Modify-Since 和 ETag/If-None-Match.

Last-Modify/If-Modify-Since

瀏覽器第一次請求一個資源的時候,服務器返回的 header 中會加上 Last-Modify,Last-modify 是一個時間標識該資源的最後修改時間。

當瀏覽器再次請求該資源時,request 的請求頭中會包含 If-Modify-Since,該值爲緩存以前返回的 Last-Modify。服務器收到 If-Modify-Since 後,根據資源的最後修改時間判斷是否命中緩存。

若是命中緩存,則返回 304,而且不會返回資源內容,而且不會返回 Last-Modify。

缺點:

短期內資源發生了改變,Last-Modified 並不會發生變化。

週期性變化。若是這個資源在一個週期內修改回原來的樣子了,咱們認爲是可使用緩存的,可是 Last-Modified 可不這樣認爲,所以便有了 ETag。

ETag/If-None-Match

與 Last-Modify/If-Modify-Since 不一樣的是,Etag/If-None-Match 返回的是一個校驗碼。ETag 能夠保證每個資源是惟一的,資源變化都會致使 ETag 變化。服務器根據瀏覽器上送的 If-None-Match 值來判斷是否命中緩存。

與 Last-Modified 不同的是,當服務器返回 304 Not Modified 的響應時,因爲 ETag 從新生成過,response header 中還會把這個 ETag 返回,即便這個 ETag 跟以前的沒有變化。

Last-Modified 與 ETag 是能夠一塊兒使用的,服務器會優先驗證 ETag,一致的狀況下,纔會繼續比對 Last-Modified,最後才決定是否返回 304。

總結

當瀏覽器再次訪問一個已經訪問過的資源時,它會這樣作:

1.看看是否命中強緩存,若是命中,就直接使用緩存了。

2.若是沒有命中強緩存,就發請求到服務器檢查是否命中協商緩存。

3.若是命中協商緩存,服務器會返回 304 告訴瀏覽器使用本地緩存。

4.不然,返回最新的資源。

實踐加深理解

talk is cheap , show me the code 。讓咱們經過實踐得真知~

在實踐時,注意瀏覽器控制檯Network的image按鈕不要打鉤。

如下咱們只對強緩存的Cache-Control和協商緩存的ETag進行實踐,其餘小夥伴們能夠本身實踐~

package.json

{
 "name": "webcache",
 "version": "1.0.0",
 "description": "",
 "main": "index.js",
 "scripts": {
   "cache": "nodemon ./index.js"
 },
 "author": "webfansplz",
 "license": "MIT",
 "devDependencies": {
   "@babel/core": "^7.2.2",
   "@babel/preset-env": "^7.2.3",
   "@babel/register": "^7.0.0",
   "koa": "^2.6.2",
   "koa-static": "^5.0.0"
 },
 "dependencies": {
   "nodemon": "^1.18.9"
 }
}

.babelrc

{
 "presets": [
   [
     "@babel/preset-env",
     {
       "targets": {
         "node": "current"
       }
     }
   ]
 ]
}

index.js

require('@babel/register');
require('./webcache.js');

webcache.js

import Koa from 'koa';
import path from 'path';
//靜態資源中間件
import resource from 'koa-static';
const app = new Koa();
const host = 'localhost';
const port = 4396;
app.use(resource(path.join(__dirname, './static')));

app.listen(port, () => {
  console.log(`server is listen in ${host}:${port}`);
});

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>前端緩存</title>
    <style>
      .web-cache img {
        display: block;
        width: 100%;
      }
    </style>
  </head>
  <body>
    <div class="web-cache"><img src="./web.png" /></div>
  </body>
</html>

咱們用koa先起個web服務器,而後用koa-static這個中間件作靜態資源配置,並在static文件夾下放了index.html和web.png。

Ok,接下來咱們來啓動服務。

npm run cache

server is listen in localhost:4396。

接下來咱們打開瀏覽器輸入地址:

localhost:4396

image

完美~(哈哈,豬仔別噴我,純屬娛樂效果)

Ok!!!接下來咱們來實踐下強緩存。~

Cache-Control

webcache.js

import Koa from 'koa';
import path from 'path';
//靜態資源中間件
import resource from 'koa-static';
const app = new Koa();
const host = 'localhost';
const port = 4396;

app.use(async (ctx, next) => {
 // 設置響應頭Cache-Control 設置資源有效期爲300秒
  ctx.set({
    'Cache-Control': 'max-age=300'  
  });
  await next();
});
app.use(resource(path.join(__dirname, './static')));

app.listen(port, () => {
  console.log(`server is listen in ${host}:${port}`);
});

image
咱們刷新頁面能夠看到響應頭的Cache-Control變成了max-age=300。

咱們順便來驗證下三級緩存原理

咱們剛進行了網絡請求,瀏覽器把web.png存進了磁盤和內存中。

根據三級緩存原理,咱們會先在內存中找資源,咱們來刷新頁面。

image

咱們在紅線部分看到了, from memory cache。nice~

ok,接下來,咱們關掉該頁面,再從新打開。由於內存是存在進程中的,因此關閉該頁面,內存中的資源也被釋放掉了,磁盤中的資源是永久性的,因此還存在。

根據三級緩存原理,若是在內存中沒找到資源,便會去磁盤中尋找!

image

from disk cache !!! ok,以上也就驗證了三級緩存原理,相信你對緩存資源的存儲也有了更深的理解了。

咱們剛對資源設置的有效期是300秒,咱們接下來來驗證緩存是否失效。

300秒後。。。

image

咱們經過返回值能夠看到,緩存失效了。

經過以上實踐,你是否對強緩存有了更深刻的理解了呢?

Ok!!!接下來咱們來實踐下協商緩存。~

因爲Cache-Control的默認值就是no-cache(須要進行協商緩存,發送請求到服務器確認是否使用緩存。),因此咱們這裏不用對Cache-Control進行設置!

ETag

//ETag support for Koa responses using etag.
npm install koa-tag -D
// etag works together with conditional-get
npm install koa-conditional-get -D

咱們這裏直接使用現成的插件幫咱們計算文件的ETag值,站在巨人的肩膀上!

webcache.js

import Koa from 'koa';
import path from 'path';
//靜態資源中間件
import resource from 'koa-static';
import conditional from 'koa-conditional-get';
import etag from 'koa-etag';
const app = new Koa();
const host = 'localhost';
const port = 4396;

// etag works together with conditional-get
app.use(conditional());
app.use(etag());
app.use(resource(path.join(__dirname, './static')));

app.listen(port, () => {
 console.log(`server is listen in ${host}:${port}`);
});

ok。第一次請求.
image
咱們發現返回值裏面已經有了Etag值。

接下來再請求的時候,瀏覽器將會帶上If-None-Match請求頭,並賦值爲上一次返回頭的Etag值,而後與 此次返回值的Etag值進行對比。若是一致則命中協商緩存。返回304 Not Modified。接下來咱們來驗證一下~

image
ok,如圖所示,完美驗證了上面的說法。

接下來咱們修改web.png ,來驗證是否資源改變時 協商緩存策略也就失效呢?

image

如圖所示.協商緩存的實踐也驗證了原理。

大功告成

寫文章真的是件挺累的事,若是以爲有幫助到你,請給star/follow 支持下做者~

源碼地址

參考文獻

前端性能優化之緩存利用

相關文章
相關標籤/搜索