做前端的同学基本都听过这两个词:浏览器缓存、CDN 缓存。面试也爱问,平时排查线上问题(比如”我明明发布了新版本,为什么用户还是看到旧页面”)也经常会撞上它们。
但很多人对这两者的理解停留在”反正都是缓存,能让网页加载快点”,具体谁负责什么、出了问题该查哪个,往往说不清楚。今天就把这两个概念彻底捋顺。
先说一个类比
想象你在网上买了一本书。
- 浏览器缓存,相当于你自己家里的书架。你买过、看过的书,直接放在自己书架上,下次想看,伸手就拿,根本不用出门。
- CDN 缓存,相当于你家小区门口新开的一家书店分店。这家分店本身不出版书,但是会从出版社(也就是源站)批发一批热门书屯在店里。你去买的时候,不用跑到几百公里外的出版社仓库,在小区门口就能买到,速度快很多。
浏览器缓存服务的是”你自己”,只帮你一个人省事;CDN 缓存服务的是”你家附近所有人”,帮一片区域的用户都省事。这就是两者最本质的区别——缓存发生的位置和服务的对象完全不同。
下面展开讲。
浏览器缓存:存在你自己电脑里
浏览器缓存,顾名思义,缓存的内容就存在用户自己的设备上(硬盘或者内存里)。当你访问一个网站,浏览器会把图片、CSS、JS 这些静态资源下载下来,同时按照服务器返回的规则,把它们存一份在本地。下次你(只有你)再访问同一个页面,浏览器一看:”咦,这个资源我本地有还没过期”,直接从本地读取,连网络请求都不用发。
浏览器缓存主要靠 HTTP 响应头来控制,核心是这么几个:
Cache-Control:告诉浏览器这个资源能缓存多久、能不能缓存。比如max-age=31536000就是说”这玩意儿一年内都别再问我要新的了,你本地存的就是对的”。Expires:老一点的写法,指定一个绝对过期时间,作用和max-age类似,现在基本被Cache-Control取代。ETag/Last-Modified:这两个是用来做”协商缓存”的。就是缓存到期之后,浏览器不敢直接用本地版本,就带着这个标记去问服务器:”我这份还是最新的吗?”如果服务器说”没变”,就返回一个 304 状态码,浏览器就继续用本地缓存,连文件内容都不用重新传,只是确认了一下”没过期”。
所以浏览器缓存其实分两种状态:强缓存(时间没到,直接用本地的,连请求都不发)和协商缓存(时间到了,但去问一下服务器有没有更新,服务器说没更新就还是用本地的)。
有个特点要注意:浏览器缓存是私有的,只属于当前这台设备、这个浏览器。你换台电脑、换个浏览器,甚至同一浏览器换个用户配置,缓存全部清零,得重新下载。这也是为什么它在 HTTP 里经常被标记为 private——这份缓存只许你自己用,不能被别人(比如你和同事共用的公司代理服务器)拿去复用。
CDN 缓存:存在离用户更近的服务器上
CDN,全称内容分发网络(Content Delivery Network)。它的缓存节点不在用户的电脑上,而是分布在全国甚至全球各地的机房里,通常离用户的物理距离比”源站”(也就是网站真正的服务器)近得多。
工作原理大概是这样:你的网站资源本来只放在一台(或几台)源站服务器上,比如在北京。如果所有用户的请求都跑到北京这台服务器去要数据,广州、成都、深圳的用户都要绕一大圈,延迟高不说,服务器压力也大。
于是就在全国各地部署很多台 CDN 节点服务器,第一次有广州用户访问时,广州节点发现自己没有这个资源,就去北京源站拉一份回来,存在自己这里,同时把内容返回给用户。后面广州地区其他用户再访问同一个资源,广州节点直接把本地存的这份发出去,根本不用再跑一趟北京,这就叫回源只发生了一次,后续都是”就近服务”。
CDN 缓存有几个和浏览器缓存很不一样的地方:
- 服务对象是一整片区域的所有用户,而不是单个用户。同一个 CDN 节点上的缓存,广州的张三、李四、王五访问,用的是同一份缓存副本。这在 HTTP 语义里对应的是
public缓存——大家都能用,不区分身份。 - 控制权在网站运营方手里,用户完全感知不到、也无法直接操作 CDN 缓存。你没法像清浏览器缓存那样,点一下按钮去清空 CDN 上的缓存,只能通过后台管理系统发起”刷新缓存”或者”预热”的请求。
- CDN 除了缓存资源,往往还承担了 负载均衡、防攻击(比如抗 DDoS)、就近接入 等一堆职责,缓存只是它众多功能里的一个,但确实是最常被普通开发者接触到的一个。
两者最核心的几点区别,列个表
| 维度 | 浏览器缓存 | CDN 缓存 |
|---|---|---|
| 存储位置 | 用户本地设备 | 分布在各地的 CDN 节点服务器 |
| 服务对象 | 单个用户(当前浏览器) | 该节点覆盖区域内的所有用户 |
| 控制方 | 用户可以主动清除 | 由网站运营方通过后台控制、刷新 |
| 典型 HTTP 标识 | private |
public |
| 生效范围 | 只影响”我”这一台设备 | 影响整个区域甚至全国用户 |
| 更新方式 | 等过期,或用户手动清缓存 | 主动刷新/预热,或等 TTL 到期 |
| 主要目的 | 省掉网络请求,加快本地二次访问 | 缩短用户和资源之间的物理距离,分摊源站压力 |
请求发生时,两者到底谁先谁后
理解顺序其实很关键,能帮你排查真实问题。一个典型的资源请求流程是这样的:
- 浏览器先看自己本地有没有这个资源的强缓存,没过期的话直接用,请求到此结束,网络请求都不发。
- 如果本地缓存过期了或者没有,浏览器才真正发出网络请求。
- 这个请求会先打到离用户最近的 CDN 节点(而不是直接打到源站)。
- CDN 节点如果自己存了这份资源且没过期,直接把内容返回给用户,这个过程完全不用麻烦源站。
- 如果 CDN 节点上也没有或者过期了,CDN 才会去回源——找网站真正的源站服务器要最新内容,拿到之后自己存一份,再返回给用户。
所以完整链路是:浏览器缓存 → CDN 缓存 → 源站,一层一层往后兜底,只有前面这层没命中,才会麻烦到下一层。这也是为什么”就近多级缓存”能大幅减轻源站压力——绝大多数请求根本不会真正到达源站。
一个常见的排查场景
回到开头提的那个问题:”我发布了新版本,为什么用户还是看到旧页面?”
现在你应该能猜到,问题可能出在两个地方,得分开排查:
- 如果是浏览器缓存没更新,用户强制刷新(Ctrl+Shift+R)能立刻看到新版本,因为强制刷新会跳过本地强缓存直接发请求。
- 如果强制刷新也没用,那大概率是 CDN 缓存 还没更新——CDN 节点上存的还是旧文件,得等 CDN 缓存的有效期(TTL)到期自动更新,或者手动在 CDN 后台触发一次”缓存刷新”,让节点重新回源拉取最新内容。
实际项目里,前端工程化经常会用给静态资源文件名加哈希值的办法(比如 app.a1b2c3.js)来彻底绕开这个麻烦:文件内容一变,文件名就变,新文件名对浏览器和 CDN 来说都是”从没见过的新资源”,自然不会有旧缓存的问题,也就不用纠结到底该等谁过期了。
小结
一句话概括区别:浏览器缓存离用户最近,只服务一个人;CDN 缓存离用户较近,服务一整片区域的人。两者不是互相替代的关系,而是配合工作的两层缓存机制,共同的目标都是让用户少等一会儿、让源站少扛一点压力。搞清楚这个层级关系,日常排查缓存类问题时就能少走很多弯路。