一个视频从服务器到你屏幕,中间经历了什么?聊聊 CDN 架构与视频分发全链路
你点击播放按钮,零点几秒后画面就出来了。这短短几百毫秒里,一个视频文件穿越了 DNS 解析、GSLB 调度、边缘节点命中/回源、TCP 握手、TLS 加密、HTTP Range 请求……背后是一整套全球分布的 CDN(内容分发网络) 在协同工作。本文把视频分发的完整链路从头拆到尾,并深入 CDN 的调度算法、缓存策略和成本优化。
TL;DR:CDN 不是一台服务器,而是一个全球分布的缓存网络。核心架构:GSLB(全局负载均衡)把用户调度到最近的边缘节点 → 边缘节点有缓存则直接返回(命中),没缓存则回源拉取 → 逐级缓存到区域节点和边缘节点。视频 CDN 的特殊性:文件大、QPS 高、需要支持 Range 请求、需要防盗链。多 CDN 调度和预热策略是成本优化的关键。
目录
- 一、没有 CDN 的世界是什么样
- 二、CDN 的核心架构:四层缓存金字塔
- 三、GSLB:如何把用户调度到"最近"的节点
- 四、缓存策略:什么该存、存多久、怎么淘汰
- 五、视频 CDN 的特殊挑战
- 六、多 CDN 调度与成本优化
- 七、回源优化:减少回源次数的技巧
- 八、实战:自建简易 CDN 的思路
- 九、合规与温馨提示
一、没有 CDN 的世界是什么样
假设你把一个视频文件放在北京的一台服务器上,全世界用户都直接访问这台服务器:
北京用户:延迟 5ms,下载速度 50MB/s ✅ 极快
上海用户:延迟 25ms,下载速度 20MB/s ✅ 还行
纽约用户:延迟 250ms,下载速度 2MB/s ⚠️ 能看但卡
悉尼用户:延迟 400ms,下载速度 500KB/s ❌ 基本没法用
物理距离是光速决定的——北京到纽约的直线距离约 11000 公里,光在光纤中的速度约 20 万公里/秒,单程至少 55ms。加上路由器跳转,实际 RTT 在 200-300ms。TCP 三次握手 + TLS 握手就要 4-5 个 RTT,还没开始传数据就已经过去 1 秒多了。
CDN 解决的三个核心问题:
| 问题 | CDN 方案 |
|---|---|
| 物理距离导致的高延迟 | 在全球部署边缘节点,用户就近访问 |
| 源站带宽瓶颈 | 边缘节点分担流量,源站只服务回源请求 |
| 跨运营商访问慢 | 在每个运营商内部部署节点,避免跨网 |
二、CDN 的核心架构:四层缓存金字塔
源站(Origin)
┌──────────┐
│ 对象存储 │ ← 原始文件所在地(OSS/S3/本地服务器)
│ / NAS │
└─────┬────┘
│ 回源(仅当所有缓存层都 miss)
┌───────────┼───────────┐
│ │ │
┌────▼────┐ ┌───▼────┐ ┌───▼────┐
│ 中心节点 │ │ 中心节点│ │ 中心节点│ ← 区域缓存层(Region)
│ (华东) │ │ (华南) │ │ (北美) │ 缓存热点内容,减少回源
└────┬────┘ └───┬────┘ └───┬────┘
│ │ │
┌───────┼───┐ ┌───┼───┐ ┌───┼───┐
┌───▼──┐┌──▼──┐ ┌▼──┐┌▼──┐ ┌▼──┐┌▼──┐
│边缘1 ││边缘2│ │边3││边4│ │边5││边6│ ← 边缘缓存层(Edge)
│(上海)││(杭州)│ │...││...│ │...││...│ 直接面向用户
└──┬───┘└──┬──┘ └───┘└───┘ └───┘└───┘
│ │
用户A 用户B
各层职责:
| 层级 | 节点数量 | 缓存容量 | 命中率 | 延迟 |
|---|---|---|---|---|
| 源站 | 1-3 个 | 无限(存储) | — | 最高 |
| 中心/区域节点 | 10-50 个 | TB 级 | 80-95% | 中 |
| 边缘节点 | 数百到数千 | 百 GB 级 | 60-80% | 最低(< 10ms) |
一次典型的视频请求流程:
1. 用户点击播放
2. 浏览器解析域名 → DNS 返回 GSLB 调度结果
3. GSLB 根据用户 IP 和节点负载 → 返回最优边缘节点 IP
4. 用户向边缘节点发起 HTTP Range 请求
5. 边缘节点查缓存:
├─ 命中(Cache Hit)→ 直接返回数据 ✅
└─ 未命中(Cache Miss)→ 向上级缓存或源站回源
├─ 区域节点命中 → 返回数据 + 缓存到边缘
└─ 区域节点未命中 → 回源 → 逐级缓存
三、GSLB:如何把用户调度到"最近"的节点
GSLB(Global Server Load Balance,全局负载均衡)是 CDN 的"大脑"。它的核心任务:给定一个用户 IP,返回最优的边缘节点 IP。
调度算法考虑的维度:
| 维度 | 权重 | 说明 |
|---|---|---|
| 地理位置 | ⭐⭐⭐⭐⭐ | 物理距离越近,延迟越低 |
| 运营商 | ⭐⭐⭐⭐ | 同运营商 > 跨运营商(BGP 互通质量参差不齐) |
| 节点负载 | ⭐⭐⭐⭐ | 避免把流量全部打到同一个节点 |
| 节点健康度 | ⭐⭐⭐⭐⭐ | 故障节点必须被自动剔除 |
| 带宽成本 | ⭐⭐ | 在质量达标的前提下,优先用便宜的节点 |
| 用户历史调度 | ⭐ | 尽量不频繁切换节点(影响缓存命中率) |
基于 DNS 的 GSLB(最常用):
用户访问 cdn.example.com
│
▼
本地 DNS → 权威 DNS(CNAME 到 GSLB)→ GSLB 返回边缘节点 IP
GSLB 的 DNS 响应 TTL 通常很短(30-60 秒),这样节点故障时能快速切换。
基于 HTTP 重定向的调度:
用户请求 https://cdn.example.com/video.mp4
│
▼
调度服务器 302 重定向 → https://edge-sh-01.cdn.example.com/video.mp4
HTTP 重定向比 DNS 调度更灵活(不受 DNS 缓存影响),但多一次 HTTP 往返,增加了首包延迟。
Anycast(任播)调度:
同一个 IP 通过 BGP 宣告到多个机房,路由协议自动把用户流量送到最近的机房。Cloudflare 大量使用 Anycast。好处是零额外延迟,缺点是调度粒度粗。
四、缓存策略:什么该存、存多久、怎么淘汰
CDN 缓存策略的核心矛盾:缓存空间有限(边缘节点通常只有几百 GB),但内容总量无限。
缓存淘汰算法:
| 算法 | 策略 | 适用场景 |
|---|---|---|
| LRU(最近最少使用) | 淘汰最久没被访问的 | 通用,CDN 标配 |
| LFU(最不经常使用) | 淘汰访问次数最少的 | 长尾内容多的场景 |
| Two-Queue | 热内容保护 + 冷内容快速淘汰 | 热点集中的视频 CDN |
| S3LRU | 三级 LRU 队列 | 现代 CDN 主流(如 Apache Traffic Server) |
视频 CDN 的特殊缓存策略:
视频文件的访问模式:前 10% 的播放量可能占了 90% 的总流量
│
▼
策略:热点视频全量缓存 + 长尾视频只缓存头部(前 30 秒)
分片缓存(Partial Cache):
视频 CDN 不一定是缓存整个文件。对于 HLS/DASH 流,可以只缓存热分片:
整部电影 2 小时 → 720 个 TS 分片(每片 10 秒)
大多数用户只看前 30 分钟 → 前 180 个分片是热点 → 优先缓存
后面 90 分钟的长尾分片 → 缓存优先级低,可能被淘汰
缓存 TTL 设置:
# 源站通过 HTTP 头控制 CDN 缓存行为
Cache-Control: public, max-age=86400 # 缓存 24 小时
ETag: "abc123" # 用于条件请求验证
Last-Modified: Wed, 01 Jul 2026 08:00:00 GMT
视频文件的 TTL 通常设得很长(7 天到 30 天),因为视频内容不会频繁修改。
五、视频 CDN 的特殊挑战
挑战 1:Range 请求的处理
视频播放器不会一次性下载整个文件,而是用 HTTP Range 分片请求:
GET /video.mp4 HTTP/1.1
Range: bytes=0-1048575 ← 只要前 1MB
GET /video.mp4 HTTP/1.1
Range: bytes=52428800-53477375 ← 拖动进度条后要中间的一段
CDN 节点需要正确处理 Range 请求:
- 如果整个文件已缓存 → 直接切片返回
- 如果只缓存了部分 → 可能需要回源拉缺失的 Range → 拼接
挑战 2:大文件的缓存写入
一个 4K 电影可能有 20GB。CDN 节点写磁盘的速度可能跟不上回源下载的速度。解决方案是边下载边服务:
第一个用户请求 4K 电影 → 边缘节点回源下载
├─ 下载了 10MB → 立即开始给用户发这 10MB
├─ 继续下载 → 继续发给用户
└─ 同时写入本地缓存
第二个用户请求同一电影 → 直接从缓存读取
挑战 3:并发 QPS
热门视频可能有数十万人同时观看。一个边缘节点需要处理每秒数万甚至数十万的 HTTP 请求。这要求:
- 零拷贝(sendfile/splice)传输,避免 CPU 参与数据搬运
- 连接复用(HTTP/2 多路复用、HTTP/3 QUIC)
- 内存缓存热文件(tmpfs),不读磁盘
六、多 CDN 调度与成本优化
大厂通常不会只用一家 CDN,而是同时接入多家(自建 + 商业 CDN),通过调度系统按需分配流量。
流量分配策略:
日常 80% → 自建 CDN(成本低)
突发 20% → 商业 CDN(弹性扩容)
海外 100% → CloudFront / Cloudflare(全球覆盖好)
直播推流 → 专线 / 高质量商业 CDN
多 CDN 调度系统架构:
class MultiCDNScheduler:
def select_cdn(self, user_ip, video_id, video_size):
# 1. 解析用户地理位置
geo = geoip.lookup(user_ip)
# 2. 查询各 CDN 的节点健康度和负载
cdns = self.get_healthy_cdns(geo)
# 3. 根据策略选择
if self.is_hot_video(video_id):
# 热点视频:优先自建 CDN(成本低)
return self.pick_lowest_cost(cdns)
elif geo.country != 'CN':
# 海外用户:选全球 CDN
return self.pick_global_cdn(cdns, geo)
else:
# 普通用户:按延迟 + 成本综合排序
return self.pick_best_performance(cdns, user_ip)
成本控制技巧:
| 技巧 | 做法 | 节省 |
|---|---|---|
| 预热 | 提前把热点内容推到边缘节点 | 减少回源带宽 |
| 分片调度 | 同一视频的不同分片走不同 CDN | 单家不超限 |
| 闲时预热 | 在凌晨低峰期预热热门内容 | 利用闲置带宽 |
| 压缩传输 | 开启 Brotli/Gzip 压缩文本类资源 | 减少 30-50% 流量 |
七、回源优化:减少回源次数的技巧
回源(Origin Pull)是 CDN 最大的成本来源——回源流量通常比边缘流量贵 3-5 倍。
技巧 1:合并回源
多个用户同时请求同一个冷门视频 → 边缘节点只回源一次 → 用拿到的数据同时服务所有等待的用户。
用户A 请求 cold_video.mp4 ─┐
用户B 请求 cold_video.mp4 ─┤
用户C 请求 cold_video.mp4 ─┼─→ 边缘节点合并为一次回源 → 数据同时给 A、B、C
用户D 请求 cold_video.mp4 ─┘
技巧 2:Range 合并
多个用户请求同一个文件的不同 Range,如果 Range 相邻或重叠,合并为一次更大的 Range 请求:
用户A: bytes=0-999999 ─┐
用户B: bytes=500000-1499999 ┼→ 合并为 bytes=0-1499999 一次回源
用户C: bytes=1000000-1999999─┘
技巧 3:预取(Prefetch)
当用户请求视频的 Range 0-1MB 时,CDN 可以"猜测"用户大概率会继续看下去,主动回源多拉一些:
用户请求 bytes=0-1048576(1MB)
CDN 回源请求 bytes=0-10485759(10MB)← 多拉了 9MB
用户继续看 → 后面的 9MB 已经缓存好了
八、实战:自建简易 CDN 的思路
如果想自建一个轻量级视频 CDN,核心组件:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ DNS/GSLB │────▶│ 边缘 Nginx │────▶│ 源站 MinIO │
│ (调度器) │ │ (缓存+服务) │ │ (对象存储) │
└─────────────┘ └─────────────┘ └─────────────┘
边缘节点 Nginx 配置:
# 反向代理 + 缓存
proxy_cache_path /data/nginx/cache levels=1:2 keys_zone=video_cache:10g
max_size=200g inactive=7d use_temp_path=off;
server {
listen 80;
server_name cdn.example.com;
location /videos/ {
proxy_cache video_cache;
proxy_cache_key "$uri$is_args$args";
proxy_cache_valid 200 206 7d; # 缓存 200 和 206 响应 7 天
proxy_cache_valid 404 1m;
# 支持 Range 请求
proxy_set_header Range $http_range;
proxy_pass http://origin.example.com;
# 切片缓存(每个 Range 单独缓存)
proxy_cache_lock on; # 合并并发回源
proxy_cache_lock_timeout 5s;
}
}
DNS 调度器(简化版):
from flask import Flask, request
import geoip2.database
app = Flask(__name__)
geo_reader = geoip2.database.Reader('GeoLite2-City.mmdb')
EDGE_NODES = {
'shanghai': {'ip': '1.2.3.4', 'lat': 31.23, 'lng': 121.47},
'beijing': {'ip': '5.6.7.8', 'lat': 39.90, 'lng': 116.40},
'guangzhou':{'ip': '9.10.11.12', 'lat': 23.13, 'lng': 113.26},
}
@app.route('/dns-query')
def dns_query():
user_ip = request.remote_addr
try:
location = geo_reader.city(user_ip)
# 简单策略:选最近的城市
best_node = min(EDGE_NODES.values(),
key=lambda n: haversine(location, n))
return best_node['ip']
except:
return EDGE_NODES['shanghai']['ip'] # 默认
def haversine(user_loc, node):
# 计算两点间的大圆距离
import math
# ... 省略具体实现
这个简化版距离生产级 CDN 还很远,但足够覆盖小型视频站的需求。
九、合规与温馨提示
技术是中性的,用法见人心。我们强烈建议:
- CDN 技术用于合法的内容分发,不用于分发盗版、侵权内容;
- 自建 CDN 节点需遵守当地法律法规,不用于绕过网络审查;
- 使用商业 CDN 时遵守其服务条款和可接受使用政策。
CDN 是互联网上最"隐形"的基础设施——你每天都在用它,但从未感知到它的存在。理解了四层缓存金字塔、GSLB 调度、缓存淘汰策略和多 CDN 调度,下次你点击播放按钮时,就能"看见"背后那一整条精心设计的分发链路。
本文由 VidDown 技术博客原创发布。VidDown 是一个免费、本地优先的在线视频解析与开发者工具站,支持多平台视频下载、格式转换、m3u8 合并等实用功能,所有数据处理均在本地完成,保护你的隐私。欢迎访问 www.viddown.cn 体验。