什么是雙鍵緩存?我們必須了解的瀏覽器緩存新規(guī)則!
Hello,大家好,我是 Sunday。
昨天有位同學(xué)問(wèn)我:“Sunday 老師,為什么我的靜態(tài)資源明明緩存了,但換個(gè)站點(diǎn)訪問(wèn),又得重新下載?”
這個(gè)本質(zhì)上就是因?yàn)?雙鍵緩存(Double-keyed Caching) 導(dǎo)致的。
所以,咱們今天就來(lái)聊聊 雙鍵緩存是什么,它是如何工作的,以及我們應(yīng)該如何優(yōu)化?
什么是雙鍵緩存?
在 傳統(tǒng)的瀏覽器緩存 中,資源的緩存通常是 基于 URL 進(jìn)行存儲(chǔ)的。
比如,當(dāng)我們?cè)L問(wèn) https://cdn.sunday.com/script.js 時(shí),那么瀏覽器會(huì)緩存這個(gè) script.js,當(dāng)其他站點(diǎn)也引用這個(gè) URL 時(shí),瀏覽器直接復(fù)用緩存,不需要重新下載。
這種傳統(tǒng)的緩存方式,就是開(kāi)始同學(xué)所說(shuō)的:資源一旦緩存,任何站點(diǎn)都可以訪問(wèn)
但這樣做有一個(gè)巨大的安全隱患——跨站點(diǎn)追蹤(Cross-site Tracking) 和 數(shù)據(jù)泄露風(fēng)險(xiǎn)。
例如:
- 某些網(wǎng)站可以通過(guò)檢查公共 CDN 資源的緩存狀態(tài),來(lái)推測(cè)用戶是否訪問(wèn)過(guò)某些網(wǎng)站(比如:廣告追蹤)。
- 黑客可以利用緩存投毒(Cache Poisoning)攻擊,讓用戶加載被污染的資源。
為了避免這些安全問(wèn)題,很多瀏覽器(比如:Chrome、Firefox)引入了雙鍵緩存機(jī)制。
雙鍵緩存的核心規(guī)則是:緩存資源時(shí),不僅考慮 URL,還要考慮 資源是在哪個(gè)站點(diǎn)加載的(Origin),也就是 “站點(diǎn) + URL” 作為緩存的唯一標(biāo)識(shí)。
換句話說(shuō):
- 以前 A 站 緩存的資源,B 站 可以直接復(fù)用 ?
- 現(xiàn)在 A 站 緩存的資源,B 站 需要重新下載 ?
雙鍵緩存是如何工作的?
雙鍵緩存 = 站點(diǎn)(Origin)+ 資源 URL
讓我們用一個(gè)例子來(lái)理解:
假設(shè)你訪問(wèn)了 網(wǎng)頁(yè) A 和 網(wǎng)頁(yè) A,它們都使用相同的 CDN 資源 https://cdn.sunday.com/script.js:
- 傳統(tǒng)緩存(單鍵緩存)
你在 網(wǎng)頁(yè) A 加載 script.js,瀏覽器緩存該文件。
你訪問(wèn) 網(wǎng)頁(yè) B,瀏覽器發(fā)現(xiàn)它請(qǐng)求相同的 script.js,于是直接從緩存中加載 (減少了網(wǎng)絡(luò)請(qǐng)求,提高了加載速度)。
- 雙鍵緩存
- 你在 網(wǎng)頁(yè) A 加載 script.js,瀏覽器緩存它,并標(biāo)記為 “僅供 網(wǎng)頁(yè) A 使用”。
- 你訪問(wèn) 網(wǎng)頁(yè) B,即使請(qǐng)求相同的 script.js,瀏覽器也會(huì)認(rèn)為它是 一個(gè)全新的資源,需要重新下載。
不同的站點(diǎn),即使請(qǐng)求相同的資源,仍然需要分別緩存!
這種方式提升了安全性,但是也會(huì)帶來(lái)最初那位同學(xué)的疑惑,就是:資源無(wú)法跨站點(diǎn)共享,必須要重復(fù)下載了。
所以說(shuō):雙鍵緩存雖然帶來(lái)的“一定的”安全性,但是也帶來(lái)了不少的問(wèn)題,比如:
- 緩存復(fù)用率降低:即使是相同的資源,不同站點(diǎn)仍然需要重新下載
- 公共 CDN 失去部分優(yōu)勢(shì):以往我們使用 CDN(如 jsDelivr、UNPKG)是為了讓多個(gè)站點(diǎn)共用緩存,現(xiàn)在效果大大降低。
- 首次訪問(wèn)成本上升:用戶訪問(wèn)某個(gè)站點(diǎn)時(shí),即使本地已經(jīng)緩存了相同的資源,仍然需要重新下載,導(dǎo)致頁(yè)面首次加載變慢。
如何優(yōu)化雙鍵緩存影響?
在上面,咱們已經(jīng)大致了解了雙鍵緩存的原理以及可能會(huì)帶來(lái)的一些影響了,所以最后咱們就來(lái)看看如何盡可能的優(yōu)化這些問(wèn)題:
1. 利用 Service Worker
Service Worker 可以在客戶端攔截請(qǐng)求,并利用 本地緩存 來(lái)減少對(duì)網(wǎng)絡(luò)請(qǐng)求的依賴。
例如,我們可以使用 Cache API 將某些資源手動(dòng)緩存下來(lái),而不受雙鍵緩存的限制:
self.addEventListener("fetch", (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request);
})
);
});
Service Worker 的緩存存儲(chǔ) 不受雙鍵緩存的影響,因此對(duì)于高頻使用的靜態(tài)資源,可以考慮讓 Service Worker 進(jìn)行管理,而不是完全依賴 HTTP 緩存。
2. 使用 HTTP/3,減少重復(fù)請(qǐng)求開(kāi)銷
由于雙鍵緩存的影響,即使同一個(gè)用戶訪問(wèn)不同網(wǎng)站,公共 CDN 資源也可能 多次下載。
但是,如果通過(guò) HTTP/3(QUIC)協(xié)議 通過(guò) 多路復(fù)用 和 0-RTT 連接,可以優(yōu)化對(duì)應(yīng)的性能問(wèn)題。
PS:如何檢查你的 CDN 是否支持 HTTP/3?
可以在 Chrome DevTools 的 Network 面板中,查看 Protocol 列,如果顯示 h3,說(shuō)明該資源使用了 HTTP/3 進(jìn)行傳輸。
3. 預(yù)加載關(guān)鍵資源
既然不能完全依賴瀏覽器緩存,我們可以主動(dòng) 預(yù)加載關(guān)鍵資源。
例如,使用 <link rel="preload"> 來(lái)預(yù)加載字體、腳本或 CSS:
<link rel="preload" href="https://你的 cdn 地址/fonts/Roboto.woff2" as="font" type="font/woff2" crossorigin="anonymous">
這樣可以確保關(guān)鍵資源即使因?yàn)殡p鍵緩存機(jī)制需要重新下載,也能更快地完成加載。