CDN搭建实战指南:从源站回源到边缘节点调度的完整思路
先想清楚:为什么要自己搭一套 CDN
在动手之前,最该回答的问题不是「用什么软件」,而是「为什么要自建」。市面上成熟的商业 CDN 服务已经很便宜,接入也只需要改一条 CNAME。如果只是为了给一个日均几千 UV 的博客加速,自建 CDN 大概会让你花掉比服务器费用更多的时间。
真正适合 CDN 搭建的场景通常有几个特征。一是流量规模足够大,带宽成本在商业 CDN 的阶梯价之外还有明显压缩空间,自建节点后单位带宽成本能降下来。二是业务对缓存规则、回源策略、日志数据有强定制需求,商业 CDN 的配置面板满足不了,比如需要按用户 ID 做分片缓存,或者需要把访问日志实时接入自己的风控系统。三是有合规或数据主权要求,内容必须落在自己可控的机房里。四是已经有分布式的闲置资源,比如各地分支机构、边缘计算盒子,把它们组织起来就是一张小型 CDN。
还有一种折中的路子:核心静态资源走商业 CDN,动态接口和内部系统走自建节点,或者用多云混合的方式把几家 CDN 厂商当节点池来调度。这种思路技术难度低很多,也能省下大量带宽开支,值得优先考虑。
CDN 搭建的整体架构
无论规模大小,一套 CDN 的骨架都是三段:调度层、边缘层、源站层。用户发起请求,先经过调度层拿到一个最优节点的 IP,然后请求打到边缘节点;边缘节点命中缓存就直接返回,没命中就向上回源,回源可能经过一层中间层(也叫父层、二层节点)做收敛,最后才落到源站。
中间层的存在很有必要。假设有 50 个边缘节点,热门资源刚更新时,50 个节点同时回源,源站瞬间要扛住 50 倍的突发流量,这就是所谓的「回源风暴」。加一层中间层后,边缘节点先回中间层,中间层再合并请求去源站,源站看到的并发就降到个位数。
软件选型:别一上来就写调度系统
很多团队一提起 CDN 搭建,第一反应是「自己写一套调度」。实际上调度是最后才需要考虑的部分,先把单节点缓存做稳,比什么都重要。
轻量起步:Nginx 反向代理加缓存
如果只是几十个节点、日请求量在百万级以内,用 Nginx 的 proxy_cache 模块就够了。它的缓存键、过期时间、stale 机制都能配置,性能也足够。一个典型的边缘节点配置大概长这样:
proxy_cache_path /data/cache levels=1:2 keys_zone=edge:200m
max_size=500g inactive=7d use_temp_path=off;
server {
listen 80;
server_name cdn.example.com;
location / {
proxy_cache edge;
proxy_cache_key "$scheme$host$uri$is_args$args";
proxy_cache_valid 200 206 302 10m;
proxy_cache_valid 404 1m;
proxy_cache_use_stale error timeout updating http_500 http_502;
proxy_cache_lock on;
proxy_cache_background_update on;
proxy_set_header Host $proxy_host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_pass https://origin.example.com;
}
}
这里有两个参数值得单独说。proxy_cache_lock on 能保证同一个资源并发未命中时只有一个请求回源,其余请求等待结果,这是防止回源风暴最廉价的手段。proxy_cache_background_update 则让过期资源的更新在后台完成,用户拿到的仍是旧内容,但不会阻塞。
进阶:OpenResty 与 Apache Traffic Server
当业务需要在边缘做逻辑处理——改响应头、鉴权、灰度分流、图片裁剪——OpenResty 会顺手很多。Lua 脚本能在请求的各个阶段介入,配合共享内存字典可以做本地计数、限速、AB 测试。代价是需要自己维护 Lua 代码质量,脚本里的阻塞操作会拖慢整个 worker。
如果节点规模上了百、单机要扛十万级 QPS,可以看看 Apache Traffic Server。它是为纯缓存场景设计的,磁盘 IO 和内存管理比通用 Web 服务器更激进,大文件分片、RAM 盘缓存、HTTP/2 支持都比较完备,缺点是配置和调试门槛偏高,社区资料相对少。
开箱即用:开源 CDN 面板
不想从零拼装的话,有现成的开源方案可以省掉大量脚手架工作,比如 GoEdge 这类项目,提供了节点管理、证书自动签发、调度、统计面板等一整套能力。它们适合中小团队快速验证业务模型,但要评估项目的活跃度和长期维护风险,别把核心业务绑在一个停更的项目上。
源站侧的准备:回源策略决定成败
CDN 搭建的质量,一半取决于边缘,另一半取决于源站配合得好不好。源站需要做几件事。
第一,只允许节点 IP 回源。通过防火墙白名单或者源站前面的网关做限制,否则别人扫到你的源站域名就能绕过 CDN 直接打源,CDN 白搭。同时记得让源站支持 X-Forwarded-For 和 X-Real-IP,否则业务日志里全是节点 IP,排查问题会非常痛苦。
第二,回源协议要稳定。HTTP 回源简单快,HTTPS 回源更安全但会增加握手开销。可行的做法是内网或专线回源走 HTTP,公网回源走 HTTPS 并开启长连接,减少 TLS 握手次数。
第三,源站要能识别缓存语义。正确的做法是在源站响应里带上 Cache-Control、ETag、Last-Modified,让 CDN 遵循这些头决定缓存时长,而不是在边缘硬编码一堆正则规则。规则散落在边缘,后期改起来几乎是灾难。
第四,支持 206 分片和 Range 请求。视频和安装包这类大文件依赖分片下载,如果边缘不支持 Range,大文件每次都要整包回源,带宽会迅速失控。
边缘节点的部署与自动化
节点数量一多,手工登录每台机器改配置就不现实了。容器化是最省事的路径:把缓存软件、配置模板、证书目录打包成镜像,用编排工具统一下发。节点上线时只做三件事——拉取镜像、注入自己的节点标识、注册到调度中心。
磁盘规划同样关键。缓存盘建议用独立 SSD,目录层级按 levels=1:2 分散,避免单目录文件过多导致 inode 查找变慢。容量不要塞满,留出 15% 到 20% 的余量,否则清理线程会频繁触发。对于超热资源,可以额外挂一块内存盘做二级缓存,把最热的那批文件放在内存里,命中延迟能压到亚毫秒。
配置管理上,尽量把「会变的」和「不变的」分开。缓存算法、日志格式、worker 数这类属于变更频率低的,写进镜像;缓存规则、黑白名单、证书这类属于变更频率高的,做成动态下发的配置中心,节点定时拉取并热加载。这样一次规则调整不用重启服务。
调度系统:让用户访问到最近的节点
调度是 CDN 的大脑,也是自建方案里最容易做砸的一环。常见做法有几种。
DNS 调度是最普遍的方式。自建权威 DNS,根据请求来源的递归解析器 IP 判断地理位置,返回最近的节点。问题在于递归解析器可能离用户很远,判断会失准,所以有条件的话要支持 EDNS Client Subnet,拿到用户真实网段再做决策。DNS 还有个天然短板:TTL 缓存导致故障切换慢,所以健康检查必须做在 DNS 之前,节点异常时调度结果要及时剔除。
HTTP 302 调度精度更高,客户端先访问一个调度接口,接口按实时探测结果返回最优节点。代价是多一次往返,而且客户端得配合改造。
Anycast 是把同一个 IP 播报到多个节点,靠 BGP 让网络层自动选路,用户天然就近。它切换快、无需客户端配合,但要求你有 AS 号和 IP 段,门槛高,而且 TCP 会话在路由变化时可能中断。
实际生产里往往是组合拳:DNS 做粗粒度分流,302 或 Anycast 做精细化补充,再叠加一层基于实时延迟探测的动态权重调整。探测数据来自各节点主动上报的到源站 RTT、丢包率、当前负载,调度中心每隔几十秒更新一次权重表。
缓存策略与刷新机制
缓存命中率是 CDN 最核心的指标。提升命中率的手段包括:合理设置缓存键(是否忽略特定查询参数、是否区分设备类型)、对高频变更的资源用版本号而非短 TTL、对动态接口的公共部分做片段缓存。
缓存键设计要克制。把 User-Agent 整个塞进缓存键,命中率会瞬间归零;只取其中真正影响内容的那几个维度,比如移动端和桌面端分开。同理,Vary 响应头也要慎用,它同样会碎片化缓存。
刷新机制分两种。主动刷新是通过 API 提交 URL 列表,调度中心广播到所有节点删除对应缓存,适合内容纠错这类紧急场景。被动刷新是等 TTL 自然过期,成本低但不够及时。建议两者结合:日常靠 TTL,紧急靠刷新接口,同时给刷新接口加上权限校验和频率限制,避免被误调用或者恶意刷空缓存。
刷新还有个坑:删缓存之后紧接着就是回源,如果同一时间删了几万个 URL,源站会瞬间被打爆。稳妥的做法是分批刷新,或者把刷新做成「标记过期」而不是「立即删除」,让节点在后续请求中逐个重建。
HTTPS、安全与合规
HTTPS 已经是标配。边缘节点要支持 TLS 1.3、会话复用、OCSP Stapling,证书最好用 ACME 协议自动签发和续期,并把证书分发到所有节点。泛域名证书能省下大量管理成本,但要注意一个节点上的证书数量过多会拖慢握手,必要时用 SNI 分流。
防护方面,防盗链是最基础的:检查 Referer 白名单,或者用签名 URL,把过期时间、客户端 IP 编进签名里,防止链接被外部站点直接引用。再往上,需要限速限并发、对异常 UA 和频次做拦截、对 CC 攻击做挑战验证。如果你不想自己实现这一整套,可以参考 Cloudflare 这类服务的安全策略设计思路,把其中适合的部分搬到自建体系里。
合规层面别忽略日志。访问日志里包含用户 IP,属于个人信息范畴,保存周期、脱敏方式、访问权限都要有明确规范。另外,如果节点分布在不同地区,跨境传输数据要提前评估法律风险。
监控、压测与灰度
没有监控的 CDN 等于没有 CDN。至少要盯住这些指标:各节点缓存命中率、回源带宽占比、首字节时间、5xx 比例、磁盘使用率、证书剩余有效期。节点探针要从多个地域发起,才能发现区域性故障。
压测要在上线前做完。用真实流量特征去压,而不是简单跑个 ab 命令。重点验证三件事:单节点在峰值 QPS 下的稳定性、缓存全部未命中时的回源能力、以及某个节点被打挂之后调度能否在可接受时间内切走。
灰度发布是最后一道保险。新配置先在一个小节点上跑,观察命中率和错误率没有异常,再逐步放量到全量。缓存软件升级同理,别一次性全推。
几个容易踩的坑
- 忘记处理 Vary 和 Set-Cookie:源站响应带 Set-Cookie 时,如果边缘照单全收并缓存,用户可能拿到别人的会话信息。要么在边缘剥离 Set-Cookie,要么把带 Cookie 的请求直接标记为不缓存。
- 缓存键少了 Host:多个域名共用一套节点时,缓存键里不含 Host,会导致 A 站访问到 B 站的内容,这类事故排查起来非常费劲。
- 压缩与缓存的顺序搞反:对于已经压缩过的内容(比如图片、视频),边缘再做一次 gzip 既浪费 CPU 又可能破坏字节范围,需要在配置里按 MIME 类型排除。
- DNS TTL 设得太长:为了降低解析压力把 TTL 设到几小时,一旦节点故障,用户会在很长时间里持续访问坏节点。
- 忽略时钟同步:签名 URL、证书校验、日志排序都依赖准确时间,节点间时钟漂移会引发一堆莫名其妙的鉴权失败。
写在最后
CDN 搭建没有标准答案。一个只有三台机器的图片站和一个覆盖多地域的视频平台,需要的完全是两套东西。合理的路径是:先用最小可行的单节点缓存跑通全链路,验证命中率和带宽收益,再逐步补上中间层、调度、自动化与安全能力。每一步都拿数据说话,比一开始就照着大厂架构画大饼要靠谱得多。
如果资源有限,也可以先把自建节点当作商业 CDN 的补充,在成本敏感的业务上跑起来,积累经验后再扩大范围。技术选型服务于业务,而不是反过来。





