實踐這一次,徹底搞懂瀏覽器快取機制

webfansplz發表於2019-01-21

前言

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

[實踐系列]前端路由

[實踐系列]Babel原理

[實踐系列]Promises/A+規範

有興趣的同學可以關注 [實踐系列] 。 求star求follow~

如果覺得自己已經掌握瀏覽器快取機制知識的同學,可以直接看實踐部分哈~

目錄

 1. DNS 快取   // 雖說跟標題關係不大,瞭解一下也不錯
 2. CDN 快取   // 雖說跟標題關係不大,瞭解一下也不錯
 3. 瀏覽器快取 // 本文將重點介紹並實踐  
複製程式碼

DNS 快取

什麼是DNS

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

全球資訊網上作為域名和IP地址相互對映的一個分散式資料庫,能夠使使用者更方便的訪問網際網路,而不用去記住能夠被機器直接讀取的IP數串。DNS協議執行在UDP協議之上,使用埠號53。

DNS解析

簡單的說,通過域名,最終得到該域名對應的IP地址的過程叫做域名解析(或主機名解析)。

www.dnscache.com (域名)  - DNS解析 -> 11.222.33.444 (IP地址)
複製程式碼

DNS快取

有dns的地方,就有快取。瀏覽器、作業系統、Local DNS、根域名伺服器,它們都會對DNS結果做一定程度的快取。

DNS查詢過程如下:

  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-etag -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 支援下作者~

原始碼地址

參考文獻

前端效能優化之快取利用

相關文章