Hello,大家好,我是 Sunday。
昨天有位同学问我:“Sunday 老师,为什么我的静态资源明明缓存了,但换个站点访问,又得重新下载?”
这个本质上就是因为 双键缓存(Double-keyed Caching) 导致的。
所以,咱们今天就来聊聊 双键缓存是什么,它是如何工作的,以及我们应该如何优化?
什么是双键缓存?
在 传统的浏览器缓存 中,资源的缓存通常是 基于 URL 进行存储的。
比如,当我们访问 https://cdn.sunday.com/script.js 时,那么浏览器会缓存这个 script.js,当其他站点也引用这个 URL 时,浏览器直接复用缓存,不需要重新下载。
这种传统的缓存方式,就是开始同学所说的:资源一旦缓存,任何站点都可以访问
但这样做有一个巨大的安全隐患——跨站点追踪(Cross-site Tracking) 和 数据泄露风险。
例如:
- 某些网站可以通过检查公共 CDN 资源的缓存状态,来推测用户是否访问过某些网站(比如:广告追踪)。
- 黑客可以利用缓存投毒(Cache Poisoning)攻击,让用户加载被污染的资源。
为了避免这些安全问题,很多浏览器(比如:Chrome、Firefox)引入了双键缓存机制。
双键缓存的核心规则是:缓存资源时,不仅考虑 URL,还要考虑 资源是在哪个站点加载的(Origin),也就是 “站点 + URL” 作为缓存的唯一标识。
换句话说:
- 以前 A 站 缓存的资源,B 站 可以直接复用 ✅
- 现在 A 站 缓存的资源,B 站 需要重新下载 ❌
双键缓存是如何工作的?
双键缓存 = 站点(Origin)+ 资源 URL
让我们用一个例子来理解:
假设你访问了 网页 A 和 网页 A,它们都使用相同的 CDN 资源 https://cdn.sunday.com/script.js:
- 传统缓存(单键缓存)
你在 网页 A 加载 script.js,浏览器缓存该文件。
你访问 网页 B,浏览器发现它请求相同的 script.js,于是直接从缓存中加载 (减少了网络请求,提高了加载速度)。
- 双键缓存
- 你在 网页 A 加载 script.js,浏览器缓存它,并标记为 “仅供 网页 A 使用”。
- 你访问 网页 B,即使请求相同的 script.js,浏览器也会认为它是 一个全新的资源,需要重新下载。
不同的站点,即使请求相同的资源,仍然需要分别缓存!
这种方式提升了安全性,但是也会带来最初那位同学的疑惑,就是:资源无法跨站点共享,必须要重复下载了。
所以说:双键缓存虽然带来的“一定的”安全性,但是也带来了不少的问题,比如:
- 缓存复用率降低:即使是相同的资源,不同站点仍然需要重新下载
- 公共 CDN 失去部分优势:以往我们使用 CDN(如 jsDelivr、UNPKG)是为了让多个站点共用缓存,现在效果大大降低。
- 首次访问成本上升:用户访问某个站点时,即使本地已经缓存了相同的资源,仍然需要重新下载,导致页面首次加载变慢。
如何优化双键缓存影响?
在上面,咱们已经大致了解了双键缓存的原理以及可能会带来的一些影响了,所以最后咱们就来看看如何尽可能的优化这些问题:
1. 利用 Service Worker
Service Worker 可以在客户端拦截请求,并利用 本地缓存 来减少对网络请求的依赖。
例如,我们可以使用 Cache API 将某些资源手动缓存下来,而不受双键缓存的限制:
self.addEventListener("fetch", (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request);
})
);
});
Service Worker 的缓存存储 不受双键缓存的影响,因此对于高频使用的静态资源,可以考虑让 Service Worker 进行管理,而不是完全依赖 HTTP 缓存。
2. 使用 HTTP/3,减少重复请求开销
由于双键缓存的影响,即使同一个用户访问不同网站,公共 CDN 资源也可能 多次下载。
但是,如果通过 HTTP/3(QUIC)协议 通过 多路复用 和 0-RTT 连接,可以优化对应的性能问题。
PS:如何检查你的 CDN 是否支持 HTTP/3?
可以在 Chrome DevTools 的 Network 面板中,查看 Protocol 列,如果显示 h3,说明该资源使用了 HTTP/3 进行传输。
3. 预加载关键资源
既然不能完全依赖浏览器缓存,我们可以主动 预加载关键资源。
例如,使用 <link rel="preload"> 来预加载字体、脚本或 CSS:
<link rel="preload" href="https://你的 cdn 地址/fonts/Roboto.woff2" as="font" type="font/woff2" crossorigin="anonymous">
这样可以确保关键资源即使因为双键缓存机制需要重新下载,也能更快地完成加载。