- 原文地址:Cache-Control for Civilians
- 原文做者:Harry
- 譯文出自:掘金翻譯計劃
- 本文永久連接:github.com/xitu/gold-m…
- 譯者:sunui
- 校對者:portandbridge、yzw7489757
最好的網絡請求就是無須與服務器通訊的請求:在網站速度爲王的比試裏,避免網絡完勝於使用網絡。爲此,使用一個可靠的緩存策略會給你的訪客帶來徹底不一樣的體驗。css
話雖如此,在工做中我愈來愈頻繁地看到不少實踐機會被無心識地錯過,甚至徹底忽視作緩存這件事。大概是由於過分聚焦於首次訪問,也可能單純是由於意識和知識的匱乏。無論是爲何,咱們有必要作一點相關知識的複習。html
Cache-Control
管理靜態資源緩存最多見且有效的方式之一就是使用 Cache-Control
HTTP 報頭。這個報頭獨立應用於每個資源,這意味着咱們頁面中的一切均可以擁有一個很是定製化、顆粒化的緩存政策。咱們由此能夠獲得大量的控制權,得以制定異常複雜而強大的緩存策略。前端
一個 Cache-Control
報頭多是這樣的:android
Cache-Control: public, max-age=31536000
複製代碼
Cache-Control
就是報頭字段名,public
和 max-age=31536000
是指令。Cache-Control
報頭能夠接受一個或多個指令,我在本文中想要講的正是這些指令的真正含義和他們的最佳使用場景。ios
public
和 private
public
意味着包括 CDN、代理服務器之類的任何緩存均可以存儲響應的副本。public
指令常常是冗餘的,由於其餘指令的存在(例如 max-age
)已經隱式表示響應是能夠緩存的。git
相比之下,private
是一個顯式指令,表示只有響應的最終接收方(客戶端或瀏覽器)能夠緩存文件。雖然 private
自己並不具有安全功能,但它意在有效防止公共緩存(如 cdn)存儲包含用戶我的信息的響應。github
max-age
max-age
定義了一個確保響應被視爲「新鮮」的時間單位(相對於請求時間,以秒計)。後端
Cache-Control: max-age=60
複製代碼
可在接下來的 60 秒緩存和重用響應。瀏覽器
這個 Cache-Control
報頭告訴瀏覽器能夠在接下來的 60 秒內從緩存中使用這個文件而沒必要擔憂是否須要從新驗證。60 秒後,瀏覽器將回訪服務器以從新驗證該文件。緩存
若是有了一個新文件供瀏覽器下載,服務器會返回 200
,瀏覽器下載新文件,舊文件也會從 HTTP 緩存中被剔除,新的文件會接替它,並應用新緩存報頭。
若是並無新的副本供下載,服務器會返回 304
,不須要下載新文件,使用新的報頭來更新緩存副本。也就是說若是 Cache-Control: max-age=60
報頭依然存在,緩存文件的 60 秒會從新開始。這個文件的總緩存時間是 120 秒。
注意:max-age
自己有一個巨坑,它告訴瀏覽器相關資源已通過期,但沒有告訴這個過時版本絕對不能使用。瀏覽器可能使用它本身的機制來決定是否在不經驗證的狀況下釋放文件的過時副本。這種行爲有些不肯定性,想確切知道瀏覽器會怎麼作有點困難。爲此,咱們有一系列更爲明確的指令,用來加強 max-age
,感謝 Andy Davies 幫我澄清了這一點。
s-maxage
s-maxage
(注意 max
和 age
之間沒有 -
)會覆蓋 max-age
指令,但只在公共緩存中生效。max-age
和 s-maxage
結合使用可讓你針對私有緩存和公共緩存(例如代理、CDN)分別設定不一樣的刷新時間。
no-store
Cache-Control: no-store
複製代碼
若是咱們不想緩存文件呢?若是文件包含敏感信息怎麼辦?好比一個包含你銀行帳戶信息的 HTML 頁面,或者是有時效性的信息?再或者是個包含實時股價的頁面?咱們根本不想從緩存中存儲或釋放響應:咱們想要的是丟掉敏感信息,獲取最新的實時信息。這時候咱們須要使用 no-store
。
no-store
是一個很是高優先級的指令,表示不會將任何信息持久化到任何緩存中,不管是私有與否。任何帶有 no-store
指令的資源都將始終命中網絡,沒有例外。
no-cache
Cache-Control: no-cache
複製代碼
這點多數人都會困惑...... no-cache
並不意味着 「no cache」。它意味着「在你和服務器驗證過而且服務器告訴你可使用緩存的副本以前,你不
能使用緩存
中的副本」。沒錯,聽起來應該叫 must-revalidate
!不過其實也沒聽起來這麼簡單。
事實上 no-cache
一個能夠確保內容最新鮮的很是智能的方式,同時也能夠儘量使用更快的緩存副本。no-cache
老是會命中網絡,由於在釋放瀏覽器的緩存副本(除非服務器的響應的文件已更新)以前,它必須與服務器從新驗證,不過若是服務器響應容許使用緩存副本,網絡只會傳輸文件報頭:文件主體能夠從緩存中獲取,而沒必要從新下載。
因此如我所言,這是一個兼顧文件新鮮度與從緩存中獲取文件可能性的智能方式,缺點是它至少會爲了一個 HTTP 報頭響應而觸發網絡。
no-cache
一個很好的使用場景就是動態 HTML 頁面獲取。想一想一個新聞網站的首頁:既不是實時的,也不包含任何敏感信息,但理想狀況下咱們但願頁面始終顯示最新的內容。咱們可使用 cache-control: no-cache
來讓瀏覽器首先回訪服務器檢查,若是服務器沒有更新鮮的內容提供(304
),那咱們就重用緩存的版本。若是服務器有更新鮮的內容,它會返回(200
)而且發送最新的文件。
提示:max-age
指令和 no-cache
指令一塊兒發送是沒用的,由於從新驗證的時間限制是零秒。
must-revalidate
更使人困惑的是,雖然上一個指令說應該叫 must-revalidate
,但事實上 must-revalidate
依然是不一樣的東西。(此次更相似一些)
Cache-Control: must-revalidate, max-age=600
複製代碼
must-revalidate
須要一個關聯的 max-age
指令;上文咱們把它設置爲 10 分鐘。
若是說 no-cache
會當即向服務器驗證,通過容許後才能使用緩存的副本,那麼 must-revalidate
更像是一個具備寬期限的 no-cache
。狀況是這樣的,在最初的十分鐘瀏覽器不會(我知道,我知道......)向服務器從新驗證,可是就在十分鐘過去的那一刻,它又到服務器去請求,若是服務器沒什麼新東西,它會返回 304
而且新的 Cache-Control
報頭應用於緩存的文件 —— 咱們的十分鐘再次開始。若是十分鐘後服務器上有了一個新的文件,咱們會獲得 200
的響應和它的報文,那麼本地緩存就會被更新。
must-revalidate
一個很適合的場景就是博客(好比我這個博客):靜態頁面不多更改。固然,最新的內容是能夠獲取的,但考慮到個人網站不多更改,咱們不須要 no-cache
這麼下重手的東西。相反,咱們會假設在十分鐘內一切都好,以後再從新驗證。
proxy-revalidate
和 s-maxage
一脈相承,proxy-revalidate
是公共緩存版的 must-revalidate
。它被私有緩存簡單地忽略掉了。
immutable
immutable
是一個很是新並且整潔的指令,它能夠把更多有關咱們所送出文件類型的信息告知瀏覽器 —— 文件內容是可變或者不可變嗎?瞭解 immutable
是什麼以前,咱們先看看它要解決什麼問題:
用戶刷新會致使瀏覽器強制驗證一個文件而不論文件新鮮與否,由於用戶刷新每每意味着發生了這兩件事之一:
......因此咱們要檢查一下服務器上是否有更加新鮮的內容。
若是服務器上有一個更新鮮的內容可用,咱們固然想下載它。這樣咱們將獲得一個 200
響應,一個新文件,而且 —— 但願是 —— 問題已經修復了。而若是服務器上沒有新文件,咱們將返回 304
報頭,沒有新文件,只有整個往返請求的延遲。若是咱們從新驗證了大量文件且都返回 304
,這會增長數百毫秒的沒必要要開銷。
immutable
就是一種告訴瀏覽器一個文件永遠都不會改變的方法 —— 它是不可變的 —— 所以不要再費心從新驗證它。咱們能夠徹底減去形成延遲的往返開銷。那咱們說的一個可變或不可變的文件是什麼意思呢?
style.css
:當咱們更改文件內容時,咱們不會更改其名稱。這個文件始終存在,其內容始終能夠更改。這個文件就是可變的。style.ae3f66.css
:這個文件是惟一的 —— 它的命名攜帶了基於文件內容的指紋,因此每當文件修改咱們都會獲得一個全新的文件。這個文件就是不可變的。咱們會在 Cache Busting 部分詳細討論這個問題。
若是咱們可以以某種方式告訴瀏覽器咱們的文件是不可變的 —— 文件內容永遠不會改變 —— 那麼咱們也可讓瀏覽器知道它沒必要檢查更新版本:永遠不會有新的版本,由於一旦內容改變,它就不存在了。
這正是 immutable
指令所作的事情:
Cache-Control: max-age=31536000, immutable
複製代碼
在支持 immutable
的瀏覽器中,只要沒超過 31,536,000 秒的新鮮壽命,用戶刷新也不會形成從新驗證。這意味着避免了響應 304
的往返請求,這可能會節約咱們在關鍵路徑上(CSS blocks rendering)的大量延遲。在高延遲的場景裏,這種節約是可感知的。
注意:千萬不要給任何非不可變文件應用 immutable
。你還應該有一個很是周全的緩存破壞策略,以防無心中將不可變文件強緩存。
stale-while-revalidate
我真的真的但願 stale-while-revalidate
能得到更好的支持。
關於從新驗證咱們已經講了不少了:瀏覽器啓程返回服務器以檢查是否有新文件可用的過程。在高延遲的場景裏,從新驗證的過程是能夠被感知的,而且在服務器迴應咱們能夠發佈一個緩存的副本(304
)或者下載一個新文件(200
)以前,這段時間簡直就是死時間。
stale-while-revalidate
提供的是一個寬限期(由咱們設定),當咱們檢查新版本時,容許瀏覽器在這段寬限期期間使用過時的(舊的)資源。
Cache-Control: max-age=31536000, stale-while-revalidate=86400
複製代碼
這就告訴瀏覽器,「這個文件還能夠用一年,但一年事後,額外給你一天你能夠繼續使用舊資源,直到你在後臺從新驗證了它」。
對於非關鍵資源來講 stale-while-revalidate
是一個很棒的指令,咱們固然想要更新鮮的版本,但咱們知道在咱們檢查更新的時候,若是咱們依然使用舊資源不會有任何問題。
stale-if-error
和 stale-while-revalidate
相似的方式,若是從新驗證資源時返回了 5xx
之類的錯誤,stale-if-error
會給瀏覽器一個使用舊的響應的寬限期。
Cache-Control: max-age=2419200, stale-if-error=86400
複製代碼
這裏咱們讓緩存的有效期爲 28 天(2,419,200 秒),事後若是咱們遇到內部錯誤就額外提供一天(86,400 秒),此間容許訪問舊版本資源。
no-transform
no-transform
和存儲、服務、從新驗證新鮮度之間沒有任何關係,但它會告訴中間代理不得對該資源進行任何更改或轉換。
中間代理更改響應的一個常見狀況是電信提供商表明開發者爲用戶作優化:電信提供商可能會經過他們的堆棧代理圖片請求,而且在他們移動網絡傳遞給最終用戶前作一些優化。
這裏的問題是開發人員開始失去對資源展示的控制,而電信服務商所作的圖像優化可能過於激進甚至不可接受,或者可能咱們已經將圖像優化到了理想程度,任何進一步的優化都不必。
這裏,咱們是想要告訴中間商:不要轉換咱們的內容。
Cache-Control: no-transform
複製代碼
no-transform
能夠與其餘任何報頭搭配使用,且不依賴其餘指令獨立運行。
小心:有的轉換是很好的主意:CDN 爲用戶選擇 Gzip 或 Brotli 編碼,看是須要前者仍是可使用後者;圖片轉換服務自動轉成 WebP 等。
小心:若是你是經過 HTTPS 運行,中間件和代理不管如何都不能改變你的數據,所以 no-transform
也就沒用了。
講緩存而不講緩存破壞(Cache Busting)是不負責任的。我老是建議甚至在考慮緩存策略以前就先要解決緩存破壞策略。反過來作就是自找麻煩了。
緩存破壞解決這樣的問題:「我只是告訴過瀏覽器在接下來的一年使用這個文件,但後來我改動了它,我不想讓用戶拿到新副本以前要等一全年!我該怎麼作?!」
style.css
這是最不建議作的事情:徹底沒有任何緩存破壞。這是一個可變的文件,咱們真的很難破壞緩存。
緩存這樣的文件你要很是謹慎,由於一旦在用戶的設備上,咱們就幾乎失去了對他們的全部控制。
儘管這個例子是一個樣式表,HTML 頁面也純屬這個陣營。咱們不能更改一個網頁的文件名,想象一下這破壞力!—— 這正是咱們傾向於從不緩存它們的緣由。
style.css?v=1.2.14
這裏依然是一個可變的文件,可是咱們在文件路徑後加了個查詢字符串。聊勝於無,但不盡完美。若是有什麼東西把查詢字符串刪掉了,咱們就徹底回到了以前講的沒有緩存破壞的樣子。不少代理服務器和 CDN 都不會緩存查詢字符串,不管是經過配置(例如 Cloudflare 官方文檔寫到:「......從緩存服務請求時,‘style.css?something’將會被標準化成‘style.css’」)仍是防護性忽略(查詢字符串可能包含請求特定響應的信息)。
style.ae3f66.css
添加指紋是目前破壞文件緩存的首選方法。每次內容變動,文件名都會隨之修改,嚴格地講咱們什麼都不緩存:咱們拿到的是一個全新的文件!這很穩健,而且容許你使用 immutable
。若是你能在你的靜態資源上實現這個,那就去幹!一旦你成功實現了這種很是可靠的緩存破壞策略,你就可使用最極致的緩存形式:
Cache-Control: max-age=31536000, immutable
複製代碼
這種方法的要點就是更改文件名,但它不非得是指紋。下面的例子都有一樣的效果:
/assets/style.ae3f66.css
:經過文件內容的 hash 破壞。/assets/style.1.2.14.css
:經過發行版本號破壞。/assets/1.2.14/style.css
:改變 URL 中的目錄。然而,最後一個示例意味着咱們要對每一個版本進行版本控制,而不是獨立文件。這反過來意味着若是咱們只想對咱們的樣式表作緩存破壞,咱們也不得不破壞了這個版本的全部靜態文件。這可能有點浪費,因此推薦選項(1)或(2)。
Clear-Site-Data
緩存很難失效 —— 這是聞名於計算機科學界的難題 —— 因而有了一個實現中的規範,這能夠幫助開發者明確地一次性清理網站域的所有緩存:Clear-Site-Data
。
本文我不想深刻探究 Clear-Site-Data
,畢竟它不是一種 Cache-Control
指令,事實上它是一個全新的 HTTP 報頭。
Clear-Site-Data: "cache"
複製代碼
給你的域下任何一個靜態文件應用這個報頭,就會清除整個域的緩存,而不只是它附着的這個文件。也就是說,若是你須要給你整個網站的全部訪客的緩存來個大掃除,你只需把上面這個報頭加到你的 HTML 上便可。
瀏覽器支持方面,截止到本文寫做只支持 Chrome、Android Webview、Firefox 和 Opera。
提示:Clear-Site-Data
能夠接收不少指令:"cookies"
、"storage"
、"executionContexts"
和 "*"
(顯然,意思是「上述所有」)。
Okay,讓咱們看一些場景,以及咱們可能使用的 Cache-Control
報頭的類型。
在線銀行之類的應用頁面羅列着你最近交易清單、當前餘額和一些敏感的銀行帳戶信息,它們都要求實時更新(想象一下,當你看到頁面裏羅列的帳戶餘額仍是一週前的你啥感受!)並且要求嚴格保密(你確定不想把你的銀行帳戶詳情存在共享緩存裏(啥緩存都很差吧))。
爲此,咱們這樣作:
Request URL: /account/
Cache-Control: no-store
複製代碼
根據規範,這足以防止瀏覽器在全部私有緩存和共享緩存中把響應持久化到磁盤中:
no-store
響應指令要求緩存中不得存儲任何關於客戶端請求和服務端響應的內容。該指令適用於私有緩存和共享緩存。上文中「不得存儲」的意思是緩存不得故意將信息存儲到非易失性存儲器中,而且在接轉後必須盡最大努力盡快從易失性存儲器中刪除信息。
但若是你還不放心,也許你能夠選擇這樣:
Request URL: /account/
Cache-Control: private, no-cache, no-store
複製代碼
這將明確指示不得在公共緩存(例如 CDN)中存儲任何信息、始終提供最新的副本而且不要持久化任何東西。
若是咱們打算作一個顯示準實時信息的頁面,咱們要儘量保證用戶老是看到最準確的、最實時的信息,咱們使用:
Request URL: /live-updates/
Cache-Control: no-cache
複製代碼
這個簡單的指令會讓瀏覽器不直接未經服務器驗證經過就從緩存顯示響應。這意味着用戶將毫不會看到過時的信息,而若是服務器上有最新信息與緩存中的相同,他們也會享受從緩存中抓取文件的好處。
這幾乎對全部網站來講都是一個明智的選擇:儘量給咱們最新的內容,同時儘量讓咱們享受緩存帶來的訪問速度。
像 FAQ 這樣的頁面可能不多更新,並且其內容不太可能對時間敏感。它固然沒有實時運動成績或航班狀態那麼重要。咱們能夠將這樣的 HTML 頁面緩存一段時間,並強制瀏覽器按期檢查新內容,而不用每次訪問都檢查。咱們這樣設置:
Request URL: /faqs/
Cache-Control: max-age=604800, must-revalidate
複製代碼
這會容許瀏覽器緩存 HTML 頁面 一週時間(604,800 秒),一旦一週過去,咱們須要向服務器檢查更新。
小心:給同一個網站的不一樣頁面應用不一樣的緩存策略會形成一個問題,在你設置 no-cache
的首頁會請求它引用的最新的 style.f4fa2b.css
,而在你的加了三天緩存的 FAQ 頁依然指向 style.ae3f66.css
。這種狀況可能影響不大,但不容忽視。
比方說們的 app.[fingerprint].js
,更新很是頻繁 —— 幾乎每次發佈版本都會更新 —— 而咱們也投入了工做,在文件每次更改時對其添加指紋,而後這樣使用:
Request URL: /static/app.1be87a.js
Cache-Control: max-age=31536000, immutable
複製代碼
無所謂咱們有多頻繁的更新 JS:由於咱們能夠作到可靠的緩存破壞,咱們想緩存多久就緩存多久。這個例子裏咱們設置成一年。之因此是一年首先是由於這已經好久了,並且瀏覽器不管如何也不可能把一個文件保存這麼久(瀏覽器用於 HTTP 緩存的存儲空間是限量的,他們會按期清空一部分;用戶也可能本身清空緩存)。超過一年的配置大機率沒什麼用。
進一步講,由於這個文件內容永不改變,咱們能夠指示瀏覽器這個文件是不可變的。一全年內咱們都無須從新驗證它,哪怕用戶刷新頁面都不須要。這樣咱們不只得到了使用緩存的速度優點,還避免了從新驗證形成的延遲弊端。
想象一個伴隨文章的純裝飾性照片。它不是信息圖表,也不含影響頁面其餘部分閱讀的關鍵內容。甚至若是它徹底不見了用戶都關注不到。
圖片每每是要下載的重量級資源,因此咱們想要緩存它;由於它在頁面中沒有那麼關鍵,因此咱們不須要下載最新版本;咱們甚至能夠在這張照片過期一點後繼續使用。看看怎麼作:
Request URL: /content/masthead.jpg
Cache-Control: max-age=2419200, must-revalidate, stale-while-revalidate=86400
複製代碼
這裏咱們告訴瀏覽器緩存 28 天(2,419,200 秒),28 天期限事後咱們想向服務器檢查更新,若是圖片沒有超過一天(86,400 秒)的過時時間,那麼咱們就在後臺請求到最新版本後再替換它。
stale-while-revalidate
之類的指令給一個不新鮮寬限期。immutable
和 stale-while-revalidate
不只能帶來緩存的傳統效益,還讓咱們在從新驗證時下降延遲成本。儘量避免使用網絡會爲用戶提供更快的體驗(也會給咱們的基礎設施更低的吞吐量,兩開花)。經過對資源的詳細瞭解和可用內容的總覽,咱們能夠開始針對咱們的應用設計作一個顆粒化、定製化且有效的緩存策略。
緩存在手,一切盡在掌控。
在某人因個人言行不類開噴以前,有必要一提的是我本身博客的緩存策略這麼差強人意,以致於我本身都看不下去了。
若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。