00 · 设计推演:从零造一个直播系统

“为什么直播延迟总是 3-10 秒?不是做不到实时——是百万并发和实时,你只能选一个。“


开场:先算带宽

老陈:需求:一个主播,100 万观众,720p 画质。先把账算清楚。

小林:720p 大概 1.5 Mbps?

老陈:对。那主播要上传多少数据?

小林:……要看架构?

老陈:先假设最简单的:主播直接发给每个观众。

小林:100 万 × 1.5 Mbps = 1.5 Tbps。不可能。

老陈:家庭宽带上行通常只有 30-100 Mbps。差了五万倍。

这就是直播系统所有设计的起点:主播上行只有一条流,观众有一百万条。


v0:主播直连(Mesh)

主播 ──► 观众1
     ──► 观众2
     ──► ...
     ──► 观众100万

主播上行 = 100万 × 1.5 Mbps = 1.5 Tbps  ★ 不可能

失败:

#问题数据
1主播上行带宽需要 1.5 Tbps,实际只有 50 Mbps
2NAT 穿透大部分观众在 NAT 后面

问题 2 其实有解(WebRTC 的 STUN/TURN),但问题 1 是物理限制,绕不过去。

老陈:注意 v0 虽然荒谬,但它暴露了核心矛盾:

主播只有一条上行,观众有一百万条下行。

后续所有架构,本质上都是在解决”如何把 1 变成 100 万”。


v1:中央服务器转发

改动:主播只推一路到服务器,服务器复制给所有观众。

主播 ──► 服务器 ──► 观众1
                 ──► 观众2
                 ──► ...

主播上行: 1.5 Mbps  ✓
服务器下行: 100万 × 1.5 Mbps = 1.5 Tbps  ★

失败:

单台服务器出口带宽通常 10-40 Gbps
需要 1.5 Tbps → 需要几十上百台服务器

但至少思路对了:主播上行解决了。剩下的是服务器集群的问题。


v2:服务器集群

改动:加机器。

                  ┌─► 边缘服务器1 ──► 2万观众
主播 ──► 中心 ─────┼─► 边缘服务器2 ──► 2万观众
                  └─► ...(50 台)

中心出口:  50 × 1.5 Mbps = 75 Mbps   ✓
边缘总出口: 1.5 Tbps(分摊到 50 台,每台 30 Gbps)

改进:能撑住了。

失败:

#问题说明
1跨地域延迟观众在北京,服务器在广州 → RTT 50ms+
2单点故障中心挂了全挂
3带宽成本高数据中心带宽比 CDN 贵得多
4扩展困难观众涨到 500 万要重新规划

v3:CDN 分发

改动:把分发交给 CDN(内容分发网络)。

主播 ──► 源站 ──► CDN 中心节点 ──► 省级节点 ──► 边缘节点(运营商机房)──► 观众

★ 边缘节点离用户最近(同城甚至同运营商)

CDN 的核心价值:缓存复用

没有 CDN:  100 万观众 = 100 万条独立流
有 CDN:    边缘节点缓存一份,同节点的 N 个观众共用一条流
          → 回源流量只有"边缘节点数 × 码率"

★ 假设 1000 个边缘节点:
  回源流量 = 1000 × 1.5 Mbps = 1.5 Gbps  (而不是 1.5 Tbps)
  ★ 降低 1000 倍

改进:

改进说明
延迟低边缘节点离用户近
成本低CDN 带宽比 IDC 便宜
扩展容易CDN 厂商有海量节点
抗突发CDN 天然能吸收流量尖峰

★ 但 CDN 的复用机制有个前提:内容必须是”完整的数据块”。

CDN 的工作方式:
  边缘节点缓存一个"对象"(文件/切片)
  多个用户请求同一个对象 → 复用
  
★ 如果你来一个字节就转发一个字节,就没法形成可缓存的对象

这就是延迟的来源。


v4:延迟从哪来

为了适配 CDN,必须用”切片”(HLS):

服务端把流切成小片段(每个 6-10 秒)
  segment0.ts, segment1.ts, ...
生成播放列表 m3u8
客户端定时拉 m3u8,下载最新的 ts 播放

延迟构成:

① 切片等待:     必须等一个切片完整生成
                6 秒切片 = 至少 6 秒延迟
② 播放列表刷新: 客户端每 3-10 秒拉一次 m3u8
③ 客户端缓冲:   为了抗抖动,缓存 2-3 个切片(12-30 秒)
④ 传输:         几百毫秒

总计: 默认配置下 20-40 秒,优化后 5-20 秒

★ 关键洞察:缓冲是延迟的主要来源,但缓冲是必要的。

为什么不减小缓冲?

网络是抖动的:
  · 带宽忽高忽低
  · 偶尔丢包、重传
  · 切换 WiFi / 4G

缓冲的作用: 用"提前下载"吸收"网络抖动"
  · 缓冲 30 秒 → 能扛住 30 秒的网络中断
  · 缓冲 0.5 秒 → 网络卡 0.5 秒就卡住了

★ 这就是核心权衡: 延迟 vs 流畅度
  直播:      流畅优先(缓冲大,延迟高)
  视频会议:  延迟优先(缓冲小,允许偶尔卡)

老陈:这是全书反复出现的那个权衡的又一次出现:

“用一致性换可用性”、“用写放大换读放大”、“用延迟换流畅度”。

当你看到一对矛盾时,先问:“我的场景更需要哪一边?” 答案清楚了,参数是唯一确定的。


v5:降低延迟的三条路

如果业务确实需要低延迟(直播带货、在线教育、体育赛事),有三条路:

路 A:优化 HLS(LL-HLS)

改动:
  ① 部分切片(Partial Segment): 200-500ms
  ② 阻塞式播放列表刷新: 服务端 hold 住请求,有新内容立即返回
  ③ 预加载提示

效果: 延迟从 5-20 秒降到 2-5 秒
代价: 服务端复杂度上升;CDN 支持不好(很多 CDN 不支持 LL-HLS)

路 B:换协议(HTTP-FLV)

改动: 不切片,流式传输
     服务端收到一个 tag(帧)就立即发给客户端

效果: 延迟 1-3 秒
代价: 需要专门的播放器(flv.js);iOS Safari 不支持
     ★ CDN 可以部分复用,但不如 HLS

路 C:换传输层(WebRTC)

改动: UDP + RTP/RTCP
     帧级别传输,无切片
     有拥塞控制、FEC、NACK

效果: 延迟 50-500ms
代价: ★★ CDN 无法复用!每个观众一条独立流
     带宽成本上升 5-10 倍
     需要 SFU 集群(而不是 CDN)

★ 三条路的本质差异:

LL-HLSHTTP-FLVWebRTC
延迟2-5s1-3s<500ms
CDN 复用★★★★★★★★ 无
成本低中高
兼容性★★★★★★★★★★★★
扩展 100 万★★★★★★★★★★★

老陈:这里有个绕不开的矛盾,值得单独说:

CDN 的缓存复用要求”数据块化”,而低延迟要求”立即发送”。

这是本质冲突,没有完美解法。

所以选型问题最终变成:你的业务能接受什么延迟?

5-10 秒可接受 → HLS + CDN(成本最低,最稳)
1-3 秒        → HTTP-FLV(性价比高)
<1 秒         → WebRTC(成本高 5-10 倍,需要互动才值)

v6:完整的现代直播架构

实际生产系统通常是多种协议并存:

主播 ──RTMP/WebRTC──► 接入层(边缘节点)
                          │
                          ▼
                    中心(转码/录制/审核)
                          │
        ┌─────────────────┼─────────────────┐
        ▼                 ▼                 ▼
   HLS 分发           HTTP-FLV 分发      WebRTC SFU
   (iOS/默认)         (PC/Android)       (连麦/超低延迟)

★ 为什么推流用 RTMP 而不用 WebRTC?

推流(上行)和拉流(下行)的需求不同:

上行:  只有一路,对延迟不敏感(1-3 秒可接受)
       → RTMP 成熟、稳定、CDN 支持好
下行:  百万路,场景多样
       → 按客户端能力/需求选不同协议

★ 这是"按链路方向分别优化"的思路
  上行的瓶颈是"主播带宽",下行的瓶颈是"CDN 复用"
  两者约束不同,解法就该不同

★ 这一节真正的收获

需求: 1 个主播 → 100 万观众,720p
  │
  ├─ v0 主播直连
  │    失败: 需要 1.5 Tbps 上行(实际 50 Mbps,差 5 万倍)
  ↓
  ├─ v1 中央服务器转发        ← 主播上行解决(1.5 Mbps ✓)
  │    失败: 服务器出口 1.5 Tbps,单机扛不住
  ↓
  ├─ v2 服务器集群
  │    失败: 跨地域延迟;带宽成本高;扩展困难
  ↓
  ├─ v3 CDN 分发              ← ★ 缓存复用,回源流量降 1000 倍
  │    代价: 需要"数据块化" → 切片 → ★ 延迟 5-20 秒
  ↓
  ├─ v4 分析延迟来源
  │    ★ 核心权衡: 缓冲越大越流畅,但延迟越高
  ↓
  └─ v5 降延迟三条路
       LL-HLS(2-5s) / HTTP-FLV(1-3s) / WebRTC(<500ms)
       ★ 延迟 vs CDN复用 vs 成本,三者不可兼得

五个可以迁移的思维模式

① 先算物理量的账

1.5 Tbps vs 50 Mbps,差 5 万倍——这个数字直接判了 v0 死刑,也指明了后续方向。 在架构设计前,先把带宽、QPS、数据量算出来。很多方案在算术阶段就被淘汰了。

② 复用是把 N 变成 1 的关键

CDN 的核心不是”更多的机器”,而是”缓存复用”。 同样的思路:连接池、线程池、Buffer Pool、CDN——都是”用共享代替独占”。

③ 每个优化都有前提

CDN 复用的前提是”内容可缓存”,也就是”数据块化”。 当你用一个机制时,问清楚它的前提条件是什么,代价是什么。

④ 矛盾不可消除,只能按场景选边

延迟 vs 流畅度、延迟 vs CDN 复用 vs 成本。 先明确业务优先级,再选方案。没有”都想要”的选项。

⑤ 按链路方向分别优化

上行(1 条)和下行(100 万条)约束完全不同,解法就该不同。 先分解问题,再分别优化。这是第 2 章”分治”思想在架构层面的应用。


现在,轮到你了

练习:我们已经解决了”分发”。还差两个关键问题:

问题 A:主播网络不好(比如 4G 信号弱,带宽从 2 Mbps 掉到 300 Kbps),画面质量怎么办?

问题 B:观众网络不好,怎么保证他不卡?

提示(不要用,先自己想):

  • A 和 B 的解法一样吗?(★ 关键区别在哪?)
  • 主播端能做什么?(想想”自适应码率”——但推流是单向的,谁来决定码率?)
  • 观众端能做什么?(多码率?动态切换?切换时要不要重新缓冲?)
  • HLS 的 m3u8 里为什么能写多个不同码率的播放列表?
  • WebRTC 的 GCC 拥塞控制是怎么做的?它跟 HLS 的自适应码率有什么本质区别?

老陈:答案的关键区别:

  • 观众端自适应(ABR):服务端预先转码出多档码率,客户端根据自身带宽选。 ★ 这是 HLS/DASH 的做法,成熟但需要服务端转码(成本高)
  • 主播端自适应:推流侧根据上行带宽动态调整编码码率。 ★ 只能改主播自己的,观众都得跟着降级
  • WebRTC 的做法:拥塞控制 + 动态码率 + 可以为每个观众单独调整(SFU 转发不同层)

这里有个有意思的点: HLS 是”服务端转 N 份,客户端选”;WebRTC 是”实时测量,实时调整”。 前者简单但浪费转码资源,后者精确但复杂度高。

又是一对权衡。


下一节

01-架构心法:决策框架与造轮子的时机

这一节我们用直播系统练了”从约束推导架构”,下一节把这些经验提炼成可复用的决策框架——特别是那个最关键的问题:什么时候该自己造?

带着这个问题去读:如果你要做直播,你会自己写 CDN 吗?为什么?