提示

返回博客列表

一个视频从服务器到你屏幕,中间经历了什么?聊聊 CDN 架构与视频分发全链路

一个视频从服务器到你屏幕,中间经历了什么?聊聊 CDN 架构与视频分发全链路

你点击播放按钮,零点几秒后画面就出来了。这短短几百毫秒里,一个视频文件穿越了 DNS 解析、GSLB 调度、边缘节点命中/回源、TCP 握手、TLS 加密、HTTP Range 请求……背后是一整套全球分布的 CDN(内容分发网络) 在协同工作。本文把视频分发的完整链路从头拆到尾,并深入 CDN 的调度算法、缓存策略和成本优化。

TL;DR:CDN 不是一台服务器,而是一个全球分布的缓存网络。核心架构:GSLB(全局负载均衡)把用户调度到最近的边缘节点 → 边缘节点有缓存则直接返回(命中),没缓存则回源拉取 → 逐级缓存到区域节点和边缘节点。视频 CDN 的特殊性:文件大、QPS 高、需要支持 Range 请求、需要防盗链。多 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 体验。

想亲手试试?用 VidDown 一键解析下载

粘贴视频链接即可解析,多平台支持、网页端即用;下载桌面客户端解锁海外平台本地解析,开通会员更享不限次下载。

顶部