CDN 是什么:让内容离用户更近
2025-11-22
CDN(内容分发网络)在各地部署缓存节点,用户访问时从最近的节点取内容,从而降低延迟、减轻源站压力。这篇文章我想从原理讲起,再聊缓存头怎么写、更新后怎么刷新、什么时候真的需要它,以及排查问题时看哪里。
为什么需要 CDN
网络延迟有一部分是物理距离决定的:信号在光纤里再快也有上限,距离越远往返时间越长。如果你的服务器在一个城市,远方的用户每个请求都要跨越长长的链路,还要经过许多个路由器。可以拿连锁超市来类比:与其让所有人都去总仓库取货,不如在各个城市开分店,常用商品提前备在分店里,顾客就近购买;分店没有的货,再从总仓库调。CDN 的节点就是“分店”,你的源站就是“总仓库”。
它带来的好处主要有几点:降低访问延迟,提升加载速度;分担源站的带宽和请求压力,源站只需要应对“缓存未命中”的那部分流量;流量高峰时更稳;并且可以在边缘做一些 HTTPS 终结、压缩、防护之类的工作。
工作方式
- 域名解析到 CDN 的节点(通常用 CNAME)。
- 节点收到请求,命中缓存就直接返回。
- 没有命中就回源到你的服务器取内容,并按规则缓存。
展开说一下第一步。假设你的站点是 www.example.com,在 CDN 服务商那里添加加速域名后,服务商会给你一个专属的 CNAME 目标,你需要在自己的 DNS 里把 www.example.com 指向它。之后用户查询这个域名时,CDN 的调度系统(通常基于 DNS 或任播等机制)会根据用户所在位置、节点负载等因素,返回一个合适的边缘节点的 IP。DNS 的基础可以参考我的 DNS 那篇。可以用下面的命令观察:
dig www.example.com dig +short CNAME www.example.com
如果返回了一个指向服务商域名的 CNAME,再往下解析到若干 IP,就说明接入已经生效。要注意:根域名(例如 example.com 本身)按 DNS 规范不能直接设置 CNAME,因此常见做法是让 www 子域名走 CDN,或者使用服务商支持的 ALIAS/ANAME 类似功能,具体以服务商文档为准。
缓存命中与回源
“命中”(HIT)是指节点本地已经有一份未过期的副本,直接返回;“未命中”(MISS)则要回源。回源时节点相当于一个客户端,向你的源站发出普通的 HTTP 请求,拿到响应后一边返回给用户,一边按规则存下来,下一个请求就能命中了。衡量 CDN 效果的一个关键指标是缓存命中率:命中率越高,源站越轻松,用户也越快。
还有一种“分层缓存”的做法,边缘节点没命中时先问上一层的中间缓存节点,而不是都直接打到源站,进一步减少回源。具体是否提供、怎么配置,各家产品不同,需要看对应文档,这里不做展开。
适合缓存什么
- 图片、CSS、JS、字体等静态资源。
- 文件名带哈希的资源可以设置很长的缓存时间。
- 个性化、需要登录的内容一般不缓存。
文件名带哈希,指的是像 app.3f9c1b2.js 这样,内容变了文件名就跟着变。既然每个版本都是不同的 URL,旧版本永远不会被误用,因此可以放心地设置一年的缓存。HTML 页面本身则不适合缓存太久,因为它引用着那些带哈希的资源,页面一旦缓存了旧版,就会一直指向旧文件。常见的搭配是:HTML 短缓存或需要验证,静态资源长缓存。
用 Cache-Control 控制缓存
缓存行为主要由源站响应头里的 Cache-Control 决定。几个常见写法:
Cache-Control: public, max-age=31536000, immutable Cache-Control: max-age=0, must-revalidate Cache-Control: no-store Cache-Control: private, max-age=600 Cache-Control: public, max-age=60, s-maxage=3600
- max-age:资源在多少秒内被认为是新鲜的,可以直接使用。
- s-maxage:只对共享缓存(比如 CDN)生效,优先级高于 max-age,可以让 CDN 缓存得比浏览器更久。
- public 与 private:public 允许 CDN 等共享缓存存储;private 表示只能由用户自己的浏览器缓存,不应由共享缓存存储。
- no-cache:可以存储,但每次使用前必须向源站验证;no-store:完全不允许存储。
- immutable:告诉浏览器在新鲜期内资源不会改变,刷新页面时也不必再去验证。
- must-revalidate:一旦过期,必须验证后才能再使用。
验证用到的是 ETag 与 Last-Modified,配合条件请求,资源没变化时源站回 304,不用重传内容,细节在 HTTP 那篇里有讲。另外要留意,如果响应里带了 Set-Cookie,或者请求带有认证信息,很多 CDN 默认不会缓存,或者需要你显式配置,所以别指望随便什么响应都能被缓存。而且各家 CDN 往往允许在控制台里覆盖源站的缓存头,规则可能和源站的设置叠加,出问题时两边都要检查。
还有一个值得注意的响应头是 Vary。它告诉缓存:同一个 URL 的响应可能随某些请求头不同而不同,比如 Vary: Accept-Encoding 表示压缩与否会产生不同的副本。设置不当会造成缓存碎片,也就是命中率下降。
缓存键与查询参数
CDN 用“缓存键”来判断两个请求是不是同一个资源,默认通常包含域名和路径,是否包含查询字符串则取决于配置。如果忽略了不该忽略的参数,用户可能拿到别人的内容;如果把每次都不同的追踪参数(比如时间戳)算进缓存键,又会导致永远不命中。配置时要想清楚:哪些参数真正影响内容,就纳入缓存键,其余的忽略。
如何刷新缓存
更新内容后,旧内容可能还在节点里没过期。办法有这么几种:
- 在控制台手动刷新(清除)指定的 URL 或目录,让节点丢弃旧副本。
- 预热:把重要资源提前拉到节点上,避免第一批用户冷启动。
- 使用版本化的文件名或带版本号的查询参数(前提是缓存键包含该参数),从根本上绕开刷新。
- 设置合适的 TTL,容忍一定时间内的不一致。
在我看来最稳妥的方式就是版本化文件名,因为刷新操作有延迟、有配额,还可能因为操作失误漏掉某个路径。另外,浏览器本地也有缓存,CDN 刷新了不代表用户浏览器里的旧版会立刻消失,这点经常被忽略。
需要注意
- 用于面向中国大陆用户的加速时,通常需要域名完成备案,并选择有资质的服务商。
- 更新内容后要刷新缓存,或使用版本化的文件名。
- CDN 也能隐藏源站 IP、抵御一部分攻击,但不是万能的。
关于备案,需要多说两句。如果你的站点面向中国大陆提供服务,一般要先在工信部门的系统里完成 ICP 备案(网站底部常见的那一串“ICP备”编号即是),并且使用在大陆开展业务、具备相应资质的 CDN 服务商。具体的流程、所需材料和要求,请以主管部门以及你所选服务商的官方说明为准,我这里不做保证性的描述。跨境的情况(用户在境外、源站在境内或相反)又是另一回事,同样需要看服务商的条款和相关规定。
关于“隐藏源站”:CDN 确实让外部看到的是节点 IP 而不是源站 IP,但如果源站地址通过其他途径泄漏(比如历史 DNS 记录、邮件头、同一服务器上的其他域名),攻击者仍可能直连源站。更稳妥的做法是在源站防火墙上只放行 CDN 的回源网段,具体网段以服务商公布的为准,防火墙的思路见 防火墙那篇。
HTTPS 与 CDN
当站点启用 HTTPS 时,浏览器与 CDN 节点之间会建立一条 TLS 连接,因此 CDN 上需要配置你域名的证书。CDN 到源站这一段(回源)可以是 HTTP 或 HTTPS,建议用 HTTPS,并校验源站证书,否则节点到源站之间就是明文传输。要避免出现“浏览器到 CDN 用 HTTPS,CDN 到源站用 HTTP,源站又强制跳转 HTTPS”这样的配置,它会造成无限重定向。证书和 TLS 的基础可以看 HTTPS 那篇。
怎么判断有没有命中
最直接的方法是看响应头。各家服务商会添加自己的头部来标示缓存状态(名称不统一,比如有的叫 X-Cache,有的叫 CF-Cache-Status 一类,请以实际服务商为准),此外还有标准的 Age 头,表示这份响应已经在缓存里待了多少秒。
curl -I https://www.example.com/static/app.3f9c1b2.js curl -s -o /dev/null -D - https://www.example.com/static/app.3f9c1b2.js curl -I --resolve www.example.com:443:203.0.113.20 https://www.example.com/ dig +short www.example.com traceroute www.example.com
连续请求同一个资源两次,第二次出现了 Age 或者命中的标识,说明缓存生效了。--resolve 可以强制指定某个节点(这里 203.0.113.20 是示例地址),用于对比不同节点的表现,或者直连源站对比内容是否一致。用 traceroute 则可以看到到达节点的大致路径与时延。
常见问题排查
- 更新了文件但用户看到旧版:先查是浏览器缓存、CDN 缓存,还是源站本身没更新。用
curl -I同时看 Cache-Control、Age 与 Last-Modified。 - 命中率很低:检查缓存键是否带了变化的参数,是否响应里有 Set-Cookie,是否 Cache-Control 写成了 no-store 或 private,Vary 是否过于宽泛。
- 回源压力突然变大:可能是批量刷新缓存后大量请求同时回源,或者热点资源过期。可以错峰刷新、合理设置 TTL。
- 个别地区访问慢:节点调度问题,或者该地区的网络线路本身有问题,可以对比不同地区的测试结果并反馈给服务商。
- 出现 502/504:CDN 节点联系不上源站,或者源站响应超时,检查源站健康状况与回源超时设置。
- 把登录后的页面缓存了:这是严重事故,用户可能看到别人的内容。个性化页面务必使用 private 或 no-store,并确认 CDN 的规则没有覆盖它。
我自己的建议
个人静态博客流量不大,起步阶段不一定需要 CDN。真正需要的时候通常是:访问者分布很广、延迟明显影响体验;有大体积的静态文件(图片、视频、下载包);源站带宽有限、被流量压得喘不过气;或者需要边缘层提供额外的防护。引入 CDN 同时也引入了新的复杂度:缓存一致性、刷新操作、额外的配置、排查链路多了一环。先把缓存头写对、静态资源压缩好、图片体积控制好,往往已经能获得不小的提升,然后再根据实际情况决定是否上 CDN。
总结
CDN 的本质是把内容缓存到离用户更近的节点:通过 CNAME 与调度把用户引到边缘,命中则直接返回,未命中则回源并缓存。用好它的关键在于 Cache-Control 的正确设置、静态资源的版本化命名、合理的缓存键,以及对个性化内容的谨慎处理。面向中国大陆用户时要留意备案与服务商资质等合规要求,具体以官方规定为准。最后,别为了用而用,先看看自己是否真的需要。