在工作中,前端代碼打包之后的生成的靜態資源就要發布到靜態服務器上,這時候就要做對這些靜態資源做一些運維配置,其中,gzip和設置緩存是必不可少的。這兩項是最直接影響到網站性能和用戶體驗的。
緩存的優點:
減少了不必要的數據傳輸,節省帶寬
減少服務器的負擔,提升網站性能
加快了客戶端加載網頁的速度
用戶體驗友好
缺點:
資源如果有更改但是客戶端不及時更新會造成用戶獲取信息滯后,如果老版本有bug的話,情況會更加糟糕。
所以,為了避免設置緩存錯誤,掌握緩存的原理對於我們工作中去更加合理的配置緩存是非常重要的。
一、強緩存
到底什么是強緩存?強在哪?其實強是強制的意思。當瀏覽器去請求某個文件的時候,服務端就在respone header里面對該文件做了緩存配置。緩存的時間、緩存類型都由服務端控制,具體表現為:
respone header 的cache-control,常見的設置是max-age public private no-cache no-store等
如下圖,
設置了cahe-control:max-age=31536000,public,immutable
max-age表示緩存的時間是31536000秒(1年),public表示可以被瀏覽器和代理服務器緩存,代理服務器一般可用nginx來做。immutable表示該資源永遠不變,但是實際上該資源並不是永遠不變,它這么設置的意思是為了讓用戶在刷新頁面的時候不要去請求服務器!啥意思?就是說,如果你只設置了cahe-control:max-age=31536000,public 這屬於強緩存,每次用戶正常打開這個頁面,瀏覽器會判斷緩存是否過期,沒有過期就從緩存中讀取數據;但是有一些 “聰明” 的用戶會點擊瀏覽器左上角的刷新按鈕去刷新頁面,這時候就算資源沒有過期(1年沒這么快過),瀏覽器也會直接去請求服務器,這就是額外的請求消耗了,這時候就相當於是走協商緩存的流程了(下面會講到)。如果cahe-control:max-age=315360000,public再加個immutable的話,就算用戶刷新頁面,瀏覽器也不會發起請求去服務,瀏覽器會直接從本地磁盤或者內存中讀取緩存並返回200狀態,看上圖的紅色框(from memory cache)。這是2015年facebook團隊向制定 HTTP 標准的 IETF 工作組提到的建議:他們希望 HTTP 協議能給 Cache-Control 響應頭增加一個屬性字段表明該資源永不過期,瀏覽器就沒必要再為這些資源發送條件請求了。
強緩存總結
cache-control: max-age=xxxx,public
客戶端和代理服務器都可以緩存該資源;
客戶端在xxx秒的有效期內,如果有請求該資源的需求的話就直接讀取緩存,statu code:200 ,如果用戶做了刷新操作,就向服務器發起http請求
cache-control: max-age=xxxx,private
只讓客戶端可以緩存該資源;代理服務器不緩存
客戶端在xxx秒內直接讀取緩存,statu code:200
cache-control: max-age=xxxx,immutable
客戶端在xxx秒的有效期內,如果有請求該資源的需求的話就直接讀取緩存,statu code:200 ,即使用戶做了刷新操作,也不向服務器發起http請求
cache-control: no-cache
跳過設置強緩存,但是不妨礙設置協商緩存;一般如果你做了強緩存,只有在強緩存失效了才走協商緩存的,設置了no-cache就不會走強緩存了,每次請求都回詢問服務端。
cache-control: no-store
不緩存,這個會讓客戶端、服務器都不緩存,也就沒有所謂的強緩存、協商緩存了。
二、協商緩存
上面說到的強緩存就是給資源設置個過期時間,客戶端每次請求資源時都會看是否過期;只有在過期才會去詢問服務器。所以,強緩存就是為了給客戶端自給自足用的。而當某天,客戶端請求該資源時發現其過期了,這是就會去請求服務器了,而這時候去請求服務器的這過程就可以設置協商緩存。這時候,協商緩存就是需要客戶端和服務器兩端進行交互的。
怎么設置協商緩存?
response header里面的設置
etag: '5c20abbd-e2e8'
last-modified: Mon, 24 Dec 2018 09:49:49 GMT
- 1
- 2
etag:每個文件有一個,改動文件了就變了,就是個文件hash,每個文件唯一,就像用webpack打包的時候,每個資源都會有這個東西,如: app.js打包后變為 app.c20abbde.js,加個唯一hash,也是為了解決緩存問題。
last-modified:文件的修改時間,精確到秒
也就是說,每次請求返回來 response header 中的 etag和 last-modified,在下次請求時在 request header 就把這兩個帶上,服務端把你帶過來的標識進行對比,然后判斷資源是否更改了,如果更改就直接返回新的資源,和更新對應的response header的標識etag、last-modified。如果資源沒有變,那就不變etag、last-modified,這時候對客戶端來說,每次請求都是要進行協商緩存了,即:
發請求–>看資源是否過期–>過期–>請求服務器–>服務器對比資源是否真的過期–>沒過期–>返回304狀態碼–>客戶端用緩存的老資源。
這就是一條完整的協商緩存的過程。
當然,當服務端發現資源真的過期的時候,會走如下流程:
發請求–>看資源是否過期–>過期–>請求服務器–>服務器對比資源是否真的過期–>過期–>返回200狀態碼–>客戶端如第一次接收該資源一樣,記下它的cache-control中的max-age、etag、last-modified等。
所以協商緩存步驟總結:
請求資源時,把用戶本地該資源的 etag 同時帶到服務端,服務端和最新資源做對比。
如果資源沒更改,返回304,瀏覽器讀取本地緩存。
如果資源有更改,返回200,返回最新的資源。
補充一點,response header中的etag、last-modified在客戶端重新向服務端發起請求時,會在request header中換個key名:
// response header
etag: '5c20abbd-e2e8'
last-modified: Mon, 24 Dec 2018 09:49:49 GMT
// request header 變為
if-none-matched: '5c20abbd-e2e8'
if-modified-since: Mon, 24 Dec 2018 09:49:49 GMT
- 1
- 2
- 3
- 4
- 5
- 6
- 7
為什么要有etag?
你可能會覺得使用last-modified已經足以讓瀏覽器知道本地的緩存副本是否足夠新,為什么還需要etag呢?HTTP1.1中etag的出現(也就是說,etag是新增的,為了解決之前只有If-Modified的缺點)主要是為了解決幾個last-modified比較難解決的問題:
一些文件也許會周期性的更改,但是他的內容並不改變(僅僅改變的修改時間),這個時候我們並不希望客戶端認為這個文件被修改了,而重新get;
某些文件修改非常頻繁,比如在秒以下的時間內進行修改,(比方說1s內修改了N次),if-modified-since能檢查到的粒度是秒級的,這種修改無法判斷(或者說UNIX記錄MTIME只能精確到秒);
某些服務器不能精確的得到文件的最后修改時間。
怎么設置強緩存與協商緩存
后端服務器如nodejs:
res.setHeader(‘max-age’: ‘3600 public’)
res.setHeader(etag: ‘5c20abbd-e2e8’)
res.setHeader(‘last-modified’: Mon, 24 Dec 2018 09:49:49 GMT)
nginx配置
偶爾自己折騰一番非前端的東西時,若心中有數,自然不會手忙腳亂。
怎么去用?
舉個例子,像目前用vue-cli打包后生成的單頁文件是有一個html,與及一堆js css img資源,怎么去設置這些文件呢,核心需求是
要有緩存,毋庸置疑
當發新包的時候,要避免加載老的緩存資源
我的做法是:
index.html文件采用協商緩存,理由就是要用戶每次請求index.html不拿瀏覽器緩存,直接請求服務器,這樣就保證資源更新了,用戶能馬上訪問到新資源,如果服務端返回304,這時候再拿瀏覽器的緩存的index.html,切記不要設置強緩存!!!
其他資源采用強緩存 + 協商緩存,理由就不多說了。