背景
緩存是用來做性能優化的好東西,但是,如果用不好緩存,就會產生一系列問題:
- 為什麼我的頁面顯示的還是老版本
- 為什麼我的網頁白屏
- 請刷新下網頁
- ...
以上問題大家或多或少都遇到過,歸根結底是使用緩存的姿勢不對,今天,我們就來一起了解下瀏覽器是如何進行緩存的,以及我們要怎樣科學的使用緩存
瀏覽器的緩存機制
1. 什麼是瀏覽器緩存?
簡單説,瀏覽器把 http 請求的資源保存到本地,供下次使用的行為,就是瀏覽器緩存
這裏先記一個點:http 響應頭,決定了瀏覽器會對資源採取什麼樣的緩存策略
2. 瀏覽器是讀取緩存還是請求數據?
- 用户第一次請求資源
- 整個完整流程
3. 緩存過程分類——強緩存 / 協商緩存
根據是否請求服務,我們把緩存過程分為強緩存和協商緩存,也可以理解為必然經過的過程稱為強緩存,如果強緩存沒有,那在和服務器協商一下
強緩存
強緩存看的是響應頭的 Expires 和 Cache-Control 字段
- Expires 是老規範,它表示的是一個絕對有效時間,在該時間之前則命中緩存,如果超過則緩存失效,並且,由於它是跟本地時間(可以隨意修改)做比較,會導致緩存混亂
- Cache-Control 是新規範,優先級要高於Expires,也是目前主要使用的緩存策略,字段是max-age,表示的是一個相對時間,例如 Cache-Control: max-age=3600,代表着資源的有效期是 3600 秒。
其他配置
no-cache:需要進行協商緩存,發送請求到服務器確認是否使用緩存。
no-store:禁止使用緩存,每一次都要重新請求數據。
public:可以被所有的用户緩存,包括終端用户和 CDN 等中間代理服務器。
private:只能被終端用户的瀏覽器緩存,不允許 CDN 等中繼緩存服務器對其緩存。
協商緩存
當強緩存沒有命中的時候,瀏覽器會發送一個請求到服務器,服務器根據 header 中的部分信息來判斷是否命中緩存。如果命中,則返回 304 ,告訴瀏覽器資源未更新,可使用本地的緩存。
協商緩存看的是 header 中的 Last-Modified / If-Modified-Since 和 Etag / If-None-Match
緩存生效,返回304,緩存失效,返回200和請求結果
Etag 優先級 Last-Modified 高
- Last-Modified / If-Modified-Since
瀏覽器第一次請求一個資源的時候,服務器返回的 header 中會加上 Last-Modify,Last-modify 是一個時間標識該資源的最後修改時間。
當瀏覽器再次請求該資源時,request 的請求頭中會包含 If-Modify-Since,該值為緩存之前返回的 Last-Modify。服務器收到 If-Modify-Since 後,根據資源的最後修改時間判斷是否命中緩存,命中返回304使用本緩存,否則返回200和請求最新資源。
- Etag / If-None-Match
etag 是更為嚴謹的校驗,一般情況下使用時間檢驗已經足夠,但我們想象一個場景,如果我們在短暫時間內修改了服務端資源,然後又迅速的改回去,理論上這種情況本地緩存還是可以繼續使用的,這就是 etag 誕生的場景。
使用 etag 時服務端會對資源進行一次類似 hash 的操作獲得一個標識(內容不變標識不變),並返回給客户端。
再次請求時客户端會在 If-None-Match 帶上 etag 的值給服務端進行對比驗證,如果命中返回304使用緩存,否則重新請求資源。
注:由於 e-atg 服務端計算會有額外開銷,所以性能較差
擴展:DNS緩存與CDN緩存
DNS 緩存
我們在網上所有的通信行為都需要IP來進行連接,DNS解析是指通過域名獲取相應IP地址的過程。
基本上有DNS的地方就有緩存,查詢順序如下:
一般我們日常會接觸到的就是有時內網域名訪問需要修改本地host映射關係,或者某些科學上網的情況,可以通過修改本地host來正常訪問網址
CDN 緩存
CDN 緩存從減輕根服務的分發壓力和縮短物理的傳輸距離(跨地域訪問)上2個層面對資源訪問進行優化。
CDN節點解決了跨運營商和跨地域訪問的問題,訪問延時大大降低。
大部分請求在CDN邊緣節點完成,CDN起到了分流作用,減輕了源服務器的負載。
一般CDN服務都由運營商提供,我們只需要瞭解如何驗證CDN是否生效即可
-
查看域名是否配置了CDN緩存
ping {{ 域名 }} 會看到轉向了其他地址(alikunlun)
例如: ping customer.kukahome.com
- 查看我們的頁面資源是否命中CDN緩存
通過查看相應頭有 X-cache:HIT 字段,則命中CDN緩存,注意這裏名稱並不固定,但一般都會有HIT標識,如果是MISS 或None之類的,則沒有命中緩存
前端針對緩存部署優化方案
構建演進
構建方面優化的核心思想是如何更優,更快速的加載資源,以及如何保證資源的新鮮度
這個優化過程也分為幾個階段,有些其實已經不適用現在的場景,但也可以瞭解下
-
早期的圖標合併雪碧圖(sprite),多腳本文件整理到一個文件:目的是通過減少碎片化的請求數量來加速資源加載(相關知識點是瀏覽器一般最多隻支持6個併發請求,同一時間請求數量需要控制在合理範圍)
- 現在雪碧圖已基本被 iconfont 代替,js 加載更多采用分模塊異步加載,而不是一味合併
-
隨着 web 應用的推廣和瀏覽器緩存技術的普及,前端緩存問題也隨着而來,最常見的就是服務端資源變了,但是客户端資源始終無法更新,這個階段工程師們想了很多方案。
- 打包時在靜態資源路徑上加上 “?v=version” 或者使用時間戳來給資源文件命名
- 跟 modified 緩存有點像,由於時間戳並不能識別出文件內容是否變動,所以有了後來的 hash 方案,理論上 hash 出來的文件只要內容不變,文件名就不變,大大提高了緩存的使用壽命,也是現代常用打包工具的默認配置
-
然後,重點來了,以上我們對 html 文件裏鏈接的資源做了一系列優化,但是 html 本身也是一種靜態資源,並且,客户在訪問頁面時是不會帶上所謂的時間戳或者版本號的,導致了很多時候雖然服務端資源更新了,但是客户端還是用老的 html 文本發起請求,這個時候就會導致各種各樣的問題了,包括但不限於白屏,展現的舊版本頁面等等
- 為了解決這個問題,目前主流的解決方案是不對 html 進行緩存(一般單頁應用html文件較小,大的是 js),只對 js,css 等靜態文件進行本地緩存
-
那麼,如何讓瀏覽器不緩存 html 呢,目前都是通過設置 Cache-Control實現, 有前端方案和後端方案,風險提示,前端方案很不靠譜,後端很多默認配置會覆蓋前端方案,可以做了解,生產中請使用後端配置。
通過 html 標籤設置 cache-control
<meta http-equiv="Pragma" content="no-cache" /> // 舊協議 <meta http-equiv="Expires" content="0" /> // 舊協議 <meta http-equiv="Cache-Control" content="no-cache" /> // 目前主流
部署配置
目前主流的前端部署方式都是使用 nginx,我們來看看 nginx 如何禁用 html 的緩存
location / {
root **;
# 配置頁面不緩存html和htm結尾的文件
if ($request_filename ~* .*.(?:htm|html)$)
{
add_header Cache-Control "private, no-store, no-cache, must-revalidate, proxy-revalidate";
}
index index.html index.htm;
}
- Private 會影響到CDN緩存命中率,但本身CDN緩存也會存在版本問題,量不大的情況下也可以禁掉
- No-cache 可以使用緩存,但是使用前必須到服務端驗證,可以是 no-cache,也可以是 max-age=0
- No-store 完全禁用緩存
- Must-revalidate 與 no-cache 類似,強制到服務端驗證,作用於一些特殊場景,例如發送了校驗請求,但發送失敗了之類
- Proxy-revalidate 與上面類似,要求代理緩存服務驗證有效性
以上配置可以跟據項目需要靈活配置,考慮到瀏覽器對緩存協議支持會有些許差異,只是想簡單粗暴禁用 html 緩存全上也沒有關係,並不會有特別大影響,除非特殊場景需要調優時需要關注。
資源壓縮
都講到這了,前端構建優化還有一個常用的就是 Gzip 資源壓縮,可以極大減小資源包體積,前端一般構建工具都有插件支持,需要注意的是也需要 nginx 做配置才能生效
http {
gzip_static on;
gzip_proxied any;
}
如果生效了,響應頭裏可以看到 content-encoding: gzip