Day 16 Hard Video Streaming HLS/DASH CDN Transcoding

视频流系统 — 2 亿 DAU 的点播,起播 2 秒、卡顿率 0.5% 以下Video Streaming: Transcoding Pipeline, Adaptive Bitrate, CDN Design, Playback Optimization

问题场景 + 需求约束

给一个 2 亿 DAU、峰值千万级并发播放的点播平台(Netflix、YouTube、Disney+)设计后端。聊天(Day 15)是连接放大,Feed(Day 14)是读放大,视频流则是带宽放大 + 计算放大的双重难题:单条 4K 流稳态约 15–25 Mbps,千万并发就是 百 Tbps 级出口带宽——比所有公网骨干能扛的还大,不可能从中心机房直接发。同时一部 2 小时电影要转出几十个码率/分辨率/编码格式的版本,是重 CPU/GPU 作业。难点不在「存视频」,而在怎么把同一内容用最省带宽的方式,在用户网络波动下流畅地推到全球每块屏幕上

高层架构

关键是把「上传转码」和「播放分发」两条路彻底分开。左侧是离线/异步的转码 pipeline:片源切成小块、并行编码成多码率梯队、打包成 HLS/DASH、写入对象存储——慢没关系,一次转码亿次播放。右侧是在线的播放路径:客户端先拿 manifest(清单),再按网络状况逐段拉 segment;99% 的字节由 CDN 边缘命中,源站只在边缘 miss 时回源。控制面(播放 API、DRM 发证、推荐)只传元数据,不碰视频字节。

graph LR UP[创作者/片方上传] --> ING[Ingest 接入] ING --> PIPE[转码 Pipeline
切块·并行编码·QC·打包] PIPE --> OBJ[(对象存储 S3
HLS/DASH segments)] OBJ -->|预推热门| EDGE[CDN 边缘
嵌入 ISP 机房] subgraph 播放路径 CLI[播放器
ABR 引擎] -->|1 取 manifest| API[播放 API] CLI -->|2 逐段拉流| EDGE EDGE -.miss 回源.-> OBJ CLI -->|DRM 发证| DRM[License Server] end classDef off fill:#1a1a30,stroke:#ffb450,color:#e8eef5 classDef on fill:#0e2030,stroke:#5eead4,color:#e8eef5 classDef origin fill:#2a1530,stroke:#ff7ab6,color:#e8eef5 class ING,PIPE off class CLI,API,EDGE,DRM on class OBJ origin

左侧离线转码(一次),右侧在线播放(亿次);带宽全压在 CDN 边缘,源站只兜底

关键技术点

1. 转码 Pipeline 与码率梯队 — 固定梯队省 CPU,内容自适应省带宽

原理:同一片源必须转出一组 (分辨率, 码率, 编码) 组合,称为码率梯队(bitrate ladder),让播放器按网速挑。片源先按 shot/GOP 边界切成几秒一块的 chunk,扔到几百~几千台机器并行编码,再做质量校验(QC)和打包。关键洞察:不同内容的压缩难度天差地别——动画/纯色画面 1Mbps 就完美,体育/爆炸场面 8Mbps 还糊。用一刀切的固定梯队,简单内容浪费带宽、复杂内容质量不够。

方案做法代价带宽收益
固定梯队所有片用同一组码率(如 235k→5800k 固定档)简单、转码快基线
Per-Title按整片复杂度选最优梯队(凸包 convex hull)需先做复杂度分析↓~20%+
Per-Shot逐镜头选分辨率/QP,VMAF 最优编码单元×10,算力暴涨↓~30%
Trade-off:带宽是视频平台最大成本,转码算力是一次性、带宽是永久性,所以「多花 CPU 换永久省带宽」几乎总是划算——这正是 per-title / per-shot 的逻辑。但代价是 pipeline 复杂度:per-shot 要先跑分析编码再跑正式编码,编码单元数量级上升,调度/重试/QC 全都更难。质量度量必须用 VMAF(感知质量,与人眼相关性 0.9+),而不是 PSNR,否则「省了码率但用户觉得糊」。

# 并行分块转码(伪代码)
chunks = split_by_shot(source)            # 按镜头/GOP 边界切,保证可独立解码
ladder = per_title_ladder(source)         # 复杂度分析 → 凸包选码率档
for c in chunks:                          # fan-out 到数百 worker
    for rung in ladder:
        enqueue(encode_task(c, rung))     # (chunk, 分辨率, 码率, codec)
# 全部完成后 reduce:拼接 + QC(VMAF 抽检) + 打包 HLS/DASH
assemble_and_package(wait_all())          # 任一 chunk 失败只需重跑那一块
现实案例:

2. 自适应码率 ABR — 测带宽够快但抖,看缓冲稳但起播慢

原理:网络时刻波动,播放器必须逐段决定下一段拉哪个码率,在「画质高」和「不卡顿」之间实时权衡。这是 客户端 的决策(服务端只提供多档 segment)。两大流派:throughput-based 用最近几段的下载速度预测下一段该选多高;buffer-based(如 BOLA)只看当前缓冲水位——缓冲多就升档、缓冲少就降档。现代播放器是混合:起播阶段缓冲空,靠 throughput 快速试探;稳态靠 buffer 抗抖动。

Trade-off:
# 混合 ABR 选档(简化伪代码,每段决策一次)
def pick_bitrate(buf_sec, recent_bw, ladder):
    safe_bw = 0.85 * ema(recent_bw)          # 留安全余量,防抖
    cap = max(r for r in ladder if r <= safe_bw)
    if buf_sec < LOW:    return min(ladder)   # 缓冲告急 → 立刻保底,宁糊不卡
    if buf_sec > HIGH:   return up_one(cap)   # 缓冲充足 → 大胆升档
    return cap                                # 稳态跟随带宽
现实案例:

3. CDN 设计 — 自建省钱但重投入,第三方快上但贵且不可控

原理:视频 99%+ 的字节必须由离用户最近的边缘节点发出,源站只兜底。难点是命中率填充策略:热门新剧上线,与其等用户 miss 再回源(瞬间打爆源站),不如提前 push 预热到边缘。冷门长尾内容则 pull-on-demand。Netflix 走到极致——把缓存服务器(Open Connect Appliance)直接放进 ISP 机房,视频根本不走公网骨干,ISP 和 Netflix 双赢(省 ISP 的 transit 费、给用户更快)。

Trade-off:
现实案例:

4. 播放与起播优化 — manifest 越全起播越慢,segment 越短切换越灵但开销越大

原理:起播时间(TTFF)= DNS/TLS + 取 manifest + 拿 DRM 证 + 下载并解码第一个 segment。要压到 2s 内,每一步都要抠:连接预建(warm CDN connection)、manifest 瘦身、首段用低码率快速起播(先放出来再悄悄升档)、关键帧前置。Segment 时长是核心旋钮:短(2s)切换灵敏、起播快,但请求数多、编码效率低(关键帧密);长(6–10s)效率高但 ABR 反应钝、起播慢。点播常用 4–6s 折中。

Trade-off:
现实案例:

扩展与优化(增长后怎么办)

常见陷阱 + 面试问题

1. 用中心机房直接发流。 千万并发 × 几 Mbps = 百 Tbps,没有任何中心出口扛得住——CDN/边缘不是优化,是前提。面试不提 CDN 直接出局。
2. ABR 放服务端。 只有客户端实时知道自己的缓冲水位和瞬时带宽,ABR 决策必须在播放器侧;服务端只负责提供多档 segment。
3. 用一刀切固定码率梯队。 简单内容浪费带宽、复杂内容质量不够。资深答法要点出 per-title/per-shot + VMAF。
4. 忽略起播与卡顿指标。 用户流失主因是「打不开/老转圈」,不是「不够清晰」。SLO 要锚定 TTFF 和 rebuffer ratio,而非平均码率。

高频追问:① 估算千万并发的出口带宽与一部电影的转码/存储成本量级。② 一段视频怎么从上传走到能在全球播放?画出 pipeline。③ ABR throughput-based 与 buffer-based 各自失效场景,怎么混合?④ 新剧首发如何避免回源风暴?⑤ 点播 vs 直播架构差异在哪(转码实时性、延迟、缓冲策略)?

深入资源

深入思考(点击展开答案)

1. 估算:1000 万并发、平均 4Mbps,需要多少出口带宽?为什么这个数字本身就排除了「中心机房发流」?

1000 万 × 4 Mbps = 4×10⁷ Mbps = 40 Tbps(峰值 4K 更多,可达上百 Tbps)。对比:单个超大数据中心的总出口通常也就几 Tbps 到十几 Tbps 量级;全球海缆某条干线也就 Tbps 级。40 Tbps 意味着你需要把流量摊到全球成百上千个边缘点,每个点扛几十~几百 Gbps,没有任何单一中心能聚合这么大出口。这就是为什么 CDN 是架构前提而非优化——而且按第三方 CDN 的 GB 单价,这个量级的带宽费会吞掉利润,逼出 Netflix 自建 Open Connect 的经济动机。算成本时记住:转码是一次性 CPU,带宽是永久性月费,所以宁可多花算力换永久带宽。

2. Per-shot 编码把编码单元数量级抬高了,pipeline 复杂度暴涨。这笔账为什么 Netflix 划算,但对一个新创业公司可能不划算?

关键是转码成本 vs 带宽节省的摊销比。Netflix 一部热剧会被播放上亿次,per-shot 省 30% 码率 = 永久省 30% 的海量带宽费,一次性多花的编码算力被亿次播放摊到可忽略。创业公司两点不同:① 播放量低,省下的带宽绝对值小,摊不平额外编码成本;② 工程复杂度——per-shot 要先跑分析编码、维护凸包、海量编码单元的调度/重试/QC,是个专门团队的活。所以理性顺序是:先固定梯队跑起来 → 量大了上 per-title(性价比拐点)→ 头部内容才上 per-shot。这也是典型的「过早优化」反例:在 PMF 之前烧钱做 per-shot,省的带宽还不够付工程师工资。

3. 一个用户在地铁里网络从 50Mbps 骤降到 1Mbps,再恢复。纯 throughput ABR 和纯 buffer-based ABR 各会怎么表现?为什么混合更好?

纯 throughput:骤降瞬间它仍按「上一段 50M」选了高码率,这一段下不完→缓冲耗尽→卡顿;恢复后又激进升档,再遇抖动再卡,画质忽高忽低。纯 buffer-based(BOLA):只看缓冲水位,骤降时缓冲开始掉它才反应、平滑降档,抗抖更好;但代价是反应滞后,且起播阶段缓冲从 0 起、没历史信息,决策保守、起播慢。混合取两者长:起播阶段缓冲空,用 throughput 快速试探把首屏拉起来;稳态切到 buffer-based 抗抖动;并对带宽估计留安全余量(如 ×0.85)防止贴边导致频繁卡。本质是「不同阶段信息量不同,用当下最可靠的信号」。

4. 一部万众期待的新剧周五晚 8 点上线,瞬间几百万人点开。如果纯靠 pull-on-demand 会发生什么?怎么防?

纯 pull:新剧的 segment 还没在任何边缘节点,几百万请求同时 miss → 全部回源 → 源站和回源链路被瞬间打爆(这是缓存版的 thundering herd / 雪崩,见 Day 2)。结果:起播超时、大面积播放失败,正好在最该稳的时刻崩。防法:① Push 预热——已知会火,上线前夜间低谷把全部码率版本主动预填到全球边缘(Netflix Open Connect 的核心打法);② 回源加 single-flight,同一 segment 只放一个回源请求,其余等待;③ 边缘分层,区域中间层先扛一道再到源站,收敛回源扇出;④ 上线分批放量或预告倒计时打散点击峰值。核心思想和缓存预热一致:可预测的热点要主动填,别等 miss

5. 点播追求「多缓冲更流畅」,直播追求「低延迟」,这对缓冲策略是直接矛盾的。LL-HLS 怎么在不放弃 HLS 的 ABR 与 CDN 兼容性的前提下压低延迟?

矛盾在于:缓冲是抗抖动的弹药,但缓冲越深、离直播现场越远。传统 HLS 要等一个完整 segment(如 6s)生成并写入才能被拉取,光这一步就累积 segment 时长的延迟。LL-HLS 的破法:把 segment 再切成 partial segment(部分段,几百毫秒),编码器边编边发、CDN 用 chunked transfer 边收边转发,播放器不必等整段完成就能拿到最新小块——延迟从「整段」降到「小块」级。同时用 preload hint / blocking playlist reload 让播放器精准预取下一小块、避免轮询空转。关键妙处是它仍是 HTTP + HLS 框架:复用现有 CDN 缓存、复用 ABR 多码率机制,不像 WebRTC 那样另起一套不可缓存的实时通道。代价是请求更频繁、对 CDN 的 chunked transfer 支持有要求,且缓冲薄了抗抖动余量变小——本质还是在「延迟 vs 流畅」上挪了个新平衡点,而非消灭矛盾。