淺談HTTP快取與CDN快取的那點事

京東雲發表於2022-11-15

HTTP 快取與 CDN 快取一直是提升 web 效能的兩大利器,合理的快取配置可以降低頻寬成本、減輕伺服器壓力、提升使用者的體驗。而不合理的快取配置會導致資源介面無法及時更新,從而引發一系列的衍生問題。本文將分別將從 HTTP 快取與 cdn 快取的規則、流程、配置入手,能讓大家瞭解基礎概念的同時,可對自己的專案配置定製化的快取調優方案,以及在遇到快取問題時如何快速定位解決。

首先,讓我們來了解一下快取在實際場景中的應用

使用者第一次訪問網站時,瀏覽器會從伺服器獲取所有的資源。在傳輸過程中,瀏覽器會透過一些約定好的響應頭,從而確定是否需要將這個資源儲存一份到本地作為快取,當使用者第二次訪問該網站的時候,瀏覽器就會從快取中載入資源,不用向伺服器請求資源,從而提高了網站的訪問速度,而若使用了 CDN,當瀏覽器本地快取的資源過期之後,瀏覽器不是直接向源站點請求資源,而是向 CDN 邊緣節點請求資源,CDN 邊緣節點中也存在快取,若 CDN 中的快取也過期,那就由 CDN 邊緣節點向源站點發出回源請求來獲取最新資源。

HTTP 快取

簡介

http 快取是一種客戶端快取,當 Web 瀏覽器向伺服器發起資源請求時,伺服器可以在響應報文頭中包含快取相關的資訊。這些 HTTP Header 會告訴瀏覽器是否以及如何快取資源,再次請求時如果命中快取將直接讀取本地快取不再發出請求。

快取規則

http 快取規則由響應頭中 ExpiresCache-Control ,Last-Modified ,Etag 這四個關鍵欄位控制。其中 Expires 和 Cache-Control 為強快取用來確定確定快取的儲存時間,Last-Modified 和 Etag 為協商快取則用來確定快取是否要被更新,接下來我們簡單來看一下區別。

強快取

・expires: HTTP1.0 中用來控制快取時間的引數,header 裡包含日期 / 時間,用 GMT 格式的字串表示, 即在此時間之後,響應過期。

・cache-control: HTTP1.1 中用來控制快取時間的引數

public: 表明響應可以被任何物件(包括:傳送請求的客戶端,代理伺服器,等等)快取。

private: 表明響應只能被單個使用者快取,不能作為共享快取(即代理伺服器例如 CDN 不能快取它)。

max-age=<seconds>: 設定快取儲存的最大週期,相對於請求的時間快取 seconds 秒,在此時間內,訪問資源直接讀取本地快取,不向伺服器發出請求。(與 expires 同時出現時,max-age 優先順序更高)

s-maxage=<seconds>: 規則等同 max-age,覆蓋 max-age 或者 Expires 頭,但是僅適用於共享快取 (比如各個代理),並且私有快取中它被忽略。(與 expires 或 max-age 同時出現時,s-maxage 優先順序更高)

no-store: 不快取伺服器響應的任何內容,每次訪問資源都需要伺服器完整響應

no-cache: 快取資源,但立即過期,每次請求都需要跟伺服器對比驗證資源是否被修改。(等同於 max-age=0)

協商快取

・Last-modified: 源頭伺服器認定的資源做出修改的日期及時間。精確度比 Etag 低。包含有 If-Modified-Since (資源修改的時間) 或 If-Unmodified-Since 首部的條件請求會使用這個欄位,Last-Modified 優先順序低於 Etag。

・Etag: HTTP 響應頭是資源的特定版本的識別符號,如果客戶端想再次請求相同的 URL,將會傳送一個包含已儲存的 ETag 和 “If-None-Match”(識別符號字串)欄位的請求。客戶端請求之後,伺服器可能會比較客戶端的 ETag 和當前版本資源的 ETag(只要檔案內容改動,ETag 就會重新計算)。如果 ETag 值匹配,這就意味著資源沒有改變,伺服器便會傳送回一個極短的響應,包含 HTTP “304 未修改” 的狀態。304 狀態告訴客戶端,它的快取版本是最新的,並應該使用它。

我們透過 chrome 控制檯可以很輕鬆的找到一個案例:

圖中配置

1.Cache-control: max-age=3600 代表相對於請求時間,快取 3600 秒,即 1 小時,在此時間內,再次訪問資源直接讀取本地快取,不向伺服器傳送請求.

2.Last-modified: Mon... 上次修改時間,如果快取時間過期,該欄位將用於與請求中的 If-Modified-Since 欄位進行對比,一致則繼續使用之前快取,不一致則認定快取失效

3.Expires: 在 http1.0 版本下被 cache-control 覆蓋,此處意為快取至 Mon, 07 Nov ...

4.Etag: Web 伺服器會返回資源和其相應的 ETag 值,該欄位將用於與當前客戶端版本資源的 ETag 進行對比,一致則繼續使用之前快取,不一致則認定快取失效

快取流程

快取規則在其中是如何起作用的呢,我們來看幾個重點關注部分

重點關注 1: 快取是否過期

基於該資源上次響應快取規則同時滿足下列條件則視為快取未過期,不發請求直接從本地快取讀取該檔案。需要注意的是,判斷快取是否過期只跟客戶端有關係,與服務端無關。1&2&3 同時滿足即認為快取未過期,相反則是已過期

1.cache-control 值為 max-age

2.max-age > 0

3. 當前 date < 上次請求時的 date + max-age

注:如果 HTTP 為 1.0 時,則用 expires 判斷是否過期,如果 HTTP 為 1.1 及其以上時,則檢視 cache-control。

重點關注 2: 詢問伺服器資源是否修改

判斷資源是否修改,需要客戶端與伺服器共同協作,客戶端在首次拿到資源快取後會儲存 Etag(若有)和 Last-Modified(若有), 在下次快取過期時會將 Etag 寫在請求頭部中的 If-None-Match 中,將 Last-Modified 值寫在請求頭部中的 If-Modified-Since 中,服務端優先對 Etag 進行對比,然後再對比 Last-Modified,一致即視為快取沒有修改,命中協商快取,返回 304,不一致則返回新檔案並帶上新的 Etag 或 Last-Modified 值。

重點關注 3: 快取規則

參考上文快取規則,不在贅述。

小結

對於 http 快取的配置,我們只有在瞭解 http 快取的原理、規則、流程後,才能根據不同的情況定製不同的規則,真正的發揮 http 快取在實際業務中的價值。

CDN 快取

cdn 快取是一種服務端快取,cdn 服務商可以將源站上的資源緩到其各地的邊緣伺服器節點上。當使用者訪問該資源時,cdn 再透過負載均衡將使用者的請求排程到最近的快取節點上,有效減少了鏈路回源,提高了資源訪問效率及可用性,降低頻寬消耗。

快取規則

與 http 快取規則不同的是,這個規則並不是規範性的,而是由 cdn 服務商來制定,我們以 JD 內部 CDN 舉例,開啟 cdn 接入介面,皮膚如下。

可以看到,提供給我們的配置項只有檔案型別(或檔案目錄)和 Http2,在 cdn 節點上快取預設遵循源站設定快取時長。

運作流程

由圖我們可以看出 CDN 的主要處理邏輯集中在快取處理階段,除了關注 CDN 快取的檔案型別及時間外,我們還需要引入一個概念 —— 回源,客戶端請求訪問資源時,如果 CDN 節點上未快取該資源,或者部署預熱任務給 CDN 節點時,CDN 節點會回源站獲取資源。如圖中所示,接入 cdn 後,我們提供服務的伺服器就是源站,源站一般情況下只會在 cdn 節點沒有資源或 cdn 資源失效時接收到 cdn 節點的資源請求,其他時間,源站並不會接收請求。簡單的概括就是,沒有資源就去源站讀取,有資源就直接傳送給使用者。值得注意的是 cdn 中有 s-maxage=0、max-age=0、no-cache、no-store、private 中的任一種時候,該型別檔案就被認定為不快取檔案,就是所有請求直接轉發源站,只有當快取時間大於 0 且快取過期的時候,才會與源站對比快取是否被修改。

快取配置

與在 Web 瀏覽器中的快取規則類似,可透過傳送快取指令標頭來控制快取在 CDN 中的執行方式。儘管大部分標頭最初都旨在解決客戶端瀏覽器中的快取問題,但現在所有中間快取(如 CDN)也會使用這些標頭,可使用兩個標頭來定義快取重新整理:Cache-Control 和 Expires。 如果兩者都存在,則 Cache-Control 為最新且優先於 Expires。 還有兩種用於驗證的標頭型別(稱為驗證程式):ETag 和 Last-Modified。 如果兩者均已定義,則 ETag 為最新且優先於 Last-Modified。以 OSS 物件儲存為例,在快取配置的文件中特別有以下說明。

快取繼承

當使用者請求您某一業務資源時,源站對應的 Response HTTP Header 中存在 Cache-Control 欄位,此時預設策略如下:

・Cache-Control 欄位為 max-age, 對該資源的快取時間以配置的快取時間為主,對於小於 1 小時的快取時長,不繼承 max-age 指定時間。

・Cache-Control 欄位為 s-maxage=0、max-age=0、no-cache、no-store、private、nil 或無 Cache-Control 欄位時,物件儲存會源節點會為 CDN 預設新增:Cache-Control: max-age=3600 頭部欄位,已確保提高快取的命中率,同時應對高併發回源流量帶來的風險與成本的增加。

快取影響

1. 如果 http 快取設定 cache-control: max-age=600,即快取 10 分鐘,但物件儲存 cdn 快取配置中設定檔案快取時間預設為 1 小時,那麼就會出現如下情況,檔案被訪問後第 20 分鐘修改並上傳到伺服器,使用者重新訪問資源,響應碼會是 304,對比快取未修改,資源依然是舊的,一個小時後再次訪問才能更新為最新資源

2. 如果不設定 cache-control 呢,在 http 快取中我們說過,如果不設定 cache-control,那麼會有預設的快取時間,但在這裡,物件儲存 cdn 服務商明確會在沒有 cache-control 欄位時主動幫我們新增 cache-control: max-age=3600。

注:針對問題 1,也並非沒有辦法,當我們必須要在快取期內修改檔案,並且不向想影響使用者體驗,那麼我們可以使用 cdn 服務商提供的強制更新快取功能,主要注意的是,這裡的強制更新是更新服務端快取,http 快取依然按照 http 頭部規則進行自己的快取處理,並不會受到影響。

小結

cdn 快取的配置並不複雜, 複雜的情況在於 cdn 快取配置會受到 http 快取配置的影響,並且不同的 cdn 運營商有各自的運營規則計費標準,結合來看才能讓 cdn 快取在業務中發揮最大的效能。

HTTP 快取與 CDN 快取的結合

在我們分別瞭解 http 快取配置和 cdn 快取配置後,讓我們再結合引言看一次二者結合的請求過程

當使用者訪問我們的業務伺服器時,首先進行的就是 http 快取處理,如果 http 快取透過校驗,則直接響應給使用者,如果未透過校驗,則繼續進行 cdn 快取的處理,cdn 快取處理完成後返回給客戶端,由客戶端進行 http 快取規則儲存並響應給使用者。再回到開篇快取在實際場景中的應用,當我們分析快取問題時,一定要將兩個流程獨立開來分析判斷,是由於 http 快取配置的不合理,還是 cdn 快取未及時更新引起的問題。

實戰場景推薦(懶人版)

不同訪問場景下的快取規則選擇

1. 不更新檔案內容

優先使用 http 的本地快取,配置 cache-control: max-age=seconds //seconds > 0(且設定為較大值 31536000,即 1 年):強快取,快取當前資源,在配置時期內,再次請求資源直接讀取本地快取。

使用 cdn 快取,當本地快取無法使用時,配置較大的 cache-control: 同樣可以讓業務直接訪問 cdn 資源,且配置時間內不會再發生回源請求。

2. 很少更新檔案內容

對於 img,css,js,fonts 等非 html 資源,我們可以直接考慮配置 cache-control: max-age=seconds //seconds > 0,並且 max-age 配置的時間可以相對久一些,類似於快取規則案例中,cache-control: max-age=36000 配置 10 小時的快取,需要注意的是,這樣配置並不代表這些資源就一定十小時不變,其根本原因在於目前前端構建工具在靜態資源中都會加入戳的概念(例如,webpack 中的 [hash],gulp 中的 gulp-rev),每次修改均會改變檔名或增加 query 引數,本質上改變了請求的地址,也就不存在快取更新的問題。

3. 頻繁更新檔案內容

對於 html 資源,作為前端資源的入口檔案,一旦被強快取,那麼相關的 js,css,img 等均無法更新。對於高頻維護的業務類專案,建議配置 cache-control: no-cache 或 cache-control: max-age=0:採用協商快取,快取當前資源,但每次訪問都需要跟伺服器對比,檢查資源是否被修改。但是基於流量和成本的考慮更推薦於 max-age 設定一個較小值,例如 3600,一小時過期。對於一些活動專案,上線後不會進行較大改動,建議業務配置一個較小的 max-age 值,否則一旦出現 bug 或是未知問題,使用者無法及時更新。

除了以上考慮,有時候其他因素也會影響快取的配置,例如春晚紅包除夕活動,高併發大流量很容易給伺服器帶來極大挑戰,這時我們作為前端開發,就可以採用靜態頁面提前載入兜底來避免使用者多次進入帶來的流量壓力。

如何減少快取規則帶來的訪問影響

1. 透過清理快取控制

我們可以使用 cdn 服務商提供的強制更新快取功能,主要注意的是,這裡的強制更新是更新服務端快取,http 快取依然按照 http 頭部規則進行自己的快取處理,並不會受到影響。

2. 透過 url 帶版本號或者版本釋出時間

我們在使用物件儲存釋出資源時,可根據版本號或釋出時間定義 url,以作為區分。

例:https://storage.jd.com/xxx/xx?verson=1.1.1

例: https://storage.jd.com/xxx/xx?verson=20221111

線上禁止的訪問策略

對於訪問同一資源,url 帶時間戳、uuid 等具有唯一性引數會直接回源至後端服務,帶來極高的頻寬成本及擊穿底層服務的風險,業務在使用 CDN 域名訪問時,請務必謹慎使用帶有時間戳、唯一性引數的 url。

總結

技術行業發展到今天,海量的流量已然成為常態,而 http 快取和 cdn 快取分別作為客戶端快取和服務端快取基石更是值得我們去深入學習、思考。

作者:管宸昊


相關文章