一、前言
關於頁面效能優化,瀏覽器快取必定是一個繞不過的話題,判斷一個網站的效能最直觀的就是看網頁開啟的速度,而提高網頁反應速度的一個方式就是使用快取。一個優秀的快取策略可以縮短網頁請求資源的距離,減少延遲,並且由於快取檔案可以重複利用,還可以減少頻寬,降低網路負荷。因此理解瀏覽器的快取機制,就顯得尤為重要。
二、快取型別
快取在巨集觀上可以分成兩類:私有快取和共享快取。共享快取就是那些能被各級代理快取的快取。私有快取就是使用者專享的,各級代理不能快取的快取。
微觀上可以分下面幾類:
1. 瀏覽器快取
快取存在的意義就是當使用者點選back按鈕或是再次去訪問某個頁面的時候能夠更快的響應。尤其是在多頁應用的網站中,如果你在多個頁面使用了一張相同的圖片,那麼快取這張圖片就變得特別的有用。瀏覽器先向代理伺服器發起Web請求,再將請求轉發到源伺服器。其中瀏覽器快取包括強快取和協商快取,下文有詳細介紹。本文主要側重點就是針對於瀏覽器快取。
2.CDN快取
CDN快取一般是由網站管理員自己部署,為了讓他們的網站更容易擴充套件並獲得更好的效能。通常情況下,瀏覽器先向CDN閘道器發起Web請求,閘道器伺服器後面對應著一臺或多臺負載均衡源伺服器,會根據它們的負載請求,動態將請求轉發到合適的源伺服器上。從瀏覽器角度來看,整個CDN就是一個源伺服器,從這個層面來說,瀏覽器和伺服器之間的快取機制,在這種架構下同樣適用。
3.代理伺服器快取
代理伺服器是瀏覽器和源伺服器之間的中間伺服器,代理轉發響應時,快取代理會預先將資源的副本(快取)儲存到代理伺服器上。當代理再次接收到對相同資源的請求時,就可以不從源伺服器那裡獲取資源,而是將之前快取的資源作為響應返回。
4.資料庫快取
資料庫快取是指,當web應用的關係比較複雜,資料庫中的表很多的時候,如果頻繁進行資料庫查詢,很容易導致資料庫不堪重荷。為了提供查詢的效能,將查詢後的資料放到記憶體中進行快取,下次查詢時,直接從記憶體快取直接返回,提供響應效率。
5.應用層快取
應用層快取是指我們在程式碼層面上做的快取。通過程式碼邏輯,把曾經請求過的資料或資源等,快取起來,再次需要資料時通過邏輯上的處理選擇可用的快取的資料。
三、快取過程分析
瀏覽器與伺服器通訊的方式為應答模式,即是:瀏覽器發起HTTP請求 – 伺服器響應該請求,那麼瀏覽器怎麼確定一個資源該不該快取,如何去快取呢?瀏覽器第一次向伺服器發起該請求後拿到請求結果後,將請求結果和快取標識存入瀏覽器快取,瀏覽器對於快取的處理是根據第一次請求資源時返回的響應頭來確定的。具體過程如下圖:
由上圖我們可以知道:
- 瀏覽器每次發起請求,都會先在瀏覽器快取中查詢該請求的結果以及快取標識
- 瀏覽器每次拿到返回的請求結果都會將該結果和快取標識存入瀏覽器快取中
以上兩點結論就是瀏覽器快取機制的關鍵,它確保了每個請求的快取存入與讀取,只要我們再理解瀏覽器快取的使用規則,那麼所有的問題就迎刃而解了,本文也將圍繞著這點進行詳細分析。為了方便大家理解,這裡我們根據是否需要向伺服器重新發起HTTP請求將快取過程分為兩個部分,分別是強快取和協商快取。
四、強快取
強快取:不會向伺服器傳送請求,直接從快取中讀取資源,在chrome控制檯的network選項中可以看到該請求返回200的狀態碼,並且size顯示from disk cache或from memory cache。
這裡以我的簡書部落格的請求為例,狀態碼為灰色的請求則代表使用了強制快取,請求對應的Size值則代表該快取存放的位置,分別為from memory cache 和 from disk cache。這裡或許小夥伴會有這樣的疑惑:
from memory cache 和 from disk cache又分別代表的是什麼呢?什麼時候會使用from disk cache,什麼時候會使用from memory cache呢?
from memory cache代表使用記憶體中的快取,from disk cache則代表使用的是硬碟中的快取,瀏覽器讀取快取的順序為memory –> disk。在瀏覽器中,瀏覽器會在js和圖片等檔案解析執行後直接存入記憶體快取中,那麼當重新整理頁面時只需直接從記憶體快取中讀取(from memory cache);而css檔案則會存入硬碟檔案中,所以每次渲染頁面都需要從硬碟讀取快取(from disk cache)。
相關的header:
Expires:response header裡的過期時間,瀏覽器再次載入資源時,如果在這個過期時間內,則命中強快取。它的值為一個絕對時間的GMT格式的時間字串, 比如Expires:Thu,21 Jan 2018 23:39:02 GMT
Cache-Control :在HTTP/1.1中,Cache-Control是最重要的規則,主要用於控制網頁快取。比如當Cache-Control:max-age=300時,則代表在這個請求正確返回時間(瀏覽器也會記錄下來)的5分鐘內再次載入資源,就會命中強快取。常見有以下六個屬性值:
public:所有內容都將被快取(客戶端和代理伺服器都可快取)。具體來說響應可被任何中間節點快取,如 Browser <– proxy1 <– proxy2 <– Server,中間的proxy可以快取資源,比如下次再請求同一資源proxy1直接把自己快取的東西給 Browser 而不再向proxy2要。
private:所有內容只有客戶端可以快取,Cache-Control的預設取值。具體來說,表示中間節點不允許快取,對於Browser <– proxy1 <– proxy2 <– Server,proxy 會老老實實把Server 返回的資料傳送給proxy1,自己不快取任何資料。當下次Browser再次請求時proxy會做好請求轉發而不是自作主張給自己快取的資料。
no-cache:客戶端快取內容,是否使用快取則需要經過協商快取來驗證決定。表示不使用 Cache-Control的快取控制方式做前置驗證,而是使用 Etag 或者Last-Modified欄位來控制快取。需要注意的是,no-cache這個名字有一點誤導。設定了no-cache之後,並不是說瀏覽器就不再快取資料,只是瀏覽器在使用快取資料時,需要先確認一下資料是否還跟伺服器保持一致。
no-store:所有內容都不會被快取,即不使用強制快取,也不使用協商快取
max-age:max-age=xxx (xxx is numeric)表示快取內容將在xxx秒後失效
s-maxage(單位為s):同max-age,只用於共享快取(比如CDN快取)。比如當s-maxage=60時,在這60秒中,即使更新了CDN的內容,瀏覽器也不會進行請求。max-age用於普通快取,而s-maxage用於代理快取。s-maxage的優先順序高於max-age。如果存在s-maxage,則會覆蓋掉max-age和Expires header。
Expires和Cache-Control兩者對比:其實這兩者差別不大,區別就在於 Expires 是http1.0的產物,Cache-Control是http1.1的產物,兩者同時存在的話,Cache-Control優先順序高於Expires;在某些不支援HTTP1.1的環境下,Expires就會發揮用處。所以Expires其實是過時的產物,現階段它的存在只是一種相容性的寫法。
強快取判斷是否快取的依據來自於是否超出某個時間或者某個時間段,而不關心伺服器端檔案是否已經更新,這可能會導致載入檔案不是伺服器端最新的內容,那我們如何獲知伺服器端內容是否已經發生了更新呢?此時我們需要用到協商快取策略。
五、協商快取
協商快取就是強制快取失效後,瀏覽器攜帶快取標識向伺服器發起請求,由伺服器根據快取標識決定是否使用快取的過程,主要有以下兩種情況:
- 協商快取生效,返回304和Not Modified
- 協商快取失效,返回200和請求結果
相關的header:
1.Last-Modified和If-Modified-Since
瀏覽器在第一次訪問資源時,伺服器返回資源的同時,在response header中新增 Last-Modified的header,值是這個資源在伺服器上的最後修改時間,瀏覽器接收後快取檔案和header;
Last-Modified: Fri, 22 Jul 2016 01:47:00 GMT
瀏覽器下一次請求這個資源,瀏覽器檢測到有 Last-Modified這個header,於是新增If-Modified-Since這個header,值就是Last-Modified中的值;伺服器再次收到這個資源請求,會根據 If-Modified-Since 中的值與伺服器中這個資源的最後修改時間對比,如果沒有變化,返回304和空的響應體,直接從快取讀取,如果If-Modified-Since的時間小於伺服器中這個資源的最後修改時間,說明檔案有更新,於是返回新的資原始檔和200
但last-modified 存在一些缺點:
①某些服務端不能獲取精確的修改時間
②檔案修改時間改了,但檔案內容卻沒有變
既然根據檔案修改時間來決定是否快取尚有不足,能否可以直接根據檔案內容是否修改來決定快取策略?—-ETag和If-None-Match
2.ETag和If-None-Match
Etag是上一次載入資源時,伺服器返回的response header,是對該資源的一種唯一標識,只要資源有變化,Etag就會重新生成。瀏覽器在下一次載入資源向伺服器傳送請求時,會將上一次返回的Etag值放到request header裡的If-None-Match裡,伺服器只需要比較客戶端傳來的If-None-Match跟自己伺服器上該資源的ETag是否一致,就能很好地判斷資源相對客戶端而言是否被修改過了。如果伺服器發現ETag匹配不上,那麼直接以常規GET 200回包形式將新的資源(當然也包括了新的ETag)發給客戶端;如果ETag是一致的,則直接返回304知會客戶端直接使用本地快取即可。
兩者之間對比:
首先在精確度上,Etag要優於Last-Modified。Last-Modified的時間單位是秒,如果某個檔案在1秒內改變了多次,那麼他們的Last-Modified其實並沒有體現出來修改,但是Etag每次都會改變確保了精度;如果是負載均衡的伺服器,各個伺服器生成的Last-Modified也有可能不一致。
第二在效能上,Etag要遜於Last-Modified,畢竟Last-Modified只需要記錄時間,而Etag需要伺服器通過演算法來計算出一個hash值。
第三在優先順序上,伺服器校驗優先考慮Etag
六、快取的機制
強制快取優先於協商快取進行,若強制快取(Expires和Cache-Control)生效則直接使用快取,若不生效則進行協商快取(Last-Modified / If-Modified-Since和Etag / If-None-Match),協商快取由伺服器決定是否使用快取,若協商快取失效,那麼代表該請求的快取失效,返回200,重新返回資源和快取標識,再存入瀏覽器快取中;生效則返回304,繼續使用快取。具體流程圖如下:
七、使用者行為對瀏覽器快取的影響
如果資源已經被瀏覽器快取下來,在快取失效之前,再次請求時,預設會先檢查是否命中強快取,如果強快取命中則直接讀取快取,如果強快取沒有命中則發請求到伺服器檢查是否命中協商快取,如果協商快取命中,則告訴瀏覽器還是可以從快取讀取,否則才從伺服器返回最新的資源。這是預設的處理方式,這個方式可能被瀏覽器的行為改變:
- 位址列訪問,連結跳轉是正常使用者行為,將會觸發瀏覽器快取機制;
- F5重新整理,瀏覽器會設定max-age=0,跳過強快取判斷,會進行協商快取判斷;
- ctrl+F5重新整理,跳過強快取和協商快取,直接從伺服器拉取資源。