Skip to content

直播 CDN 基础:调度、回源、分片与成本控制

更新: 5/30/2026 字数: 0 字 时长: 0 分钟

适合读者:规模化直播业务的前端 / SRE、成本敏感团队。本文不纠缠协议细节,专讲"CDN 这层黑盒里到底在干什么、钱花在了哪、前端能做什么"。

image.png

开篇:CDN 是直播公司最贵的一行账单

任何一家做到百万并发的直播公司,财报里一定有"带宽成本"这一项,往往占技术总支出的 40%–60%

  • 1 路 1080p 观众下行约 3 Mbps
  • 100 万并发 = 3 Tbps
  • 按国内 95 计费约 20–50 元/Mbps/月
  • 单场百万直播 1 小时的带宽账单 = 20–40 万元量级

这意味着:CDN 层每优化 1%,就是一辆车的钱。这篇文档聊的每一个点,都会直接折算到这条账单上。

一、CDN 调度:用户请求如何到达"最近的"节点

1.1 经典 DNS 调度

用户访问 live.example.com 的完整链路:

浏览器 → 本地 DNS(LocalDNS,运营商)→ 权威 DNS(CDN 调度中心)

              ┌────────────────────────────┘

       根据 LocalDNS 的出口 IP
       判断运营商 + 地理位置


       返回最优边缘节点 IP


       浏览器直连边缘节点拉流

优点:标准 HTTP 协议生态天然支持,零客户端改造。

三大硬伤

  1. 调度粒度是 LocalDNS,不是用户:一个省的电信用户共用几个 LocalDNS,调度只能按 LocalDNS 的出口 IP 判断——粒度粗。
  2. DNS 劫持与污染:国内运营商劫持 DNS 改广告、返回错误节点,时有发生。
  3. DNS 缓存 TTL:通常 60s–300s,故障切换最快也要 1 分钟。直播故障 1 分钟损失巨大。

1.2 HTTPDNS:直播的标配

HTTPDNS 的本质:用 HTTP 请求直接问 CDN"我该去哪个节点",绕开 LocalDNS。

客户端 SDK ──HTTP──► httpdns.example.com/d?host=live.example.com&ip=用户真实IP


                  返回精确到用户 IP 的最优节点列表


           ["1.2.3.4", "1.2.3.5", "1.2.3.6"]  # 有序,第一个最优

四大优势

  • 用户真实 IP:调度粒度从"省级"变成"/24 子网",命中率显著提升;
  • 秒级切换:节点故障时下一次请求立刻返回新 IP;
  • 防 DNS 劫持:HTTP 响应比 DNS 应答难劫持得多;
  • 多结果返回:客户端可以并行尝试 + 故障切换。

实战要点

  • 本地缓存要短:推荐 60s,别跟传统 DNS 缓存策略学;
  • 必须有兜底:HTTPDNS 服务挂了要 fallback 到 LocalDNS,不要让这层变成 SPOF;
  • 冷启动走 IP 直连:首次请求用兜底 IP,避免 App 启动瞬间 HTTPDNS 还没返回就开始拉流。

1.3 Anycast 调度:同一个 IP,全球就近

部分头部 CDN(Cloudflare、Fastly、Akamai 等)用 BGP Anycast:全球几千个节点用同一个 IP 对外,路由器把用户请求送到"网络距离最近"的节点。

  • 优点:零调度延迟、故障天然切换;
  • 代价:节点部署成本高、BGP 能力门槛高;
  • 国内用不了 Anycast:运营商互联复杂,主流国内 CDN 仍以 DNS/HTTPDNS 为主。

1.4 调度的"最近"到底指什么

运营商维度(电信/联通/移动)、地理位置、节点健康度、节点负载、节点实时带宽水位——五个维度加权。工程上会把调度策略拆成:

候选集  = 过滤(运营商匹配 + 健康检查通过 + 带宽未超水位)
排序   = 网络距离 × 负载 × 优先级
返回   = Top N(通常 3–5 个)

二、回源:CDN 边缘找不到内容时怎么办

2.1 回源的层级

用户 → 边缘节点(miss)→ 父层节点(miss)→ 源站
                │                  │
              90%+ 命中          ~9% 命中      ~0.1% 命中
  • 边缘 miss 率:HLS 点播 <1%,直播新热流 10%–30%;
  • 回源带宽:打到源站的带宽 = 边缘 miss × 观众请求量,这部分带宽成本最高(源站出口最贵);
  • 回源风暴:热点直播刚开播、所有边缘节点同时 miss,千路请求瞬间打爆源站。

2.2 回源收敛:直播的命门

回源收敛有两层含义:

层内收敛(同一边缘节点):一个节点上 1000 个用户请求同一个切片,节点只向父层发 1 个请求,其它 999 个等结果。

用户 A ─┐
用户 B ─┤──► 边缘节点 ──► 父层(合并为 1 次回源)
...     │
用户 N ─┘

边缘节点实现:合并请求(request coalescing),Nginx 的 proxy_cache_lock、各家 CDN 的"回源合并"开关。

层间收敛(边缘 → 父层 → 源站):多个边缘节点回源时先合并到父层,再统一回源站。

工程结果:源站的回源 QPS 从百万降到千级,回源带宽降低 2–3 个数量级

2.3 主备源站

直播源站必须双活:

推流端 ──► 源站 A(主)

   └────► 源站 B(备)   ← 双推或 RTMP relay 复制

CDN 配置 主备回源

preferred_origin: origin-a.example.com
backup_origin:    origin-b.example.com
health_check: /healthz, timeout=1s, interval=5s
failover_threshold: 3 consecutive failures

要点

  • 双推 > relay:主播端直接推两路,任一源站挂了另一路完全不受影响;
  • 时间戳一致:主备源站的切片时间戳必须一致,否则切换时播放器会花屏;
  • 回源幂等:主备来回切时同一切片可能被请求两次,源站要能返回同样内容。

2.4 热流预热

典型场景:某明星 8 点开播,预计百万观众。

  • 不预热:开播瞬间几千个边缘节点集体 miss,源站被打爆,前 30s 观众全部卡顿;
  • 预热:开播前 5 分钟由调度系统主动把推流"引导"到所有目标区域的边缘节点 pre-populate。

前端视角:

  • 预热是服务端 CDN 团队做的,前端配合的事情是提前下发 stream URL,让客户端 App 做 DNS 预解析、TCP 预连(见第五节)。

2.5 防盗链与回源鉴权

免费看就是偷钱,直播公司的防盗链要求极高:

  • URL 签名?sign=xxx&expire=1777909555,CDN 在边缘层校验,过期/篡改直接 403;
  • Referer 黑白名单:限制只有自家域名能嵌;
  • 回源鉴权:CDN 回源时带 Token 头,源站验证,避免 CDN 被伪造请求拉走流。

三、分片协议:切片大小就是延迟本身

3.1 HLS / DASH 的切片模型

HLS 的 #EXTINF 和 DASH 的 <SegmentTemplate> 决定每一片的时长。切片大小 = 延迟的主要来源

切片时长端到端延迟典型值首屏耗时CDN 友好度
10s(HLS 默认,Apple 旧推荐)20–30s3–5s极高
6s(HLS Apple 现推荐)10–20s2–4s
4s8–12s1–3s
2s4–8s1–2s
1s(LL-HLS Partial Segment)2–5s<2s需 HTTP/2 CDN
200ms(LL-HLS 极限)1–2s<1s需 HTTP/2 + CMAF 支持

3.2 为什么不能无限切小

切片越小,延迟越低,但:

  • 回源频次 ×N:6s 切到 200ms,回源 QPS 涨 30 倍,CDN 水位和成本全涨;
  • HTTP 开销占比飙升:一个请求的 TCP/TLS/Header 固定开销约几 KB,200ms 切片的 payload 也就几十 KB,有效载荷比下降
  • m3u8 列表刷新频繁:每切片都要刷新播放列表,HLS 3 的做法根本撑不住,必须用 LL-HLS 的 Blocking Playlist Reload
  • 老 CDN 不支持:LL-HLS 要 HTTP/2 服务端推送或长轮询,传统 CDN 要么不支持要么加钱。

3.3 CMAF + LL-HLS + LL-DASH:低延迟的正确姿势

CMAF(Common Media Application Format)的核心:用同一份 fMP4 分片同时喂 HLS 和 DASH。配合 Chunked Transfer Encoding,一个切片还没生成完就能边生成边下发。

传统 HLS:等切片完整生成 → 写 m3u8 → 用户下载
        ─────────────────── 6s 延迟 ───────────►

LL-HLS + CMAF:
 推流入 ──► 每 200ms 一个 Part(fMP4 chunk)

            └──► 边生成边通过 HTTP chunked 下发
                 用户拿到第 1 个 chunk 就能开始解码

CDN 要求

  • 支持 HTTP/2(LL-HLS Preload Hint + Blocking Reload 用 HTTP/2 协议特性);
  • 支持 chunked response 透传:不等 body 完整就流式回给用户;
  • 切片粒度的回源合并:而不是"等完整切片再合并"。

老 CDN 不一定全支持,上 LL-HLS 前一定要压测。

3.4 直播不同档位切片策略

一个直播业务里往往多套并存:

观众群协议切片延迟目标
VIP/连麦WebRTC无切片<1s
活跃观众LL-HLS1–2s Part2–5s
普通观众标准 HLS4–6s8–15s
海外/弱网标准 HLS6s15–30s

前端 SDK 根据用户标签/网络选档位,CDN 侧分别做缓存策略。

四、带宽计费与成本优化

4.1 两种主流计费模式

流量计费:按实际下行总字节数,通常 0.15–0.30 元/GB(国内,海外更贵)。

  • 适合:流量小、波动大、突发场景。

带宽计费(95 峰值):一个月内每 5 分钟采样一次出口带宽,去掉最高的 5%,按第 95 百分位收费,通常 20–50 元/Mbps/月

  • 适合:流量大、峰值稳定、直播公司主流。
  • 算例:峰值 1 Tbps × 30 元 × 1024 = 月均 3000 万级

哪种便宜

  • 月均带宽利用率 > 40% → 95 计费便宜;
  • 利用率 < 40% → 流量计费便宜;
  • 直播公司有晚高峰(8–11 点),日内波动大但月度波动小,95 计费几乎是唯一选择

4.2 成本优化的五条经典路径

(1) 多 CDN 分流削峰

总流量 10 Tbps 峰值
  分配给 CDN-A:4 Tbps (A 的 95 峰值 = 4T)
         CDN-B:3 Tbps (B 的 95 峰值 = 3T)
         CDN-C:3 Tbps (C 的 95 峰值 = 3T)

核心:每家 CDN 的峰值各自 95 计费,总和往往比集中到一家便宜 10%–20%。

(2) 错峰调度

把不同时区/国家的流量错开峰值,月 95 峰值就能压低。海外业务尤其受用。

(3) 编码优化降码率

  • H.264 → H.265:码率省 40%,带宽成本直接省 40%,代价是转码成本上涨 2 倍(转码远比带宽便宜);
  • H.265 → AV1:再省 20%–30%,代价更大;
  • 动态码率(Content-Aware Encoding):静态画面(游戏直播的 UI、PPT 共享)码率自动压低,省 10%–30%。

(4) ABR 默认码率下调

大多数用户并不能分辨 1080p 和 720p。默认档位从 1080p 改成 720p:

  • 码率从 3 Mbps → 1.5 Mbps,直接省 50% 带宽
  • VIP/Wi-Fi 用户自动上 1080p,体验不受影响。

(5) P2P 辅助分发

  • 用户之间互相传切片,命中率 30%–70%;
  • 能把 CDN 出口带宽降 30%–60%
  • 实现成本高(需自研 P2P SDK + Tracker),头部平台自研,中小团队买第三方(如字节 P2P、网易 P2P-CDN)。

4.3 成本归因:每一分钱花在谁身上

直播带宽账单 = Σ(每家 CDN 的 95 峰值 × 单价)

推导:
  单 CDN 峰值  = Σ 活跃观众 × 平均码率 × 该 CDN 份额
  活跃观众    = 开播场次 × 场均观众 × 同时在线率
  平均码率    = Σ(档位码率 × 档位占比)

前端可以影响的三个变量:

  • 档位占比:ABR 策略、默认档位;
  • 同时在线率:首屏速度、卡顿率、体验留存;
  • CDN 份额:调度分流策略。

五、前端 SDK 层:就近接入 + 多 CDN 容灾

5.1 为什么前端要关心 CDN

传统观点:CDN 是服务端/SRE 的事,前端只管拉 URL。错——几个关键动作必须前端做:

  • HTTPDNS 的发起方是客户端
  • 多 CDN 切换的决策和执行在客户端
  • 首屏优化的 DNS/TCP 预热在客户端
  • QoS 上报是客户端发出去的,驱动后续调度优化

5.2 一张"多 CDN 容灾"架构图

   ┌──────────────────────────────────────────────┐
   │ 前端 SDK                                      │
   │                                              │
   │  ┌─────────────┐   ┌──────────────────────┐  │
   │  │ URL 获取层   │──►│ HTTPDNS 解析          │  │
   │  │ (业务 API)   │   │ 返回 CDN 列表 + IP    │  │
   │  └─────────────┘   └──────┬───────────────┘  │
   │                           ▼                  │
   │  ┌──────────────────────────────────────┐   │
   │  │  CDN 优选器 (CDNSelector)             │   │
   │  │  - 历史成功率 + 延迟 + 用户分桶        │   │
   │  │  - 输出有序的 CDN 列表                │   │
   │  └──────┬───────────────────────────────┘   │
   │         ▼                                    │
   │  ┌──────────────────────────────────────┐   │
   │  │  播放器                               │   │
   │  │  首选 CDN 拉流                        │   │
   │  │  ├─ 首屏超时 → 切次选                  │   │
   │  │  ├─ 连续卡顿 → 切次选                  │   │
   │  │  └─ 回源失败/403 → 切次选              │   │
   │  └──────┬───────────────────────────────┘   │
   │         ▼                                    │
   │  ┌──────────────────────────────────────┐   │
   │  │  QoS 上报                             │   │
   │  │  首屏/卡顿/切换事件 → 后端调度大脑      │   │
   │  └──────────────────────────────────────┘   │
   └──────────────────────────────────────────────┘

5.3 CDN 切换的触发条件

js
// 伪代码:CDN 健康度判定
const CDN_FAILOVER_RULES = {
  firstFrameTimeout: 5000,       // 首屏 5s 还没来
  bufferingDurationMs: 3000,     // 连续卡顿 3s
  bufferingCountIn60s: 3,        // 1 分钟卡 3 次
  http4xxCount: 2,               // 两次 4xx(签名错、鉴权失败)
  http5xxCount: 1,               // 一次 5xx
  dnsResolveFailCount: 1,        // DNS 解析失败
}

触发后:

  1. 销毁当前 player 实例,避免旧实例继续消耗带宽;
  2. 取下一个 CDN URL(列表已在初始化时排好序);
  3. 构造新 player,带上 origin_cdn_failure 标记供上报;
  4. 设置切换冷却期:60s 内不再切回已失败的 CDN,避免震荡。

5.4 最小化切换抖动:并行拉流 + 热切

高端玩法是同时连两个 CDN,主路渲染、备路预热:

  • 主 CDN 出问题瞬间切过去,无需重建 TCP/TLS/ABR 协商;
  • 代价:备路带宽翻倍(通常只在"高价值用户"或"开播前 30s"启用)。

5.5 首屏极致优化

几个能在 100–500ms 内看到收益的前端动作:

  • DNS 预解析<link rel="dns-prefetch" href="//cdn.example.com"> + HTTPDNS 预热;
  • TCP 预连<link rel="preconnect" href="//cdn.example.com">,进直播间前提前 3 次握手;
  • HTTP/2 连接复用:m3u8 和 ts 切片走同一个连接,省 RTT;
  • Early Data(0-RTT):TLS 1.3 支持,HTTPDNS/CDN 需打通;
  • 提前拉 m3u8:进入房间列表就预取 m3u8,点进房间后只拉切片;
  • GOP 缓存:服务端/边缘节点保留最新一个 GOP,新用户接入从 I 帧开始,首帧能快到 200ms 内。

5.6 用户分桶:千万级业务的必备

一次性全量切 CDN 有风险(可能次选也有问题)。推荐分桶 + 灰度:

js
// 伪代码:按 user_id % 100 决定首选 CDN
function pickCDN(userId, cdnConfig) {
  const bucket = hash(userId) % 100
  // 80% → CDN-A(主力、便宜)
  if (bucket < 80) return ['cdn-a', 'cdn-b', 'cdn-c']
  // 15% → CDN-B(验证、分流)
  if (bucket < 95) return ['cdn-b', 'cdn-a', 'cdn-c']
  // 5%  → CDN-C(兜底、新厂商验证)
  return ['cdn-c', 'cdn-a', 'cdn-b']
}

配置下发走后端(配置中心),前端 SDK 定时轮询。SRE 可以秒级调整分桶比例,故障时 1 分钟内把流量从 A 搬到 B

5.7 QoS 上报:调度大脑的数据源

客户端必须上报的关键事件:

事件字段用途
stream_startuser_id, cdn, url, ts会话起点
first_framecost_ms首屏分桶统计
bufferingduration_ms, cdn卡顿率指标
cdn_switchfrom, to, reason切换成功率
abr_switchfrom_bitrate, to_bitrate, reasonABR 策略评估
errorcode, cdn, url故障定位
stream_endduration_ms, total_bytes完播率、带宽估算

这些数据是调度系统的基础——没有前端上报,后端调度只能猜。

六、前端 SDK 最小骨架

js
class LivePlayer {
  constructor({ streamId, userId }) {
    this.streamId = streamId
    this.userId = userId
    this.cdnList = []
    this.currentCdnIndex = 0
    this.failedCdns = new Set()
    this.qos = new QoSReporter(userId)
  }

  async init() {
    // 1. 从后端拿流地址和 CDN 列表
    const { cdnList } = await api.getStreamUrl(this.streamId)
    this.cdnList = this.rankCDNs(cdnList)  // 本地历史成功率再排一次

    // 2. HTTPDNS 并行解析 Top 3 CDN
    await Promise.all(
      this.cdnList.slice(0, 3).map(cdn => httpdns.resolve(cdn.host))
    )

    // 3. 预连
    this.cdnList.slice(0, 2).forEach(cdn => {
      const link = document.createElement('link')
      link.rel = 'preconnect'
      link.href = `https://${cdn.host}`
      document.head.appendChild(link)
    })

    this.play(0)
  }

  play(index) {
    const cdn = this.cdnList[index]
    this.currentCdnIndex = index
    this.qos.report('stream_start', { cdn: cdn.name, url: cdn.url })

    this.player = new Player({
      url: cdn.url,
      isLive: true,
      plugins: [HlsPlugin],
    })

    this.player.on('firstFrame', ms => this.qos.report('first_frame', { cost_ms: ms, cdn: cdn.name }))
    this.player.on('buffering', d => {
      this.qos.report('buffering', { duration_ms: d, cdn: cdn.name })
      if (d > 3000) this.switchCDN('buffering')
    })
    this.player.on('error', err => {
      this.qos.report('error', { code: err.code, cdn: cdn.name })
      if (this.isFatalError(err)) this.switchCDN('error')
    })
  }

  switchCDN(reason) {
    const old = this.cdnList[this.currentCdnIndex]
    this.failedCdns.add(old.name)

    const next = this.cdnList.findIndex(
      (c, i) => i > this.currentCdnIndex && !this.failedCdns.has(c.name)
    )
    if (next === -1) {
      this.qos.report('fatal', { reason: 'all_cdn_failed' })
      return
    }

    this.qos.report('cdn_switch', {
      from: old.name, to: this.cdnList[next].name, reason,
    })
    this.player.destroy()
    this.play(next)

    // 60s 后把 old 从黑名单移除
    setTimeout(() => this.failedCdns.delete(old.name), 60_000)
  }
}

七、速查表

问题看哪里优先动作
某地区大面积卡顿CDN 健康度看板分桶切流到备用 CDN
首屏慢QoS first_frame 分桶DNS/TCP 预热、GOP 缓存
带宽成本超预算95 峰值分布、ABR 档位占比降默认码率、H.265、多 CDN 分流
源站压力大回源 QPS、回源带宽边缘合并、父层聚合、热流预热
新 CDN 上量难分桶占比、错误率灰度分桶从 1% 起步
境外用户卡跨境 CDN 选型海外专属 CDN、Anycast 节点
切片延迟大切片时长、m3u8 刷新上 LL-HLS + CMAF

结语

CDN 这一层看起来是"服务端和 SRE 的事",但前端是直接决定带宽成本和用户体验的最后一公里。调度、回源、分片的每一个决策最终都会落到客户端的一次 fetch、一次 <video>.play()、一次 player.destroy() 上。

对一家规模化直播公司来说,CDN 基础能力的强弱决定了:

  • 体验天花板:首屏、卡顿、低延迟能做到什么程度;
  • 成本下限:同样规模能比同行便宜多少;
  • 故障恢复:大促、明星开播、热点事件时能不能扛住。

把 HTTPDNS、回源收敛、LL-HLS、多 CDN 切换这几块的心智模型建起来,后续无论是选型、谈判还是故障复盘,都能站在一个更高的视角说话。这就是"懂 CDN"对前端工程师的真实价值。