CDN 技术体系:从分发原理到工程落地
为什么需要 CDN:延迟的物理极限
光在真空中的速度约 30 万公里/秒,在光纤中约 20 万公里/秒。从北京到洛杉矶的光纤距离约 1.7 万公里,单程传输延迟就有 85ms。算上 TCP 三次握手、TLS 协商、服务器处理,一次 HTTPS 请求轻松超过 300ms。如果页面包含 50 个静态资源,串行加载就是 15 秒 —— 这还是理想情况,实际还有 BGP 路由绕行、运营商互联瓶颈、跨境流量管控等因素叠加。
CDN(Content Delivery Network)的核心思路就是四个字:就近服务。把内容副本分散到全球各地的边缘节点,让用户从物理距离最近、网络质量最好的节点获取内容,把跨洲际的 300ms 压缩到同城的 5-20ms。
但 CDN 绝不只是"部署一堆缓存服务器"这么简单。它本质上是一套分布式内容分发系统,需要解决调度、缓存、一致性、回源、安全等一系列工程问题。这篇文章会从架构到落地,系统性地拆解 CDN 的技术体系。
一句话总结:CDN 的价值不仅是降低延迟,而是通过将流量卸载到边缘来保护源站、提升可用性、降低带宽成本。
核心架构:三层分发体系
现代 CDN 普遍采用边缘-中间层-源站的三层架构。理解这三层的分工和协作关系,是理解 CDN 所有技术决策的基础。
L1 边缘层(Edge PoP)
边缘节点是直接面向用户的第一道缓存。它的核心目标是最大化缓存命中率,尽可能在本地响应请求而不回源。
典型的 Edge PoP 部署在各大城市的 IDC 或运营商机房中,节点数量从几十到几千不等。每个节点内部通常由 Nginx/Traffic Server 等反向代理软件驱动,配合 SSD + 内存的两级本地缓存。
边缘节点的设计取舍在于:覆盖广度 vs 单节点缓存深度。节点越多,用户就近命中的概率越高,但单个节点分到的流量就越少,缓存命中率反而下降(因为缓存需要流量来"养热")。这就是为什么需要中间层。
L2 中间层(Mid-Tier / Shield)
中间层是边缘和源站之间的聚合缓存层,也叫 Origin Shield。它的核心价值是收敛回源流量。
假设有 200 个边缘节点,每个节点对同一个资源的首次请求都要回源。没有中间层时,源站要承受 200 次回源请求。加入 4 个区域中间层后,边缘节点先回源到最近的中间层,中间层聚合后只需 4 次(甚至经过 Request Collapsing 后仅 1 次)回源到源站。回源 QPS 降低了两个数量级。
中间层通常部署在核心城市(如北京、上海、广州),拥有比边缘节点更大的缓存容量(TB 级 SSD),因为它需要覆盖一个区域内所有边缘节点的缓存 MISS 流量。
源站(Origin)
源站是内容的权威来源,可以是对象存储(如 S3、OSS)、应用服务器、或者其他 CDN。源站设计的核心原则是:CDN 应该为源站挡住绝大部分流量,源站只需要处理缓存 MISS 的长尾请求。
在实际架构中,源站通常配置多活部署和故障切换,CDN 侧配置主备源站地址。当主源站不可达时,CDN 自动切换到备源站,这是 CDN 提升系统可用性的重要机制。
一句话总结:三层架构的本质是一个流量漏斗 —— 从用户到边缘到中间层到源站,每一层过滤掉大部分请求,最终只有极少数 MISS 请求到达源站。
请求调度机制:把用户引导到最优节点
CDN 的调度系统决定了用户的请求会被路由到哪个边缘节点。调度质量直接影响用户体验 —— 选错节点意味着更高的延迟和更低的命中率。
DNS 调度
DNS 调度是最主流的 CDN 调度方式。工作原理是:业务域名通过 CNAME 指向 CDN 厂商的调度域名,CDN 的 GSLB(Global Server Load Balancing)系统在 DNS 解析阶段返回最优边缘节点的 IP。
| 步骤 | 动作 |
|---|---|
| 1 | 用户请求 cdn.example.com |
| 2 | 本地 DNS 递归解析 |
| 3 | 发现 CNAME:cdn.example.com → cdn.provider.net |
| 4 | 请求 CDN 权威 DNS |
| 5 | GSLB 根据用户 IP、节点负载、健康状态返回最优节点 IP |
| 6 | 用户直连该边缘节点 |
GSLB 的调度决策综合多个维度:
| 决策因子 | 说明 | 数据来源 |
|---|---|---|
| 地理就近 | 用户 IP 对应的地理位置与节点的物理距离 | IP 地理库(MaxMind、淘宝 IP 库) |
| 运营商匹配 | 同运营商访问,避免跨网互联瓶颈 | IP 运营商库 |
| 节点负载 | 节点当前 CPU、带宽、连接数 | 实时监控上报(秒级) |
| 健康状态 | 节点是否存活、丢包率、响应时间 | 主动探测 + 被动检测 |
| 成本权重 | 不同节点的带宽单价不同 | 计费系统 |
DNS 调度的关键局限是精度问题。GSLB 看到的是 Local DNS 的 IP 而不是用户的真实 IP,如果用户使用了公共 DNS(如 8.8.8.8),GSLB 会误判用户位置。EDNS Client Subnet(ECS)扩展协议通过在 DNS 请求中携带用户 IP 的前缀来缓解这个问题,但并非所有 DNS 链路都支持。
HTTP 302 调度
HTTP 302 调度是 DNS 调度的补充。用户先请求一个调度中心,调度中心返回 302 重定向到最优节点。它的优势是调度精度更高(可以拿到用户真实 IP),且可以在 HTTP 层做更复杂的调度逻辑(基于 URL、Cookie、Header 等)。缺点是多一次 HTTP 往返,增加首请求延迟。
在实践中,302 调度常用于大文件下载和视频点播场景,这类场景下一次额外重定向的成本相对于整个传输时间可以忽略不计。
Anycast 调度
Anycast 是在 BGP 层面做调度:多个物理节点共享同一个 IP 地址,路由系统自动将流量导向最近的节点。Cloudflare 和 Google CDN 大量使用 Anycast。
Anycast 的优势是无需 DNS 解析延迟、天然抗 DDoS(攻击流量被分散到所有节点),但对网络运维能力要求极高,需要自己管理 BGP 对等关系。
三种调度方式对比
| 维度 | DNS 调度 | HTTP 302 调度 | Anycast |
|---|---|---|---|
| 调度精度 | 中(依赖 LDNS IP) | 高(用户真实 IP) | 中(BGP 路由决策) |
| 首请求延迟 | 低 | 高(多一次 RTT) | 最低 |
| 调度灵活性 | DNS TTL 限制切换速度 | 实时切换 | 依赖 BGP 收敛 |
| 实现复杂度 | 中 | 低 | 高(需 ASN/BGP 运维) |
| 典型使用方 | 阿里云 CDN、腾讯云 CDN | 网宿、部分视频 CDN | Cloudflare、Fastly |
一句话总结:DNS 调度是主流方案,302 调度是精度补充,Anycast 是网络层原生方案。大多数商业 CDN 采用 DNS 为主、302 为辅的混合调度策略。
缓存体系设计:命中率就是生命线
CDN 的核心价值通过缓存命中率来体现。命中率每提升 1%,意味着 1% 的流量不再回源,直接减少源站压力和带宽成本。商业 CDN 的静态资源命中率通常在 95% 以上,达到这个水平需要在缓存键、分层策略和一致性机制上做精细设计。
缓存键(Cache Key)设计
缓存键决定了"什么条件下两个请求可以共享同一个缓存响应"。设计不当会导致两个极端:缓存键太粗,不同内容共享同一缓存导致错误响应;缓存键太细,同一内容被存储多份导致命中率暴跌。
默认的缓存键通常是 {协议}://{Host}{Path}{Query String},但实际需要根据业务场景定制:
# Nginx 缓存键配置示例
# 默认:包含完整 URL
proxy_cache_key "$scheme$host$request_uri";
# 优化:忽略无关 Query 参数(如追踪参数)
# 将 ?utm_source=xxx&id=123 归一化为只按 id 缓存
proxy_cache_key "$scheme$host$uri$arg_id";
# 多设备适配:加入设备类型
proxy_cache_key "$scheme$host$request_uri$http_x_device_type";
常见的缓存键设计陷阱:
| 问题 | 表现 | 解决方案 |
|---|---|---|
| Query 参数顺序不同 | ?a=1&b=2 和 ?b=2&a=1 被当作不同资源 |
对 Query 参数排序后再计算缓存键 |
| 无关参数污染 | ?utm_source=xxx 导致同一资源产生大量缓存副本 |
配置忽略特定 Query 参数 |
| Vary Header 过宽 | Vary: User-Agent 导致每种浏览器一份缓存 |
将 User-Agent 归一化为设备类型(mobile/desktop) |
| Cookie 泄漏到缓存键 | 每个用户一份缓存副本 | 明确区分可缓存/不可缓存请求 |
分层缓存策略
缓存不是一个单一的层级,而是从热到冷的多级结构:
| 缓存层 | 介质 | 容量 | 延迟 | 适用内容 |
|---|---|---|---|---|
| L0 内存缓存 | RAM | GB 级 | μs | 热点资源(首页图片、热门视频首帧) |
| L1 SSD 缓存 | NVMe SSD | TB 级 | ms | 温数据(近期访问的中频资源) |
| L2 中间层缓存 | 大容量 SSD | 数十 TB | ms + 网络延迟 | 区域热点汇聚 |
| L3 源站 | 对象存储/数据库 | PB 级 | 数十 ms + 网络延迟 | 全量数据 |
缓存淘汰算法也需要针对 CDN 场景优化。LRU 在面对扫描型流量(如爬虫)时容易被污染,大量低频资源把热点资源挤出缓存。实践中更常用 LFU 变体或 2Q/ARC 算法,通过访问频率来抵抗缓存污染。
缓存一致性机制
当源站内容更新时,CDN 上的旧缓存需要被及时清除或刷新。这是 CDN 最核心的技术挑战之一,后文会在技术难点章节深入展开。这里先概述三种主要机制:
TTL 过期:通过 Cache-Control: max-age=3600 或 Expires Header 设置缓存过期时间。简单可靠,但存在 TTL 窗口内的不一致。适用于对一致性要求不高的静态资源(CSS/JS/图片)。
主动 Purge:源站内容更新后,通过 CDN 的 Purge API 主动清除指定 URL 或目录的缓存。大多数 CDN 支持 URL 级别和目录级别的 Purge,全网生效时间从秒级到分钟级不等。
版本化 URL:将文件指纹(Content Hash)嵌入 URL,如 app.a3b2c1.js。文件内容变更时 URL 随之变化,浏览器和 CDN 都会请求新 URL,完全绕过缓存一致性问题。这是前端静态资源最推荐的做法,配合 Cache-Control: immutable 可以将缓存时间设为一年。
# 版本化 URL 示例
# 旧版本:/static/app.a3b2c1.js → Cache-Control: max-age=31536000, immutable
# 新版本:/static/app.d4e5f6.js → 全新 URL,无需 Purge
# 入口 HTML:/index.html → Cache-Control: no-cache(每次校验)
一句话总结:缓存键决定缓存粒度,分层策略决定缓存效率,一致性机制决定数据新鲜度 —— 三者共同决定了 CDN 的缓存命中率。
回源策略与容灾
当缓存未命中时,CDN 需要从上层获取内容。回源看似简单,但在高并发和故障场景下,回源策略的设计直接关系到源站的存活。
回源路径优化
CDN 厂商在全球部署了专用的骨干网络(如阿里云的 Global Backbone、Cloudflare 的 Argo Smart Routing),边缘节点到源站的回源不走公网,而是通过优化过的专线或隧道传输。这解决了公网路由不稳定、跨境丢包高的问题。
回源路径优化通常包含三个层面:智能路由选择(实时测速选择最快路径)、TCP 优化(连接复用、拥塞算法调优)、协议优化(回源使用 HTTP/2 多路复用减少连接数)。
回源合并(Request Collapsing)
当多个用户同时请求同一个缓存 MISS 的资源时,如果每个请求都独立回源,源站会承受巨大的并发压力。Request Collapsing(也叫 Coalescing)机制将并发的相同请求合并为一次回源,其余请求等待首个回源完成后共享响应。
# Request Collapsing 伪代码
class CacheNode:
def __init__(self):
self.cache = {}
self.pending = {} # key -> Future
async def get(self, key):
# 1. 缓存命中,直接返回
if key in self.cache:
return self.cache[key]
# 2. 已有回源请求在飞,挂起等待
if key in self.pending:
return await self.pending[key]
# 3. 首个 MISS 请求,发起回源
future = asyncio.Future()
self.pending[key] = future
try:
result = await self.fetch_from_origin(key)
self.cache[key] = result
future.set_result(result)
return result
finally:
del self.pending[key]
Request Collapsing 在突发热点场景下效果显著(如热搜事件导致某个页面瞬间涌入大量请求),但也有陷阱:如果回源失败,所有等待的请求都会失败。实现时需要考虑超时控制、失败后部分重试等容错逻辑。
多源站容灾
生产环境中源站应配置为多活或主备模式:
# CDN 回源配置示例(概念性)
origin:
primary:
- host: origin-a.example.com
weight: 70
health_check:
interval: 10s
threshold: 3 # 连续 3 次失败标记不可用
- host: origin-b.example.com
weight: 30
fallback:
- host: backup-origin.example.com
trigger: all_primary_down
retry:
max_attempts: 2
retry_on: [502, 503, 504, timeout]
backoff: 100ms
容灾策略的关键设计点包括:健康检查频率(太频繁增加源站负担,太慢故障切换不及时)、故障判定阈值(单次超时 vs 连续失败)、回切策略(源站恢复后如何平滑切回)。
一句话总结:回源是 CDN 的"最后一公里",路径优化减少延迟,Request Collapsing 保护源站,多源站容灾保障可用性。
动态内容加速
CDN 最初是为静态内容设计的,但现代 Web 应用中动态内容(API 响应、个性化页面、实时数据)占比越来越高。动态内容无法通过传统缓存加速,CDN 转而从网络传输层面优化动态请求的性能。
动态路由优化
公网路由并非最短路径,BGP 协议优先考虑的是商业关系而非延迟。CDN 厂商通过实时探测全球节点间的延迟和丢包率,在自己的骨干网中选择最优传输路径。这种"动态路由选优"可以将跨地域 API 请求的延迟降低 30%-50%。
原理类似于导航软件的实时路况避堵:不走默认的高速公路(公网 BGP 路由),而是根据实时路况(网络探测数据)选择最快的路径。
TCP/TLS 优化
动态请求的一大延迟来源是 TCP 和 TLS 握手。CDN 通过以下手段优化:
| 优化技术 | 原理 | 收益 |
|---|---|---|
| TCP 连接复用 | 边缘节点与源站保持长连接池 | 消除重复的 TCP 三次握手 |
| TLS Session Resumption | 复用之前的 TLS 会话 | 将 TLS 握手从 2-RTT 降为 1-RTT |
| TLS 1.3 | 更快的握手协议 | 首次连接 1-RTT,恢复连接 0-RTT |
| TCP BBR | Google 的拥塞控制算法 | 在高延迟/高丢包链路上吞吐量提升数倍 |
| QUIC/HTTP3 | 基于 UDP 的传输协议 | 0-RTT 连接建立,消除队头阻塞 |
其中 QUIC/HTTP3 是最具革命性的优化。传统 HTTP/2 基于 TCP,一个丢包会阻塞整个连接上的所有流(队头阻塞)。QUIC 将每个流独立处理,丢包只影响对应的流,对于并发请求多的场景提升显著。
Edge Computing(边缘计算)
更进一步,现代 CDN 支持在边缘节点运行自定义逻辑,将计算推向用户。Cloudflare Workers、AWS CloudFront Functions、Deno Deploy 都是这个方向的产品。
典型的边缘计算用途包括:A/B 测试(在边缘决定用户分组,无需回源)、请求改写(URL Rewrite、Header 注入)、鉴权(在边缘验证 JWT,拒绝非法请求到达源站)、SSR(在边缘执行服务端渲染)。
一句话总结:动态加速的核心不是缓存,而是通过优化网络路径、传输协议和边缘计算来缩短源站与用户之间的"逻辑距离"。
安全能力
CDN 天然处于用户和源站之间的流量路径上,这使它成为安全防护的最佳位置。攻击流量在边缘就被过滤,不会到达源站。
DDoS 防护
CDN 的全球分布式架构本身就是 DDoS 防护的第一道屏障。数百个节点加起来的带宽容量可以达到 Tbps 级别,远超单源站能承受的流量。
DDoS 防护分为网络层(L3/L4)和应用层(L7)两个层面。网络层防护通过流量清洗中心过滤 SYN Flood、UDP Flood 等攻击。应用层防护则需要识别 HTTP Flood、CC 攻击等模拟正常请求的攻击流量,通常结合速率限制、行为分析、Challenge(JS 验证/验证码)等手段。
WAF(Web Application Firewall)
CDN 集成的 WAF 在边缘检查每个 HTTP 请求,拦截 SQL 注入、XSS、命令注入等攻击。相比源站部署的 WAF,CDN WAF 的优势是:攻击在边缘就被拦截,不消耗源站资源;规则更新全球同步,响应新漏洞更快。
规则通常分为三层:核心规则集(OWASP CRS 等通用规则)、托管规则(CDN 厂商维护的场景化规则集)、自定义规则(业务方根据自身场景添加的特定规则)。
Bot 管理
CDN 还需要区分正常用户、善意爬虫(如 Googlebot)和恶意 Bot(爬虫、撞库、刷票)。Bot 检测通常基于以下信号:请求频率和模式(正常用户不会每秒请求 100 次同一接口)、客户端指纹(TLS 指纹、HTTP/2 指纹可以区分浏览器和脚本)、行为特征(鼠标轨迹、页面停留时间)。
HTTPS 证书管理
CDN 需要管理大量的 TLS 证书(每个加速域名一张)。现代 CDN 普遍支持自动化证书管理:集成 Let's Encrypt 自动签发和续期、支持 SAN/通配符证书减少证书数量、证书热更新不中断服务。
证书部署还涉及一个工程难题:新证书需要在几分钟内同步到全球数百个边缘节点,同时不能在同步过程中出现部分节点用旧证书、部分用新证书导致的服务异常。
一句话总结:CDN 从网络层到应用层构建了纵深防御体系,DDoS 防护靠分布式架构消化流量,WAF 和 Bot 管理在应用层精确拦截,证书管理保障传输安全。
核心技术难点深度分析
上面讲的是 CDN 的能力全景,这一章深入那些真正困难的技术问题 —— 这些问题没有完美解,只有在一致性、性能、成本之间做取舍。
难点一:缓存一致性
缓存一致性是 CDN 最经典的难题,本质是 CAP 定理在缓存场景的投影。
TTL 方案的困境:TTL 设长了,源站更新后用户长时间看到旧内容;设短了,缓存频繁失效导致命中率下降。而且 TTL 只能保证"最终一致",在 TTL 窗口内的不一致是确定会发生的。
Purge 方案的困境:主动 Purge 看似能立即生效,但在全球数百个节点上同步 Purge 需要时间(通常 5-30 秒),在传播过程中存在一致性窗口。更棘手的是级联失效:Purge 一个热点资源会导致所有节点同时回源,形成回源风暴(Thundering Herd),可能打垮源站。
工程上的最佳实践是分层处理:
| 内容类型 | 一致性策略 | 说明 |
|---|---|---|
| 带 Hash 的静态资源 | 版本化 URL + immutable |
零一致性问题,缓存一年 |
| 入口 HTML | no-cache + ETag |
每次校验但响应 304,延迟增加 1-RTT |
| API 响应 | 短 TTL(10-60s)+ stale-while-revalidate | 容忍短暂不一致换取性能 |
| 用户敏感数据 | no-store 不缓存 |
牺牲性能换一致性 |
| 紧急更新(如安全补丁) | Purge + Soft Purge 配合 | Soft Purge 不删缓存,标记为 stale,后台异步回源 |
stale-while-revalidate 是一个巧妙的折中:缓存过期后先返回旧内容(stale),同时后台异步回源更新缓存。用户感知到的延迟不变,而缓存在后台更新。HTTP 标准原生支持这个语义:
Cache-Control: max-age=60, stale-while-revalidate=300
# 60 秒内:返回缓存
# 60-360 秒:返回旧缓存 + 后台回源更新
# 360 秒后:必须等待回源
难点二:长尾内容的冷启动
CDN 的缓存命中率遵循典型的幂律分布:1% 的热门内容贡献 80% 的流量,而剩余 99% 的长尾内容各自流量很低。长尾内容在每个边缘节点的访问频率可能低到无法维持缓存(被 LRU 淘汰),每次访问都要回源。
这个问题的本质是缓存容量有限,而内容数量趋于无限。解决方向有三个:
中间层兜底:长尾内容在边缘 MISS 后,在中间层有更大概率命中(因为中间层聚合了多个边缘的流量,单资源访问频率更高)。这是三层架构的核心价值之一。
预热(Prefetch):对于可预测的内容(如即将发布的活动页面),提前将内容推送到边缘节点。预热需要权衡:推送太多会占用缓存空间挤出其他热点内容,推送太少则首批用户仍要回源。
动态分级:根据内容的访问模式动态调整其在 CDN 中的存储策略。高频访问的内容缓存到内存,中频缓存到 SSD,低频只在中间层保留,极低频不缓存直接回源。这需要实时的访问统计和自适应的缓存策略。
难点三:大文件分发
视频、安装包、固件升级等大文件(GB 级)的分发面临独特的挑战。
Range 回源:用户请求一个 4GB 的视频,CDN 不应该先把整个文件从源站拉下来再返回给用户(那延迟就太大了)。正确做法是 Range 回源 —— CDN 将大文件切分为固定大小的分片(如 2MB),按需从源站拉取用户实际请求的分片范围。
# 用户请求视频的某一段
GET /video.mp4 HTTP/1.1
Range: bytes=10485760-12582911 # 请求 10MB-12MB 的数据
# CDN 仅回源对应的分片
# 分片 5: bytes=10485760-12582911
# 不拉取整个 4GB 文件
分片缓存:大文件被切分为分片后独立缓存,每个分片有自己的缓存键和 TTL。这样用户拖动视频进度条时,CDN 只需要获取新的分片,已缓存的分片直接返回。
断点续传:大文件下载可能因网络中断而失败。CDN 需要支持客户端通过 Range Header 从上次中断的位置继续下载,而不是重新开始。这要求 CDN 的分片缓存机制与 Range 请求正确配合。
难点四:多 CDN 调度
大型互联网公司通常不会只用一家 CDN,而是同时接入 2-3 家 CDN 厂商,原因包括:容灾(单一 CDN 故障时切换到备用)、成本(不同 CDN 在不同区域价格不同)、性能(不同 CDN 在不同地区的节点覆盖不同)。
多 CDN 调度面临的核心问题:
调度层实现:需要在业务 DNS 和 CDN DNS 之间加一层自研的调度系统。这个调度系统需要实时监控各 CDN 在各地区的性能和可用性,动态分配流量比例。实现方式通常是自建 DNS 或使用 DNS 调度服务(如 NS1、Route 53 流量策略)。
监控一致性:需要统一各 CDN 的监控数据口径。不同 CDN 对"命中率""回源率""响应时间"的定义和计算方式不完全一致,需要部署独立的第三方监控(如 RUM 探测、拨测)来获得客观数据。
缓存 Purge 同步:内容更新时需要同时向所有 CDN 发送 Purge 请求,且各 CDN 的 Purge API 接口、鉴权方式、生效时间都不同,需要一个统一的 Purge 网关来适配。
成本核算:多 CDN 的计费模式不同(按带宽峰值、按流量、按请求数),需要统一的成本核算系统来优化流量分配策略,在性能和成本之间找到最优平衡点。
一句话总结:CDN 的核心技术难点都没有银弹 —— 缓存一致性是一致性与性能的取舍,长尾冷启动是容量与覆盖的取舍,大文件分发是完整性与响应速度的取舍,多 CDN 调度是可用性与复杂度的取舍。
研发接入与上线
前面讲了 CDN 的原理和技术难点,这一章切换到研发工程师的视角:拿到一个 CDN 服务后,从域名规划到生产上线,具体要做哪些事情,每一步的关键配置和踩坑点是什么。
第一步:域名规划与 CNAME 接入
CDN 接入的起点是域名设计。核心决策是:哪些域名走 CDN,哪些不走。
| 域名 | 是否走 CDN | 原因 |
|---|---|---|
static.example.com |
是 | 静态资源(JS/CSS/图片),缓存命中率高 |
img.example.com |
是 | 用户上传图片,配合图片处理服务 |
api.example.com |
视情况 | API 请求通常不缓存,但可用动态加速 |
www.example.com |
是 | 页面 HTML,短 TTL 或 no-cache + ETag |
admin.example.com |
否 | 内部管理后台,无需加速 |
域名确定后,在 CDN 控制台添加加速域名,CDN 会分配一个 CNAME 地址(如 static.example.com.cdn.provider.net)。然后在 DNS 服务商处将业务域名 CNAME 指向这个地址:
; DNS 配置示例
static.example.com. 300 IN CNAME static.example.com.cdn.provider.net.
img.example.com. 300 IN CNAME img.example.com.cdn.provider.net.
上线前将 TTL 设为较短的值(如 300 秒),方便出问题时快速切回源站。稳定运行后可以调高到 3600 秒减少 DNS 查询。
常见踩坑点:根域名(example.com)不能配置 CNAME(DNS 标准限制)。如果需要对根域名加速,需要使用 CDN 厂商提供的 CNAME 展平(CNAME Flattening)功能,或者将根域名 301 重定向到 www.example.com。
第二步:源站配置
源站配置告诉 CDN"缓存 MISS 时去哪里取内容"。关键配置项包括:
# 源站配置(概念性)
origin:
type: domain # domain | ip | oss_bucket
address: origin.example.com # 源站地址,不要填 CDN 加速域名(会回环)
port: 443
protocol: https # 回源协议,推荐 HTTPS
host_header: static.example.com # 回源 Host,源站靠这个区分站点
# 多源站负载均衡
backup:
- address: origin-backup.example.com
weight: 0 # 权重 0 = 纯备用,主源站挂了才启用
# 回源超时
connect_timeout: 5s
read_timeout: 30s
# 回源重试
retry:
enabled: true
on_status: [502, 503, 504]
max_attempts: 2
关键注意:host_header 必须设对。源站通常用 Nginx 的 server_name 匹配虚拟主机,如果回源 Host 不匹配任何 server block,Nginx 会返回默认站点甚至 404,这是新手最常见的问题。
回环陷阱:源站地址绝对不能填 CDN 加速域名本身。否则 CDN 回源时会解析到自己,形成无限回环,最终超时报错。源站应该用独立的域名(如 origin.example.com,DNS 直接指向源站 IP)或直接用 IP。
第三步:缓存规则配置
缓存规则决定了"什么内容缓存多久"。大多数 CDN 支持按路径、文件后缀、HTTP Header 三种维度配置,优先级从高到低:
| 优先级 | 规则类型 | 示例 | TTL |
|---|---|---|---|
| 1(最高) | 精确路径 | /index.html |
0(不缓存) |
| 2 | 路径前缀 | /api/* |
0(不缓存) |
| 3 | 文件后缀 | .js .css .png .woff2 |
365 天 |
| 4 | 文件后缀 | .html |
60 秒 |
| 5(最低) | 遵循源站 | 其余全部 | 遵循 Cache-Control Header |
研发侧需要在源站的 Web Server 上配好响应头,CDN 会据此决定缓存行为:
# Nginx 源站缓存头配置
# 带 Hash 的静态资源:缓存一年,不校验
location ~* \.(js|css|png|jpg|svg|woff2)$ {
if ($uri ~* "\.[a-f0-9]{8,}\.") {
add_header Cache-Control "public, max-age=31536000, immutable";
}
}
# HTML 入口页:每次校验
location ~* \.html$ {
add_header Cache-Control "no-cache";
etag on;
}
# API 接口:不缓存
location /api/ {
add_header Cache-Control "no-store";
}
# 用户上传图片:缓存但允许 Purge 更新
location /uploads/ {
add_header Cache-Control "public, max-age=86400";
}
关键原则:能用版本化 URL 的资源(前端构建产物)用 immutable 长缓存;入口文件(HTML)用 no-cache(每次校验但可 304);API 和个性化内容用 no-store。这样在不牺牲一致性的前提下最大化命中率。
第四步:HTTPS 配置
生产环境必须全站 HTTPS。CDN 的 HTTPS 配置分为两段:用户到边缘节点(边缘证书)和边缘节点到源站(回源协议)。
边缘证书:在 CDN 控制台上传证书(证书 + 私钥),或使用 CDN 提供的免费证书(Let's Encrypt)。如果有多个子域名,推荐使用通配符证书(*.example.com)减少管理成本。
回源 HTTPS:推荐边缘到源站也使用 HTTPS(端到端加密)。CDN 控制台设置回源协议为 HTTPS,源站需要配置有效证书。如果源站证书是自签名的,需要在 CDN 侧关闭证书校验(不推荐,仅限内网场景)。
# HTTPS 配置要点
https:
# 边缘侧
edge_cert: "*.example.com" # 通配符证书
tls_min_version: TLSv1.2 # 最低 TLS 版本
http2: enabled # 启用 HTTP/2
hsts: max-age=31536000 # HSTS Header
# 回源侧
origin_protocol: https
origin_sni: origin.example.com # SNI,多域名源站必须设
HTTP 强制跳转 HTTPS:CDN 侧开启 HTTP → HTTPS 301 重定向,确保用户始终走加密通道。
第五步:CI/CD 集成
CDN 接入后,发布流程需要增加缓存刷新步骤。典型的前端发布流程变为:
#!/bin/bash
# deploy.sh - 前端发布脚本
# 1. 构建(产出带 hash 的文件)
npm run build
# 输出: dist/app.a3b2c1.js, dist/style.d4e5f6.css, dist/index.html
# 2. 上传静态资源到对象存储(源站)
aws s3 sync dist/ s3://my-bucket/static/ \
--exclude "*.html" \
--cache-control "public, max-age=31536000, immutable"
# 3. 上传 HTML(短缓存)
aws s3 cp dist/index.html s3://my-bucket/static/index.html \
--cache-control "no-cache"
# 4. 刷新 CDN 缓存(仅刷新 HTML,JS/CSS 有 hash 无需刷新)
# 阿里云 CDN
aliyun cdn RefreshObjectCaches \
--ObjectPath "https://static.example.com/index.html"
# 或 AWS CloudFront
aws cloudfront create-invalidation \
--distribution-id E1234567890 \
--paths "/index.html"
# 5. 验证:检查新版本是否生效
curl -sI https://static.example.com/index.html | grep -i "etag\|last-modified"
核心原则:带 Hash 的资源不需要 Purge(URL 变了,自动用新版本),只需要 Purge 入口 HTML。这样 Purge 范围极小,生效快且不会引发回源风暴。
对于需要即时生效的场景(如紧急修复),在 Purge 后可以主动预热关键 URL:
# Purge 后主动预热,让边缘节点提前拉取新内容
aliyun cdn PushObjectCache \
--ObjectPath "https://static.example.com/index.html"
第六步:上线验证 Checklist
在 CNAME 切换之前,通过绑定本地 Host 的方式验证 CDN 链路是否正常:
# 本地 Host 绑定,绕过 DNS 直接访问 CDN 节点
# 先解析 CDN CNAME 获取边缘节点 IP
dig static.example.com.cdn.provider.net +short
# 假设返回 1.2.3.4
# 在 /etc/hosts 中添加
# 1.2.3.4 static.example.com
# 然后用 curl 验证
curl -vI https://static.example.com/test.js 2>&1 | grep -E "HTTP|cache|age|server"
上线验证清单:
| 检查项 | 验证方法 | 预期结果 |
|---|---|---|
| HTTPS 正常 | curl -vI https://... |
TLS 握手成功,证书域名匹配 |
| 缓存命中 | 连续请求两次,检查响应头 | 第二次 X-Cache: HIT |
| 回源正常 | Purge 后请求,检查响应头 | X-Cache: MISS,内容正确 |
| 缓存头正确 | 检查 Cache-Control Header |
与源站配置一致 |
| Gzip/Brotli | Accept-Encoding: gzip 请求 |
Content-Encoding: gzip |
| 跨域头 | 前端 AJAX 请求 | Access-Control-Allow-Origin 正确 |
| 404 行为 | 请求不存在的路径 | 返回自定义 404 页而非 CDN 默认错误页 |
| 大文件 Range | Range: bytes=0-1023 请求 |
返回 206 Partial Content |
特别注意跨域配置:如果静态资源域名和页面域名不同(如 static.example.com vs www.example.com),源站必须返回 Access-Control-Allow-Origin Header,且 CDN 的缓存键需要包含 Origin Header(通过 Vary: Origin 实现),否则跨域请求的缓存响应可能缺少 CORS 头。
一句话总结:CDN 接入不是"控制台点几下"的事情,域名规划、源站配置、缓存头设计、HTTPS 链路、CI/CD 刷新流程、上线验证缺一不可,每一步都有容易踩的坑。
内容变更与缓存刷新体系
CDN 上线后,最高频的运维操作就是"内容更新了,CDN 上的缓存怎么同步"。不同类型的内容变更对刷新策略的要求完全不同,需要一套体系化的方案而不是靠人工去控制台点 Purge。
按变更场景选择刷新策略
| 变更场景 | 刷新策略 | 原因 |
|---|---|---|
| 前端发版(JS/CSS/图片) | 不刷新,依赖版本化 URL | 文件名含 Hash,新版本是新 URL |
| 入口 HTML 更新 | Purge 具体 URL | HTML 引用新 Hash 文件,Purge 后用户拿到新 HTML |
| CMS 文章发布/修改 | Purge 文章 URL + 列表页 URL | 文章内容变了,列表页也可能变 |
| 用户头像/上传图片替换 | Purge 具体 URL | 同一 URL 内容变了 |
| 全站改版/模板变更 | 目录级 Purge | 影响面大,按路径前缀批量刷新 |
| 紧急安全修复 | Purge + 预热 | 需要确保所有节点立刻拿到新内容 |
| 配置变更(如 CORS 头) | 目录级 Purge 或全量 Purge | 响应头变了,缓存的旧响应头需要清除 |
Purge 服务设计
生产环境不应该让开发者手动去 CDN 控制台刷缓存,而是封装一个内部的 Purge 服务,由业务系统自动触发:
# purge_service.py - 统一缓存刷新服务
import hashlib
import time
import requests
class CDNPurgeService:
"""统一 CDN 缓存刷新服务,支持多 CDN 厂商"""
def __init__(self, providers: list):
self.providers = providers # [AliyunCDN, CloudFrontCDN, ...]
def purge_urls(self, urls: list, callback_url: str = None):
"""URL 级别刷新"""
task_id = self._gen_task_id(urls)
for provider in self.providers:
provider.submit_purge(urls=urls, task_id=task_id)
# 异步轮询刷新状态
self._poll_and_notify(task_id, callback_url)
return task_id
def purge_dirs(self, dirs: list, callback_url: str = None):
"""目录级别刷新(影响范围大,需审批)"""
task_id = self._gen_task_id(dirs)
for provider in self.providers:
provider.submit_purge(dirs=dirs, task_id=task_id)
self._poll_and_notify(task_id, callback_url)
return task_id
def purge_and_warm(self, urls: list):
"""刷新 + 预热:先清除旧缓存,再主动回源填充新内容"""
task_id = self.purge_urls(urls)
self._wait_purge_complete(task_id)
for provider in self.providers:
provider.submit_prefetch(urls=urls)
return task_id
Purge 服务的关键设计要点:
多 CDN 同步刷新:如果接了多家 CDN,Purge 请求必须同时发到所有厂商。服务内部适配各厂商不同的 API 格式和鉴权方式,对上游调用方暴露统一接口。
异步 + 回调:CDN Purge 不是立即生效的(通常 5-30 秒全网生效),服务提交 Purge 后应返回任务 ID,后台轮询各厂商的任务状态,全部生效后通过回调通知上游。
频率限制与合并:CDN 厂商对 Purge API 有调用频率限制(如每秒 50 次、每天 10000 条 URL)。Purge 服务需要做请求合并 —— 短时间内对同一 URL 的多次 Purge 请求合并为一次,目录级 Purge 可以覆盖子 URL 的 Purge 请求。
审计日志:记录每次 Purge 的触发方、目标 URL、时间、生效状态。出问题时可以快速定位"是不是有人误刷了缓存"。
业务系统集成模式
不同类型的业务系统接入 Purge 服务的方式不同:
前端发布系统:CI/CD Pipeline 在部署步骤完成后,自动调用 Purge 服务刷新入口 HTML。在前面"CI/CD 集成"一节已展示过脚本示例。
CMS / 内容管理系统:文章发布或修改时,CMS 通过 Webhook 或消息队列通知 Purge 服务。需要刷新的 URL 包括:文章详情页、相关列表页、RSS Feed、Sitemap。
# CMS 发布文章后触发刷新
def on_article_published(article):
urls_to_purge = [
f"https://www.example.com/posts/{article.slug}", # 文章页
"https://www.example.com/posts/", # 文章列表
f"https://www.example.com/category/{article.category}/", # 分类页
"https://www.example.com/sitemap.xml", # Sitemap
"https://www.example.com/feed.xml", # RSS
]
purge_service.purge_urls(urls_to_purge)
电商系统:商品信息更新(价格、库存、图片)时触发刷新。电商场景的特殊挑战是高频变更 —— 大促期间可能每秒有数百个 SKU 价格变动,需要 Purge 请求合并和优先级队列来避免打满 CDN 的 Purge API 限额。
用户上传服务:用户替换头像或封面图时,因为 URL 不变(/avatar/user123.jpg),必须 Purge 旧缓存。更好的做法是 URL 带上版本参数(/avatar/user123.jpg?v=1681234567),每次上传递增版本号,这样无需 Purge。
Soft Purge 与 Stale 策略
传统 Purge(Hard Purge)直接删除缓存,下一个请求必须回源。如果热点资源被 Purge,会导致大量请求同时回源,形成回源风暴。
Soft Purge 是更安全的替代方案:不删除缓存,而是将缓存标记为 stale(过期),CDN 继续用旧缓存响应请求,同时在后台异步回源拉取新内容。等新内容就绪后替换旧缓存。用户感知到的延迟不变,源站也不会被打垮。
# Hard Purge vs Soft Purge 对比
#
# Hard Purge:
# t=0 Purge 执行 → 缓存删除
# t=1 用户请求 → 缓存 MISS → 回源(用户等待)
# t=2 回源完成 → 返回新内容 → 写入缓存
#
# Soft Purge:
# t=0 Purge 执行 → 缓存标记为 stale
# t=1 用户请求 → 返回 stale 缓存(用户无感知)+ 后台异步回源
# t=2 回源完成 → 缓存更新为新内容
# t=3 后续请求 → 返回新内容
Soft Purge 配合 stale-while-revalidate Header 效果最佳。但要注意:对一致性要求极高的场景(如价格变更、安全补丁),仍然需要 Hard Purge + 预热来确保立即生效。
刷新生效验证
Purge 提交后需要验证是否真的生效了。不能只信任 CDN 返回的"任务完成"状态,要从用户视角验证:
# 验证脚本:从多个地区检查缓存是否已刷新
#!/bin/bash
URL="https://static.example.com/index.html"
EXPECTED_ETAG="\"abc123\"" # 新版本的 ETag
# 从不同地区的 DNS 解析获取不同边缘节点 IP
REGIONS=("北京:1.2.3.4" "上海:5.6.7.8" "广州:9.10.11.12")
for region_ip in "${REGIONS[@]}"; do
region="${region_ip%%:*}"
ip="${region_ip##*:}"
actual_etag=$(curl -sI --resolve "static.example.com:443:$ip" \
"$URL" | grep -i "etag" | tr -d '\r')
if echo "$actual_etag" | grep -q "$EXPECTED_ETAG"; then
echo "[$region] OK - 新版本已生效"
else
echo "[$region] PENDING - 仍返回旧版本 ($actual_etag)"
fi
done
对于自动化程度更高的团队,可以将验证集成到 Purge 服务中:Purge 完成后自动从多个地区拨测验证,全部通过后才标记刷新任务为成功,否则触发告警。
一句话总结:缓存刷新不是"调一下 API"的事情,它需要一套包含策略选择、服务封装、业务集成、Soft Purge 容错、生效验证的完整体系,才能在内容新鲜度和源站稳定性之间取得平衡。
运营优化
CDN 选型决策框架
选择 CDN 不是选"最好的",而是选"最适合的"。以下框架帮助做结构化评估:
| 评估维度 | 关键问题 | 评估方法 |
|---|---|---|
| 节点覆盖 | 目标用户集中在哪些地区/运营商? | 对比各 CDN 的节点分布图与业务用户分布 |
| 性能基线 | 首字节时间、下载速度是否满足 SLA? | 接入试用,用 RUM / 拨测对比 |
| 功能匹配 | 是否支持所需特性(QUIC、边缘计算、实时日志)? | 功能清单逐项对比 |
| 安全能力 | DDoS 防护容量、WAF 规则丰富度? | 安全团队评估 |
| 成本模型 | 按带宽/流量/请求计费,哪种模式更优? | 基于历史流量数据模拟计算 |
| 运维集成 | API 完整性、Terraform 支持、日志格式? | 开发团队评估集成成本 |
监控体系
CDN 监控需要覆盖四个层面:
用户侧(RUM):通过 JavaScript SDK 采集真实用户的加载性能数据,包括 DNS 解析时间、TCP 连接时间、首字节时间(TTFB)、内容下载时间。这是最接近用户真实体验的数据。
节点侧:CDN 厂商提供的实时日志和监控面板,包括缓存命中率、回源 QPS、错误率、带宽使用量。通过实时日志(如 Kafka 投递)接入自己的监控系统做自定义分析。
源站侧:监控回源流量是否在预期范围内。回源 QPS 突增通常意味着缓存策略有问题(配置错误、缓存被批量 Purge、热点变化)。
拨测:从全国/全球各地的拨测节点定期请求 CDN 资源,主动检测各地区的可达性和性能。拨测数据是发现区域性故障的最快手段(比用户投诉快得多)。
# CDN 关键监控指标
performance:
- ttfb_p50: < 50ms # 首字节时间中位数
- ttfb_p99: < 200ms # 首字节时间 P99
- download_speed: > 10MB/s # 大文件下载速度
cache:
- hit_ratio: > 95% # 缓存命中率
- origin_qps: < 源站容量的 30% # 回源 QPS 安全水位
availability:
- error_rate_5xx: < 0.01% # 5xx 错误率
- availability: > 99.95% # 可用性 SLA
cost:
- bandwidth_utilization: > 60% # 带宽利用率(按峰值计费时)
- cost_per_gb: 持续跟踪 # 单位流量成本趋势
成本优化策略
CDN 成本主要由带宽和请求数构成,优化方向包括:
提升命中率:这是最直接的成本优化手段。命中率从 90% 提升到 95%,回源流量减半,源站带宽成本降一半。通过优化缓存键、调整 TTL、启用 stale-while-revalidate 来实现。
压缩优化:开启 Gzip/Brotli 压缩可以将文本类资源体积减少 60-80%。Brotli 比 Gzip 压缩率高 15-20%,但压缩速度慢,适合配合静态预压缩使用。图片资源使用 WebP/AVIF 格式替代 JPEG/PNG,可以在同等画质下减少 30-50% 体积。
计费模式选择:流量稳定且波峰波谷差异小的业务适合带宽峰值计费(95 计费),突发流量大的业务适合按流量计费。部分 CDN 支持"预付费带宽包"模式,单价更低但需要预估用量。
回源带宽优化:源站开启 HTTP 304(条件请求)和 Range 回源,避免回源时传输完整文件。配置合理的回源 Host 和路径,避免不必要的 302 跳转增加回源次数。
灰度与切换方案
CDN 切换(更换厂商或调整配置)是高风险操作,应采用灰度策略:
第一步,小流量验证:通过 DNS 权重将 5% 的流量切到新 CDN,持续观察 1-3 天的性能和错误率指标。
第二步,逐步放量:按 5% → 20% → 50% → 100% 的节奏逐步放量,每个阶段稳定运行一段时间后再推进。
第三步,快速回滚:预留回滚方案,通过降低 DNS TTL(切换前 24 小时将 TTL 降到 60-120 秒)确保必要时可以在分钟级别回切旧 CDN。
第四步,双跑对比:在切换过程中保持新旧 CDN 双跑,对比两者在各个维度的数据。确认新 CDN 各项指标不低于旧 CDN 后,再完成最终切换。
一句话总结:CDN 上线只是开始,持续的监控、成本优化和灰度切换能力才是长期运营 CDN 的关键。
全文总结
CDN 表面上是"把文件缓存到离用户近的地方",但实际是一套涉及网络调度、分布式缓存、一致性保障、协议优化、安全防护的完整技术体系。
回顾全文的技术脉络:三层架构构建了流量漏斗,DNS/302/Anycast 解决了用户到节点的调度问题,分层缓存和一致性机制保障了命中率和数据新鲜度,回源策略和 Request Collapsing 保护了源站,动态加速和边缘计算将 CDN 从"缓存层"扩展为"边缘计算平台",安全能力则利用 CDN 的流量位置构建了纵深防御。从研发接入的视角看,域名规划、源站配置、缓存头设计、HTTPS 链路、CI/CD 集成每一步都有容易踩的坑;内容变更后的缓存刷新更是需要 Purge 服务封装、多场景策略、Soft Purge 容错和生效验证组成的完整体系;而上线后的监控、成本优化和灰度切换则决定了 CDN 能否持续发挥价值。
理解了这些原理和难点后,面对实际的 CDN 选型、架构设计、故障排查和成本优化,才能做出有依据的工程决策,而不是凭经验和直觉。