快取策略

yanyongchao發表於2019-02-18

學習整理了web快取的一些策略,如有不正確的地方,歡迎指正。

快取策略

瀏覽器端的快取規則

快取行為主要由快取策略決定,而快取策略由內容擁有者設定。這些策略主要通過特定的HTTP頭部來清晰地表達。

當一個使用者發起一個靜態資源請求的時候,瀏覽器會通過以下幾步來獲取資源:

  1. 本地快取階段:先在本地查詢該資源,如果有發現該資源,而且該資源還沒有過期,就使用這一個資源,完全不會傳送http請求到伺服器;
  2. 協商快取階段:如果在本地快取找到對應的資源,但是不知道該資源是否過期或者已經過期,則發一個http請求到伺服器,然後伺服器判斷這個請求,如果請求的資源在伺服器上沒有改動過,則返回304,讓瀏覽器使用本地找到的那個資源;
  3. 快取失敗階段:當伺服器發現請求的資源已經修改過,或者這是一個新的請求(在本來沒有找到資源),伺服器則返回該資源的資料,並且返回200, 當然這個是指找到資源的情況下,如果伺服器上沒有這個資源,則返回404。

本地快取

可以通過設定請求頭來進行配置

Expires

 res.setHeader('Expires', new Date(Date.now() + 30 * 1000).toUTCString())
複製程式碼

Cache Control

 res.setHeader('Cache-Control', 'max-age=30')
複製程式碼

注意:Cache-Control:這個是http 1.1中為了彌補 Expires 缺陷新加入的。如果設了max-age,max-age就會覆蓋expires

完整程式碼

http.createServer(function(req, res) {
  let { pathname } = url.parse(req.url, true)
  let filepath = path.join(__dirname, pathname)
  fs.stat(filepath, (err, stat) => {
    if (err) {
      sendError(req, res)
    } else {
      send(req, res, filepath)
    }
  })
}).listen(8080)

function sendError(req, res) {
  res.statusCode = 404
  res.end('Not Found')
}

function send(req, res, filepath) {
  res.setHeader('Content-Type', mime.getType(filepath))
  res.setHeader('Expires', new Date(Date.now() + 30 * 1000).toUTCString())
  res.setHeader('Cache-Control', 'max-age=30')
  fs.createReadStream(filepath).pipe(res)
}
複製程式碼

協商快取

Last-Modified & if-modified-since

  1. Last-Modified與If-Modified-Since是一對報文頭,屬於http 1.0。
  2. last-modified是WEB伺服器認為物件的最後修改時間,比如檔案的最後修改時間,動態頁面的最後產生時間。
http.createServer(function(req, res) {
  let { pathname } = url.parse(req.url, true)
  let filepath = path.join(__dirname, pathname)
  fs.stat(filepath, (err, stat) => {
    if (err) {
      res.statusCode = 404
      res.end('Not Found')
    } else {
      let ifModifiedSince = req.headers['if-modified-since']
      let LastModified = stat.ctime.toGMTString()
      if (ifModifiedSince === LastModified) {
        res.statusCode = 304
        res.end()
      } else {
        send(req, res, filepath, stat)
      }
    }
  })
}).listen(8080)

function send(req, res, filepath, stat) {
  res.setHeader('Content-Type', mime.getType())
  res.setHeader('Last-Modified', stat.ctime.toGMTString())
  fs.createReadStream(filepath).pipe(res)
}
複製程式碼

ETag & If-None-Match

  1. ETag與If-None-Match是一對報文,屬於http 1.1。
  2. ETag可以用來解決這種問題。ETag是一個檔案的唯一標誌符。就像一個雜湊或者指紋,每個檔案都有一個單獨的標誌,只要這個檔案發生了改變,這個標誌就會發生變化。
  3. ETag機制類似於樂觀鎖機制,如果請求報文的ETag與伺服器的不一致,則表示該資源已經被修改過來,需要發最新的內容給瀏覽器。
  4. 同時使用這兩個報文頭,在完全匹配If-Modified-Since和If-None-Match即檢查完修改時間和Etag之後,如都與伺服器的相符,伺服器返回304,否則,傳送最新內容給瀏覽器。
http.createServer(function(req, res) {
  let { pathname } = url.parse(req.url, true)
  let filepath = path.join(__dirname, pathname)
  fs.stat(filepath, (err, stat) => {
    if (err) {
      sendError(req, res)
    } else {
      let ifNoneMatch = req.headers['if-none-match']
      let out = fs.createReadStream(filepath)
      let md5 = crypto.createHash('md5')
      out.on('data', function(data) {
        md5.update(data)
      })
      out.on('end', function() {
        let etag = md5.digest('hex')
        if (ifNoneMatch === etag) {
          res.statusCode = 304
          res.end()
        } else {
          send(req, res, filepath, etag)
        }
      })
    }
  })
}).listen(8080)

function sendError(req, res) {
  res.statusCode('404')
  res.end('Not Found')
}

function send(req, res, filepath, etag) {
  res.setHeader('Content-Type', mime.getType())
  res.setHeader('ETag', etag)
  fs.createReadStream(filepath).pipe(res)
}
複製程式碼

Etag/lastModified過程如下:

  1. 客戶端請求一個頁面(A)。
  2. 伺服器返回頁面A,並在給A加上一個Last-Modified/ETag。
  3. 客戶端展現該頁面,並將頁面連同Last-Modified/ETag一起快取。
  4. 客戶再次請求頁面A,並將上次請求時伺服器返回的Last-Modified/ETag一起傳遞給伺服器。
  5. 伺服器檢查該Last-Modified或ETag,並判斷出該頁面自上次客戶端請求之後還未被修改,直接返回響應304和一個空的響應體。

伺服器端快取

CND快取

CDN快取,也叫閘道器快取、反向代理快取。瀏覽器先向CDN閘道器發起WEB請求,閘道器伺服器後面對應著一臺或多臺負載均衡源伺服器,會根據它們的負載請求,動態地請求轉發到合適的源伺服器上。

CDN快取策略

  1. CDN邊緣節點快取策略因服務商不同而不同,但一般都會遵循http標準協議,通過http響應頭中的Cache-control: max-age的欄位來設定CDN邊緣節點資料快取時間。
  2. 當客戶端向CDN節點請求資料時,CDN節點會判斷快取資料是否過期,若快取資料並沒有過期,則直接將快取資料返回給客戶端;否則,CDN節點就會向源站發出回源請求(back to the source request),從源站拉取最新資料,更新本地快取,並將最新資料返回給客戶端。
  3. CDN服務商一般會提供基於檔案字尾、目錄多個維度來指定CDN快取時間,為使用者提供更精細化的快取管理。
  4. CDN快取時間會對“回源率”產生直接的影響。若CDN快取時間較短,CDN邊緣節點上的資料會經常失效,導致頻繁回源,增加了源站的負載,同時也增大的訪問延時;若CDN快取時間太長,會帶來資料更新時間慢的問題。開發者需要增對特定的業務,來做特定的資料快取時間管理。
  5. CDN快取重新整理CDN邊緣節點對開發者是透明的,相比於瀏覽器Ctrl+F5的強制重新整理來使瀏覽器本地快取失效,開發者可以通過CDN服務商提供的“重新整理快取”介面來達到清理CDN邊緣節點快取的目的。這樣開發者在更新資料後,可以使用“重新整理快取”功能來強制CDN節點上的資料快取過期,保證客戶端在訪問時,拉取到最新的資料。

CDN優勢

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

HTML5快取思路

  1. 使用者可離線訪問你的應用,這對於無法隨時保持聯網狀態的移動終端使用者來說尤其重要
  2. 使用者訪問本地的快取檔案,通常意味著更快的訪問速度
  3. 僅僅載入被修改過的資源,避免同一資源對伺服器多次的請求,大大降低了對伺服器的訪問壓力

相關文章