騰訊DeepOcean原創文章:dopro.io/http-cache-…
首先,讓我們來看這樣一個場景配置http快取與cdn快取一直以來都是web效能優化中重要而常見的手段。合理的http快取與cdn快取配置可以起到減輕伺服器壓力,緩解網路瓶頸,提升使用者體驗等作用,不當的快取配置卻會導致資源無法及時更新,使用者體驗差異,甚至流程出錯等問題。本文主要講解http快取與cdn快取的原理和配置規則,希望通過本文的講解能夠讓大家清楚什麼是合理的快取配置,如何為自己的專案定製化快取方案,以及如果碰到快取問題,應該如何分析解決。
專案A上線了一個新特性,包含著邏輯的改動和頁面UI的更新,小明作為專案開發將程式碼提交後進行了預釋出。產品經理小紅開始體驗新特性,奇怪的是,小紅進入專案後卻並沒有看到最新的特性,這時小明思考了一會說,小紅你點選重新整理再試試,果然,重新整理後專案有了變化,新特性出來了,但是這時又有了新的問題,專案裡的圖片似乎還是舊圖,小明又思考了一會,隨後在電腦前搗鼓一番,讓小紅再次體驗,終於,這個時候特性完整驗收通過。上述的案例中,其實就包含著http快取和cdn快取的應用,當然,這是一個反面教材,實際上線過程中,我們不可能讓每一個使用者點選重新整理來體驗我們的新特性,那應該如何解決上述問題呢,接下來上乾貨。
http快取是客戶端快取,瀏覽器作為客戶端接受到服務端響應後,對於響應首部欄位進行解析,分析出相應的快取規則,將資源按規則進行快取,再次請求時如果命中快取則直接讀取本地快取不再發出請求。
Expires,Cache-Control ,Last-Modified ,Etag 四個欄位,Expires和Cache-Control用來確定確定快取的儲存時間,Last-Modified 和Etag則用來確定快取是否要被更新,我們簡單來看一下區別。
- expires: HTTP1.0中用來控制快取時間的引數,響應頭包含日期/時間, 即在此時間之後,響應過期。
- cache-control: HTTP1.1中用來控制快取時間的引數
- public: 表明響應可以被任何物件(包括:傳送請求的客戶端,代理伺服器,等等)快取。
- private: 表明響應只能被單個使用者快取,不能作為共享快取(即代理伺服器不能快取它)。
- 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首部的條件請求會使用這個欄位。
- Etag: HTTP響應頭是資源的特定版本的識別符號。
圖中配置
- cache-control: max-age=31535000 代表相對於請求時間,快取31536000秒,即365天,在此時間內,再次訪問資源直接讀取本地快取,不向伺服器傳送請求.
- last-modified: Mon...上次修改時間,如果快取時間過期,該欄位將用於與請求中的If-Modified-Since欄位進行對比,一致則繼續使用之前快取,不一致則認定快取失效
- expires: 在http1.0版本下被cache-control覆蓋,此處意為快取至Sat, 11 Aug ...
快取規則在其中是如何起作用的呢,我們來看幾個重點關注部分
重點關注1: 快取是否過期
基於該資源上次響應快取規則同時滿足下列條件則視為快取未過期。需要注意的是,判斷快取是否過期只跟客戶端有關係,與服務端無關。1&2&3同時滿足即認為快取未過期,相反則是已過期
- cache-control值為max-age
- max-age > 0
- 當前 date < 上次請求時的date + max-age
重點關注2: 詢問伺服器資源是否修改
判斷資源是否修改,需要客戶端與伺服器共同協作,客戶端在首次拿到資源快取後會儲存Etag(若有)和Last-Modified(若有),在下次快取過期時會將Etag寫在請求頭部中的If-None-Match中,將Last-Modified值寫在請求頭部中的If-Modified-Since中,服務端優先對Etag進行對比,然後再對比Last-Modified,完全通過後即視為快取沒有修改,有一項不通過則認為資源已被修改,快取失效
重點關注3: 快取規則
快取規則主要由cache-control欄位和expires欄位體現,同時出現則以cache-control為準具體情況如下:
- cache-control=no-store 不快取任何資源
- cache-control=no-cache 快取但立即過期
- cache-control=max-age(s-maxage) = 0 快取但立即過期(等同於no-cache)
- cache-control=max-age(s-maxage)= seconds (seconds > 0)—— 基於請求時間快取seconds秒
- cache-control=其他 根據http標準,如果不攜帶任何關於快取時常的標記,則快取時間等於當前時間和 Last-Modified時間的差值的10%,等同於cache-control=max-age=(date - Last-Modified)/ 10,通過fiddler抓包可看到英文原文:No explicit HTTP Cache Lifetime information was provided.Heuristic expiration policies suggest defaulting to: 10% of the delta between Last-Modified and Date.
- expires = 過去的時間或無效時間,快取但立即過期,等同於cache-control=no-cache
- expires = 未來的時間,快取到對應時間
快取配置
從上述規則和與流程圖中我們可以看到,快取規則的配置其實並不複雜,除開Etag和Last-Modified用於快取對比(實際使用中只需要開啟該功能即可),我們需要關注的其實只是cache-control(expires可轉化為max-age形式,不再贅述),方案如下:- cache-control: no-store:不快取,每次訪問都從服務下載所有資源。
- cache-control: no-cache或cache-control: max-age=0:對比快取,快取當前資源,但每次訪問都需要跟伺服器對比,檢查資源是否被修改。
- cache-control: max-age=seconds //seconds > 0:強快取,快取當前資源,在一定時期內,再次請求資源直接讀取本地快取。
實際專案中,方案1的應用基本上看不到,對比方案2和方案3,方案1沒有任何優勢。在方案2和方案3的選擇中,我們會對資源作區分。
- 對於img,css,js,fonts等非html資源,我們可以直接考慮方案3,並且max-age配置的時間可以儘可能久,類似於快取規則案例中,cache-control: max-age=31535000配置365天的快取,需要注意的是,這樣配置並不代表這些資源就一定一年不變,其根本原因在於目前前端構建工具在靜態資源中都會加入戳的概念(例如,webpack中的[hash],gulp中的gulp-rev),每次修改均會改變檔名或增加query引數,本質上改變了請求的地址,也就不存在快取更新的問題。
- 對於html資源,我們建議根據專案的更新頻度來確定採用哪套方案。html作為前端資源的入口檔案,一旦被強快取,那麼相關的js,css,img等均無法更新。對於高頻維護的業務類專案,建議採用方案2,或是方案3但max-age設定一個較小值,例如3600,一小時過期。對於一些活動專案,上線後不會進行較大改動,建議採用方案3,不過max-age也不要設定過大,否則一旦出現bug或是未知問題,使用者無法及時更新。
小結
對於http快取的配置,我們始終要做到兩點,一是清楚明白http快取的原理與規則,二是明確快取的配置不是一次性的,根據不同的情況配置不同的規則,才能夠更好的發揮http快取的價值。cdn快取
cdn快取是一種服務端快取,CDN服務商將源站的資源快取到遍佈全國的高效能加速節點上,當使用者訪問相應的業務資源時,使用者會被排程至最接近的節點最近的節點ip返回給使用者,在web效能優化中,它主要起到了,緩解源站壓力,優化不同使用者的訪問速度與體驗的作用。快取規則
與http快取規則不同的是,這個規則並不是規範性的,而是由cdn服務商來制定,我們以騰訊雲舉例,開啟cdn加速服務配置,皮膚如下。
可以看到,提供給我們的配置項只有檔案型別(或檔案目錄)和重新整理時間,意義也很簡單,針對不同檔案型別,在cdn節點上快取對應的時間。
cdn運作流程
由圖我們可以看出,cdn快取的配置主要作用在快取處理階段,雖然配置項只有檔案型別和快取時間,但流程卻並不簡單,我們先來明確一個概念——回源,回源的意思就是返回源站,何為源站,就是我們自己的伺服器,很多人誤解接入cdn就是把資源放在了cdn上,其實不然,如圖中所示,接入cdn後,我們的伺服器就是源站,源站一般情況下只會在cdn節點沒有資源或cdn資源失效時接收到cdn節點的請求,其他時間,源站並不會接收請求(當然,如果我們知道源站的地址,我們可以直接訪問源站)。明確了回源的概念後,cdn的流程就顯得不那麼複雜了,簡單的理解就是,沒有資源就去源站讀取,有資源就直接傳送給使用者。與http快取不同的是,cdn中沒有no-cache(max-age=0)的情況,當我們設定快取時間為0的時候,該型別檔案就被認定為不快取檔案,就是所有請求直接轉發源站,只有當快取時間大於0且快取過期的時候,才會與源站對比快取是否被修改。
快取配置
cdn快取配置並不麻煩,整體來說,建議和http快取配置保持統一。需要特別注意的是,cdn的快取配置會受到http快取配置的影響,而且各個cdn服務商並不完全一致,以騰訊云為例,在快取配置的文件中特別有以下說明。這會對我們有什麼影響呢?
- 如果我們http快取設定cache-control: max-age=600,即快取10分鐘,但cdn快取配置中設定檔案快取時間為1小時,那麼就會出現如下情況,檔案被訪問後第12分鐘修改並上傳到伺服器,使用者重新訪問資源,響應碼會是304,對比快取未修改,資源依然是舊的,一個小時後再次訪問才能更新為最新資源
- 如果不設定cache-control呢,在http快取中我們說過,如果不設定cache-control,那麼會有預設的快取時間,但在這裡,cdn服務商明確會在沒有cache-control欄位時主動幫我們新增cache-control: max-age=600。
小結
cdn快取的配置並不複雜, 複雜的情況在於cdn快取配置會受到http快取配置的影響,並且不同的cdn運營商對於這種影響的處理也都不一致,實際使用時,建議去對應的cdn服務商文件中找到對應的注意事項。http快取與cdn快取的結合
當我們分別理解了http快取配置和cdn快取配置後,我們還有一件事情,就是理解二者結合時,請求的流向問題
當使用者訪問我們的業務伺服器時,首先進行的就是http快取處理,如果http快取通過校驗,則直接響應給使用者,如果未通過校驗,則繼續進行cdn快取的處理,cdn快取處理完成後返回給客戶端,由客戶端進行http快取規則儲存並響應給使用者。當我們分析快取問題時,一定要將兩個流程獨立開來分析,現在來看開篇時的錯誤案例,很明顯,第一個問題時由於http快取配置不合理,導致使用者必須進行強制重新整理才能更新資源,第二個問題則是cdn快取未及時更新造成的。
總結
http快取和cdn快取分別作為客戶端快取和服務端快取共同影響著我們的web請求流向,要想做好快取配置,首先是清楚快取的原理和配置規則,其次則是結合專案分析快取級別,具體情況具體處理。