一、若是請求命中本地緩存則從本地緩存中獲取一個對應資源的"copy";
二、檢查這個"copy"是否fresh,是則直接返回,不然繼續向服務器轉發請求。
三、服務器接收到請求,而後判斷資源是否變動,是則返回新內容,不然返回304,未變動。
四、客戶端更新本地緩存。 css
no-cache:強制客戶端跳過步驟2,直接向服務器發送請求。也就是說每次請求都必須向服務器發送。
must-revalidate:意味着某個資源在本地已緩存時長短於 max-age
指定時長時,能夠直接使用,不然就要發起驗證。
no-store:緩存將不存儲response,包括header和body。測試結果代表,除每次請求都必發送到服務器外,響應代碼均是200,且request並無發送"If-Modified-Since"和"If-None-Match"頭,這意味着緩存的確沒有存儲response。
以上三者都是要求客戶端每次請求都必須到服務器進行revalidate,此功能還能夠經過max-age=0實現git
使用緩存會帶來巨大的性能提高,還能節省帶寬、減小服務端開銷,但不少網站對緩存只知其一;不知其二,讓相互依賴的資源出現競態條件,從而沒法同步更新。github
使用緩存的最佳實踐大致上能夠概括爲這兩種模式:django
Cache-Control: max-age=31536000gulp
max-age
指定時間內,緩存副本能夠直接使用,不須要與服務端協商;頁面:嘿,我須要 "/script-v1.js"、"/styles-v1.css" 和 "/cats-v1.jpg"。(10:24)瀏覽器
緩存:我這兒都沒有。 服務端
,你有麼?(10:24)緩存
服務端:固然,給你。對了 緩存
,把這些資源存起來,一年以內直接用吧。(10:25)服務器
緩存:多謝!(10:25)網絡
頁面:贊哦!(10:25)框架
頁面:嘿,此次我要 "/script- v2 .js"、"/styles- v2 .css" 和 "/cats-v1.jpg"。(08:14)
緩存:我只有其中一個,先拿去用,剩下的我沒有。 服務端
,看看?
服務端:好,給你新的 CSS 和 JS 文件。對了 緩存
,這些你也留着用一年吧。(08:15)
緩存:超讚!(08:15)
頁面:多謝!(08:15)
緩存:嗯,"/script-v1.js" 和 "/styles-v1.css" 有一陣子沒被用到。我決定刪掉它們。(12:32)
在這種模式中,絕對不會修改某個 URL 的內容,只會改變 URL 自己:
<script src="/script-f93bca2c.js"> <link rel="stylesheet" href="/styles-a837cb1e.css"> <img src="/cats-0e9a2ef4.jpg" alt="…">
上面每一個 URL 都包含了與文件內容同步修改的部分。這部分能夠是版本號、修改時間,或者是文件內容的 MD5 —— 個人博客就是這麼幹的。
大部分服務端框架都有對應工具來輕鬆完成這項工做(我在用 Django 的 ManifestStaticFilesStorage ),還有一些很輕量的 Node.js 庫能夠實現一樣功能,例如 gulp-rev 。
可是,文章或博客詳情這類 HTML 頁面不適用於這種模式。這些頁面 URL 不能包含版本號,頁面內容還必須能修改。尤爲是發現有拼寫錯誤或語法錯誤後,須要快速頻繁地更新。
Cache-Control: no-cache
頁面:嘿,我須要 "/about/" 和 "/sw.js" 兩份資源。(11:32)
緩存:我搞不定。 服務端
,看看?(11:32)
服務端:哈,我有,給你。 緩存
,你能夠本身存一份,但用以前要先問我哈。(11:33)
緩存:明白!(11:33)
頁面:多謝!(11:33)
頁面:我又想要 "/about/" 和 "/sw.js"。(9:46)
緩存:等一下。 服務端
,我能直接用我本地的副本嗎?在我這兒,"/about/" 最後修改於週一,"/sw.js" 最後修改於昨天。(9:46)
服務端:"/sw.js" 在那以後就沒改過呢。(9:47)
緩存:贊! 頁面
,給你 "/sw.js"。(9:47)
服務端:但 "/about/" 改過,我給你最新的版本。 緩存
,跟以前同樣,你能夠本身存一份,但用以前要先問我~(9:47)
緩存:瞭解!(9:47)
頁面:贊噢!(9:47)
注: no-cache
並非說「不緩存」,它意味着使用緩存前必須檢查(或者說 驗證
)這個資源在服務端是否有更新。 no-store
用來告知瀏覽器徹底不要緩存這個資源。相似的, must-revalidate
並非說「每次都要驗證」,它意味着某個資源在本地已緩存時長短於 max-age
指定時長時,能夠直接使用,不然就要發起驗證。好,這下明白了。
這種模式下,也能夠給資源響應加上 ETag
(資源的版本 ID)、 Last-Modified
時間這兩個頭部。下次客戶端請求這些資源時,會經過 If-None-Match
或 If-Modified-Since
這兩個請求頭帶上以前的值,這樣服務端就能夠返回「直接用你以前緩存的版本吧,它們是最新的」,換成行話就是「HTTP 304」。
若是服務端沒辦法發送 ETag
/ Last-Modified
頭部,那每次都須要發送完整的響應內容。
這種模式下,每次都會產生網絡請求,因此它沒有能節省網絡請求的模式一好。
模式一被基礎設施影響,模式二被網絡請求影響,都是常見的事兒。因此又有了中間方案:給可變內容加上短一點的 max-age
。這個折中方案太太太糟糕了。
不幸的是這種作法並不罕見,Github pages 當前就是這樣。
假設這樣三個資源:
/article/
/styles.css
/script.js
都有這樣的響應頭:
Cache-Control: must-revalidate, max-age=600
If-Modified-Since
和 If-None-Match
請求頭;頁面:嘿,我須要 "/article/"、"/script.js" 和 "/styles.css"。(10:21)
緩存:我這裏沒有, 服務端
?(10:21)
服務端:沒問題,給你。對了 緩存
,這些資源你能夠存 10 分鐘。(10:22)
緩存:明白!(10:22)
頁面:多謝!(10:22)
頁面:嘿,我又想要 "/article/"、"/script.js" 和 "/styles.css"。(10:28)
緩存:天哪,真抱歉,我把 "/styles.css" 給弄丟了,其它的都有,先給你。 服務端
,你能再把 "/style.css" 發給我嗎?(10:28)
服務端:固然能夠,實際上它在你上次請求以後發生了改變。一樣,你又能夠把它緩存 10 分鐘。(10:29)
緩存:沒問題。(10:29)
頁面:多謝!等等!完全掛了!!發生什麼啦?(10:29)
這種場景在測試環境能夠構造出來,但在真實環境中難復現,也難追查。在上述例子中,實際上服務端同時更新了 HTML、CSS 和 JS,可是頁面最終從緩存中拿到舊的 HTML 和 JS,並從服務端拿到最新的 CSS。版本不匹配致使功能異常。
一般,當咱們對 HTML 改動很大時,極可能 CSS 須要爲新結構做出調整,JS 也須要配合 CSS 和 HTML 改動而進行相應修改。這些資源相互依賴,但沒法經過緩存頭反映出來。最終頁面可能會拿到一部分新資源,一部分舊資源。
max-age
是響應時間的相對值,某個頁面上的全部資源請求,會被設置在大體相同的時間後失效,但仍有小几率出現競爭。若是你有一些不包含 JS、或包含不一樣 CSS 的頁面,過時時間能夠不一樣步。更爲糟糕的是,瀏覽器一直都在淘汰緩存的資源,它不可能知道某些 HTML、CSS 和 JS 相互有依賴,因此會出現部分淘汰的狀況。綜上所述,最終頁面拿到版本不匹配的資源並不是不可能發生。
對於用來來講,這會破壞頁面佈局和/或功能,從小問題到大事故都有可能發生。
謝天謝地,咱們有個解決方案。。。
刷新頁面,會讓瀏覽器向服務端發起驗證,忽略 max-age
。因此若是用戶對 max-age
的這個問題頗有經驗的話,點擊刷新按鈕就能解決一切問題。固然,要求用戶這樣作會下降用戶對你的信任,會讓用戶以爲你的網站很不穩定。
給常常改變的內容設置 max-age
一般是錯誤的選擇,但也不全是。如今你看到的這個頁面(譯者注:指原文頁面)就設置了三分鐘的 max-age
。在這裏競態條件不是問題,由於這個頁面不依賴任何使用一樣緩存模式的資源(個人 CSS、JS 及圖片都屬於模式一:不變內容),也不被其它使用一樣緩存模式的頁面所依賴。
這種模式意味着,若是我足夠幸運寫了一篇大受歡迎的文章,個人 CDN(Cloudflare)會幫個人服務器抗住流量,只要我能接受修改文章要等三分鐘才能被用戶看到,我確實能夠接受。
這種模式用起來也沒那麼容易。若是我給一篇文章增長了一段內容,再在另一篇文章中指向它,我就在頁面之間建立了會引入競爭的依賴關係。用戶可能點擊連接後看到的是不包含新增內容的緩存副本。要避免這種問題發生,我必須在更新文章後,去 Cloudflare 的控制檯刷新這篇文章的緩存,等三分鐘再在其它文章加上指向它的連接。是的,你必須很是當心地使用這種模式。
只要用法恰當,緩存能極大的提高性能、節省帶寬。讓不變內容能夠輕鬆改變 URL,讓可變內容走服務端驗證。若是你很勇敢,當你能確認你的內容既不依賴別人也不被別人依賴時,才針對可變內容使用 max-age
,由於它可能沒法同步更新。
從新載入:不額外添加cache-control
f5或刷新按鈕:cache-control:max-age=0 發送確認請求 檢查資源是否修改 ctrl+f5: cache-control:no-cache 並清空緩存 pragma:no-cache