最好的請求,是不必與伺服器進行通訊的請求。
— 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) 訊息可否複用,若否,則重新獲取回應。
先不討論如何驗證,若 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 』檢視快取條目:
代理快取 (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)。
共享快取 用於提供多位使用者,重複使用的回應,
通常 (但不總是) 部署為中介的一部分。
相反的,私有快取 專注於個人用戶,
通常僅儲存於 使用者代理 (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 則留言
這篇太神啦~ 感謝大大!
長年來一直將 200 和 304 快取搞混 – -…
文章一開始闡明的 「新鮮/陳舊 & 驗證/非驗證」 觀念,非常清楚實用!
感謝 Hank 長期以來的支持 🙂
真的講的很好 受益許多 謝謝:)
真的很不錯,推