HTTP

HTTP 快取 (Cache)

最好的請求,是不必與伺服器進行通訊的請求。
— Google web performance engineer, Ilya Grigorik

 
HTTP 快取 (HTTP Cache),或稱 HTTP 緩存,
回應訊息本地儲存 以及 控制、檢索、刪除 該訊息的機制。
用於減少將來 等效請求 的回應時間 與 網路頻寬的消耗,以提升效能。
 
[註]:
快取 (Cache) 原意為 儲存東西的安全地方,
後也時常引申為東西本身,此處即 儲存的回應訊息。
 


 

新鮮度 (Freshness)

就像家中的儲備糧食:
若食物「新鮮」,可以直接拿來吃;
若食物「過期」,則必須出去購買。
 

Photo by fe-amart.
 
User Agent 在發送 HTTP 請求,獲得 回應訊息 後,
能將之儲存,成為「儲備糧食」 — — HTTP Cache
(不同的是,「過期」 並不代表訊息不可用)
 
其具有一定的 新鮮度 (Freshness),在下次發送等效請求前,
若快取「新鮮 (fresh)」,可以直接使用 而 不需發送網路請求
否則「陳舊 (stale)」,需發送網路請求驗證 (validate) 訊息可否複用,若否,則重新獲取回應。
(注意,各自存在例外情形)
 
本章的若干篇文,便立基於此概念,
探討如何判斷新鮮與否、例外情形、驗證機制…。
 


 

200 vs. 304

(4-6) 回應狀態碼 (Response Status Codes) 中,提及:
 
200 (OK)

代表請求已經成功。

 
304 (Not Modified) — 未修改

代表 已接收的 GET 或 HEAD 條件請求 (conditional request),
將使 Server 回應 200 (OK)。

 
兩者皆時常用於 快取 (Cache) 機制,
並 (通常) 分別對應上述的 新鮮回應 (Fresh Response)陳舊回應 (Stale Response)
 
 

新鮮回應 (Fresh Response)

當 快取 為 新鮮回應 時,因 不需發送網路請求
User Agent 應直接使用該 回應 與 狀態碼。
 
例如,Firefox 網路監控器 中,
以 空心灰圓 ,表示從 瀏覽器快取 (browser cache) 中獲取的回應:

 
Chrome 開發者工具,則以灰色字體標注,
回應是從 Disk 或 Memory 快取而來:

 
當然,200 (OK) 只是最常見的,
其他 回應狀態碼 也可能出現在 新鮮回應 (Fresh Response) 的快取中:

 
Q:
咦?還有哪些狀態碼是 可快取的 (cacheable) ?
 
A:
(4-6) 回應狀態碼 給出了完整的列表!

200, 203, 204, 206

300, 301 

404, 405, 410, 414

501

 
然而,實作仍取決於各 User Agent,
例如,有些瀏覽器除了 成功 的 2xx,其他並不快取。
 
除此之外,還得考量 (4-1) 請求方法 的可快取性,
唯有同時滿足兩者 (且表頭欄位未說明不可快取)回應訊息 才是 可快取的 (cacheable)
 
 

陳舊回應 (Stale Response)

當 快取 為 陳舊回應 時,
需發送網路請求驗證 (validate) 訊息可否複用,若否,則重新獲取回應。
 
validate-concept
 
先不討論如何驗證,若 Server 檢查該訊息可以複用 (驗證通過),
則回應 304 (Not Modified) — 未修改,User Agent 便能以該儲存回應 (快取) 滿足請求。
 
這樣做的好處是,無須重下載等效的回應訊息:

304 回應 不得 包含訊息主體,它始終終止於表頭之後的第一個空行 (CRLF)。

 
因此,即使沒有「直接從 瀏覽器快取 中獲得回應」來得快,
透過 驗證 (validate) 的動作,檢查 Server 是否有更動
確保使用的是 等效的 (不一定是最新的) 表示資料。
 
例如,本站的首頁在回應訊息中設置了快取規範,
強制宣告頁面為 永遠陳舊,確保使用者總是看到最新的文章列表:

(其實我認為,以我的撰文速度,設置過期時間為 1 個月也可以 😂)
 
使用者不進行驗證,直接使用快取內容,
或許效能強大,但看不到最新的文章列表也沒意義,對吧 😉。
 
從而能瞭解一概念:

不易改變的靜態內容 (e.g., css、js),
適合較長的過期時間,省去驗證動作;反之。

 


 

分類

HTTP 規範的快取機制,主要快取對象為 — 回應訊息 (response message)
一般用於 客戶端 (Client-side)代理 (Proxy) 快取。
 
伺服端快取 (Server-side Cache) 的快取對象,通常較其餘兩者廣泛,
例如,HTML 頁面、儲存腳本的編譯碼、資料庫的查詢結果、常用的變數…。
 
 

客戶端快取 (Client-Side Cache)

倘若 使用者代理 (User Agent)源伺服器 (Origin Server) 橫跨多國,網路延遲將非常可觀:

 
User Agent 能透過將 回應訊息 儲存於 本地 (e.g., Disk, Memory),
以減少將來 等效請求 的延遲與網絡開銷,大幅地提升效能。
此時又稱為 — — 瀏覽器快取 (Browser Cache)
 

 
Firefox 與 Google Chrome 可分別以 URL:
『 about:cache 』、『 chrome://cache 』檢視快取條目:
about-cache
 
 

代理快取 (Proxy Cache)

然而上例中, User Agent 無論如何仍得面臨 冷啟動 的 快取錯失 (cold-start misses)
— — 第一次的頁面訪問 或 快取 驗證,仍得千里迢迢至 Origin Server 😵。
 
此時,若有一離 User Agent 較近的中介節點,且擁有目標資源的副本,
User Agent 能藉由該節點,獲取目標資源,而不需聯繫 Origin Server,
大大降低 錯失懲罰 (miss penalty) 😆:

 
該節點又稱為 快取代理伺服器 (Caching Proxy Server)
可以是 Client 自行設置的 代理、Server 設置的 閘道 或者 內容傳遞網路 (CDN)。
 
同樣地,其儲存的回應訊息具有一定的 新鮮度 (Freshness),在滿足 Client 請求之前,
若快取「新鮮 (fresh)」,Cache Server 可以直接使用 而 不需發送網路請求 (至 Origin Server)

 
否則「陳舊 (stale)」或 快取未命中,Cache Server 需發送網路請求 (至 Origin Server)驗證 (validate) 訊息可否複用:

Photo by Varnish.
 
 

Varnish

常見的 Caching Proxy Server,
如 歷史悠久的 Web 快取代理 — Squid
專注於 HTTP 反向快取代理 的 Varnish
 

 
個人推薦 Varnish,其 架設容易、設定簡單,
且對 HTTP 語意 具備良好的支援,方便管理快取生命週期,
能有效緩解 Origin Server 流量,大幅提升效能!
 
安裝方式非常容易,例如 Debian 系:
(可能版本較舊)

$ sudo apt-get update
$ sudo apt-get install varnish

 
官網 還貼心的提供各雲端平台映像,
能秒開 Varnish Server 😮,可前往暸解更多安裝方式。
 
安裝完成後,即可進行各基本設定,
例如,將 Varnish 指向源伺服器:

backend default {
  .host = "172.17.72.6";   # Host IP
  .port = "8080";
}

 
設定方式超出本篇範圍,請參閱 官網 說明,
或等我很閒時寫一篇 Varnish 心得 😂。
 
 

共享 vs. 私有

有時 Server 並不希望連接鏈的 中介 存取隱私資訊,
因此依存取權能分為:共享快取 (Shared Cache)私有快取 (Private Cache)
中介 (Intermediaries) 示意圖
 
共享快取 用於提供多位使用者,重複使用的回應,
通常 (但不總是) 部署為中介的一部分。
 
相反的,私有快取 專注於個人用戶,
通常僅儲存於 使用者代理 (User Agent) 本機的 Disk、Memory 中。
 
Origin Server 實作資源時,考量此點是必要的,
HTTP 提供了簡易的方式,「勸導」中介不得快取:

GET / HTTP/1.1

Host: example.com Cache-Control: private

 

 


 

總結

那麼,到底該採用 客戶端快取、代理快取 還是 伺服端快取?
 
Ans:
可以的話都用!
 
 

多層級快取 (Multilevel Cache)

雖然他們的目標皆是 — 提升效能 (improve performance)
然而,帶來的效益卻並不盡相同。
 
例如,單純採用 客戶端快取 (Client-side Cache) 雖能大幅降低請求的數量與成本,
然而,源伺服器 (Origin Server) 仍得處理 其他 無快取 請求
當流量、並發連線數稍多,Server 便可能坐以待斃 😂。
 
此時,使用 閘道 (Gateway) 分散流量、代理快取 (Proxy Cache) 處理快取內容交付,
配合 伺服端快取 (Server-side Cache) 加速請求處理 (e.g., 編譯快取、資料庫查詢快取),
便能有效降低 錯失率錯失懲罰,Server 有望再拼一波。
 
這便是 伺服端快取 (Server-side Cache) 一文,
提及的 多層級快取 (Multilevel Cache) 概念:

 
 

CAP 定理

然而,事情沒有想像的美好:

混用的同時,也帶來了系統複雜度。

 
使用快取,除了得考量節點間的 快取一致性
如何在 CAP 定理 取折衷,也成了一大學問:

是否允許使用者,一定機會看到舊資料? (犧牲一致性)
若否,則允許系統阻塞 或 暫時拒絕請求? (犧牲可用性)
僅實踐 最終一致性 (Eventual Consistency)

 
例如,考量到 高可用性、擴展性 與 效能,
你可能會想用 Cassandra,對各個請求實踐更細緻的 CAP 權衡。
 
總之:

快取越多,越難維持資料新鮮度。

 
引進任何快取機制前,都得三思而後行,
務必準備好適當的快取策略,並區分 可快取 與 不可快取 內容。
 
 

快取檢查表

快取的生命週期,中介的路由方式…,也是重要的考量因素,
您必須依自身需求,發展 最適宜的 快取策略。
 
Ilya Grigorik 告訴我們:
天底下 沒有 所謂的 最佳快取策略
根據您的流量模式、提供的資料類型以及應用特定的資料更新要求,
您必須定義及設置每個資源最適合的設定和整體的「快取階層」。
 
快取檢查表
 
內容看不懂沒有關係,接下來的篇章將會提及,
原文 在此,可做為下集的先前知識,非常建議閱讀 😃。
 
 

作者: 鄭中勝
喜愛音樂,但不知為何總在打程式 ? 期許能重新審視、整理自身所學,幫助有需要的人。

在《HTTP 快取 (Cache)》中有 4 則留言

  1. 這篇太神啦~ 感謝大大!
    長年來一直將 200 和 304 快取搞混 – -…
    文章一開始闡明的 「新鮮/陳舊 & 驗證/非驗證」 觀念,非常清楚實用!

發表迴響