今天小微開店寶在測試環境釋出更新的時候,同事問:“為什麼我需要手動清理瀏覽器快取才能看到變更?難道系統上線後也需要客戶自己清理瀏覽器快取嗎!”看來,這個坑需要我來填了。
什麼是瀏覽器快取
瀏覽器快取(Brower Caching)
是瀏覽器在本地磁碟對使用者最近請求過的文件進行儲存,當訪問者再次訪問同一頁面時,瀏覽器就可以直接從本地磁碟載入文件。
瀏覽器快取的優點有:
-
減少了冗餘的資料傳輸,節省了網費
-
減少了伺服器的負擔,大大提升了網站的效能
-
加快了客戶端載入網頁的速度
在前端開發面試中,瀏覽器快取是web效能優化面試題中很重要的一個知識點,從而說明瀏覽器快取是提升web效能的一大利器,但是瀏覽器快取如果使用不當,也會產生很多問題,正所謂是,想說愛你,並不是很容易的事。所以,結合最近遇到的案例,本文對瀏覽器快取相關的知識進行總結歸納,希望對讀者有所幫助。
瀏覽器快取的分類
瀏覽器快取主要有兩類:快取協商
和徹底快取
,也有稱之為協商快取
和強快取
。
瀏覽器在第一次請求發生後,再次請求時:
-
瀏覽器會先獲取該資源快取的header資訊,根據其中的
expires
和cahe-control
判斷是否命中強快取
,若命中則直接從快取中獲取資源,包括快取的header資訊,本次請求不會與伺服器進行通訊; -
如果沒有命中強快取,瀏覽器會傳送請求到伺服器,該請求會攜帶第一次請求返回的有關快取的header欄位資訊(Last-Modified/IF-Modified-Since、Etag/IF-None-Match),由伺服器根據請求中的相關header資訊來對比結果是否命中協商快取,若命中,則伺服器返回新的響應header資訊更新快取中的對應header資訊,但是並不返回資源內容,它會告知瀏覽器可以直接從快取獲取;否則返回最新的資源內容
強快取
強快取是利用http的返回頭中的Expires
或者Cache-Control
兩個欄位來控制的,用來表示資源的快取時間。
Expires
該欄位是http1.0時的規範,它的值為一個絕對時間的GMT格式的時間字串,比如Expires:Mon,18 Oct 2066 23:59:59
GMT。這個時間代表著這個資源的失效時間,在此時間之前,即命中快取。這種方式有一個明顯的缺點,由於失效時間是一個絕對時間,所以當伺服器與客戶端時間偏差較大時,就會導致快取混亂。
Cache-Control
Cache-Control是http1.1時出現的header資訊,主要是利用該欄位的max-age
值來進行判斷,它是一個相對時間,例如Cache-Control:max-age=3600,代表著資源的有效期是3600秒。cache-control除了該欄位外,還有下面幾個比較常用的設定值:
-
no-cache:不使用本地快取。需要使用快取協商,先與伺服器確認返回的響應是否被更改,如果之前的響應中存在ETag,那麼請求的時候會與服務端驗證,如果資源未被更改,則可以避免重新下載。
-
no-store:直接禁止遊覽器快取資料,每次使用者請求該資源,都會向伺服器傳送一個請求,每次都會下載完整的資源。
-
public:可以被所有的使用者快取,包括終端使用者和CDN等中間代理伺服器。
-
private:只能被終端使用者的瀏覽器快取,不允許CDN等中繼快取伺服器對其快取。
Cache-Control與Expires可以在服務端配置同時啟用,同時啟用的時候Cache-Control優先順序高。
協商快取
協商快取就是由伺服器來確定快取資源是否可用,所以客戶端與伺服器端要通過某種標識來進行通訊,從而讓伺服器判斷請求資源是否可以快取訪問,這主要涉及到下面兩組header欄位,這兩組搭檔都是成對
出現的,即第一次請求的響應頭帶上某個欄位(Last-Modified
或者Etag
),則後續請求則會帶上對應的請求欄位(If-Modified-Since
或者If-None-Match
),若響應頭沒有Last-Modified或者Etag欄位,則請求頭也不會有對應的欄位。
Last-Modify/If-Modify-Since
瀏覽器第一次請求一個資源的時候,伺服器返回的header中會加上Last-Modify,Last-modify是一個時間標識該資源的最後修改時間,例如Last-Modify: Thu,31 Dec 2037 23:59:59 GMT。
當瀏覽器再次請求該資源時,request的請求頭中會包含If-Modify-Since,該值為快取之前返回的Last-Modify。伺服器收到If-Modify-Since後,根據資源的最後修改時間判斷是否命中快取。
如果命中快取,則返回304,並且不會返回資源內容,並且不會返回Last-Modify。
ETag/If-None-Match
與Last-Modify/If-Modify-Since不同的是,Etag/If-None-Match返回的是一個校驗碼。ETag可以保證每一個資源是唯一的,資源變化都會導致ETag變化。伺服器根據瀏覽器上送的If-None-Match值來判斷是否命中快取。
與Last-Modified不一樣的是,當伺服器返回304 Not Modified的響應時,由於ETag重新生成過,response header中還會把這個ETag返回,即使這個ETag跟之前的沒有變化。
為什麼要有Etag
你可能會覺得使用Last-Modified已經足以讓瀏覽器知道本地的快取副本是否足夠新,為什麼還需要Etag呢?HTTP1.1中Etag的出現主要是為了解決幾個Last-Modified比較難解決的問題:
-
一些檔案也許會週期性的更改,但是他的內容並不改變(僅僅改變的修改時間),這個時候我們並不希望客戶端認為這個檔案被修改了,而重新GET;
-
某些檔案修改非常頻繁,比如在秒以下的時間內進行修改,(比方說1s內修改了N次),If-Modified-Since能檢查到的粒度是s級的,這種修改無法判斷(或者說UNIX記錄MTIME只能精確到秒);
-
某些伺服器不能精確的得到檔案的最後修改時間。
Last-Modified與ETag是可以一起使用的,伺服器會優先驗證ETag,一致的情況下,才會繼續比對Last-Modified,最後才決定是否返回304。
強快取與協商快取的區別可以用下表來表示:
快取型別 | 獲取資源形式 | 狀態碼 | 傳送請求到伺服器 |
---|---|---|---|
強快取 | 從快取取 | 200(from cache) | 否,直接從快取取 |
協商快取 | 從快取取 | 304(Not Modified) | 否,通過伺服器來告知快取是否可用 |
使用者行為對快取的影響
使用者操作 | Expires/Cache-Control | Last-Modied/Etag | |
---|---|---|---|
位址列回車 | 有效 | 有效 | |
頁面連結跳轉 | 有效 | 有效 | |
新開視窗 | 有效 | 有效 | |
前進回退 | 有效 | 有效 | |
F5重新整理 | 無效 | 有效 | |
Ctrl+F5強制重新整理 | 無效 | 無效 |
實際問題分析
如文章開頭所屬,程式碼更新到線上後使用者瀏覽器不能自行更新,我們不能要求客戶在系統更新後都進行一次快取清理的操作。
到底該如何解決呢?
在資源請求的URL中增加一個引數,比如:js/mian.js?ver=0.7.1。這個引數是一個版本號,每一次部署的時候變更一下,當這個引數變化的時候,強快取都會失效並重新載入。這樣一來,靜態資源,部署以後就需要重新載入。這樣就比較完美的解決了問題。
進一步思考
這樣做是不是最完美的呢?很遺憾,不是。
至於有什麼更好的建議,希望大家在留言區進行討論。
謝謝!