再也不怕面試被問快取了
- 2021 年 7 月 18 日
- 筆記
快取是一個老生常談的問題,重要性不言而喻,HTTP 協議中規定了很多請求頭和響應頭來控制快取。也因為如此,很多人無法分清某個頭部的作用和優先順序。本文嘗試做一下梳理和總結。
經典 GET 請求過程
先看一個經典的 GET 請求的處理過程,如下圖:

當一個請求達到時,瀏覽器(為方便敘述,已瀏覽器為例)先檢查被訪問的資源是否已被快取,如果未被快取(快取未命中 cache miss),則將請求轉發給原始伺服器。如果被快取(快取命中,cache hit),則會檢查快取是否足夠新鮮。如果快取的副本足夠新鮮,則直接將副本返回給客戶端,否則會向服務端發起新鮮度驗證(revalidation)。如果發現與服務端文件一致,則將本地快取副本返回給客戶端,否則將請求轉發給原始伺服器。
在這個過程中,由快取提供服務的請求所在的比例稱為快取命中率(cache hit rat)。這種描述方式只能描述請求級別的命中情況,無法體現具體有多少流量來自快取。比如一個訪問頻次很低,尺寸很大的文件,如果以該命中率來描述的話,命中率非常低。但是這個文件卻佔據了絕大多數的訪問流量。因此還需要另一個命中率指標來描述,那就是位元組命中率(byte hit rate)。位元組命中率表示的是快取提供的位元組在傳輸的所有位元組中所佔的比例。下面章節中伺服器再驗證的兩種策略即是這兩種命中率的具體使用。
快取機制
上圖中,HTTP 通過一些簡單的機制在不要求伺服器記住有哪些快取擁有其文檔副本的情況下,保持已快取數據與伺服器數據之間充分一致。這些機制可以分為兩個部分,第一部分稱為文檔過期(document expiration),第二部分稱為服務端再驗證(server revalidation)。
文檔過期
通過 Cache-Control:max-age 首部和 Expires 首部,HTTP 讓原始伺服器向每個文檔附加一個「過期日期」。在快取文檔過期之前,快取可以以任意頻次使用這些副本,而無需與服務端聯繫。
Expires 首部與 Cache-Control:max-age 首部本質上是一樣的,區別是 Expires 是 HTTP/1.0 協議規定的首部,且首部取值為一個絕對時間,在這個時間之後快取失效;Cache-Control:max-age 是 HTTP/1.1 協議規定的首部,且首部取值是一個相對時間,單位為秒。
伺服器再驗證
HTTP 定義了 5 個條件請求首部來完成伺服器再驗證。
- If-Modified-Since
- If-None-Match
- If-Unmodified-Since
- If-Range
- If-Match
其中最有用的是 If-Modified-Since 和 If-None-Match 兩個首部。
If-Modified-Since: Date 再驗證
If-Modified-Since: Date 再驗證請求工作方式如下:
- 如果自指定日期後,文檔被修改了,If-Modified-Since 條件為真,GET 請求就會執行。攜帶新首部的新文檔會被返回給快取,新首部除了其他資訊以外,還包含了一個新的過期日期。
- 入股自指定日期後,文檔沒有被修改過,條件就為假,會向客戶端返回一個小的 304 Not Modified 響應報文,為了提高有效性,一般會發送一個新的過期日期,不會返迴文檔的主體。
If-Modified-Since 請求首部通常與 Last-Modified 伺服器響應首部配合工作。原始伺服器會將最後的修改日期附加到文檔上去。當快取要對已快取的文檔進行再驗證時,就會包含一個 If-Modified-SinceIf-Modified-Since 首部,其中攜帶有最後修改已快取副本額日期:
If-Modified-Since: <cached last-modified date>
If-None-Match: etag 實體標籤驗證
有些情況下,If-Modified-Since: Date 再驗證無法很好的解決快取問題。比如一個被周期性複寫的文件,但是文件的內容往往是一樣的。這種情況下,就需要藉助實體標籤(Etag)驗證了。實體標籤就是「版本標識符」,是附加到文檔上的任意標籤(引用字元串),可能包含了文檔的序列號或版本名,或者是文檔內容的校驗資訊。
If-None-Match: etag 實體標籤驗證的工作過程與 If-Modified-Since: Date 再驗證的工作過程基本一致,不同的是,伺服器會在響應中附加一個 Etag 響應頭。當快取要對已快取的文檔進行再驗證時,就會將這個 etag 放到 If-None-Match 請求頭中去。
伺服器控制快取的能力
伺服器也可以通過如下方式控制快取,優先順序一次遞減:
- Cache-Control: no-store 禁止快取對響應進行複製。
- Cache-Control: no-cache/ Pragma: no-cache 快取可以複製響應,但是在與原始伺服器進行新鮮度再驗證之前不能將其提供給客戶端。Pramga: no-cache 為了兼容 HTTP/1.0,優先順序低於 Cache-Control: no-cache。
- Cache-Control: must-revalidate 在事前沒有跟原始伺服器進行再驗證的情況下,快取不能提供快取副本。
- Cache-Control: max-age max-age 指定的秒數內有效。max-age 為零時,不可快取。
- Expires: Date 在實際的絕對日期之前有效。
客戶端的新鮮度控制
客戶端通過 Cache-Control 請求首部來強化或放鬆對過期時間的限制。
- Cache-Control: max-stale=< s > 快取可以隨意提供副本,如果指定的秒數,那麼在這段時間內,文檔不能過期。
- Cache-Control: min-fresh=< s > 至少在未來< s >秒內文檔保持新鮮。
- Cache-Control: max-age=< s > 快取無法返回快取時間超過< s >的文檔。如果與 max-stale 通用,max-stale 優先順序更高。
- Cache-Control: no-cache/Pragma: no-cache 除非進行了再驗證,否則客戶端不接受已快取的資源。
- Cache-Control: no-store 快取應該刪除本地快取副本,使用原始伺服器響應。
- Cache-Control: only-if-cache 只有當快取中有副本存在時,客戶端才會獲取一份副本。
小結
合理的快取策略可以幫助我們減少冗餘數據傳輸,節省頻寬,同時加快響應速度。不當的快取策略也可能導致客戶端一直使用過期的快取副本,無法得到及時更新。因此,在搞清楚快取機制後,根據業務需要進行合理配置才是有效使用快取的正確姿勢。
更多精彩文章,請看左上角公告~~