一个视频已经有 720p 的前两个片段,但第三个片段仍在重试。此时发布清单会让播放器拿到一个合法文本,却在播放途中遇到 404。转码成功率、任务去重和播放可用性不是同一个指标;对外确认点应放在必需分段与清单构成完整版本之后。

YouTube、Netflix 在这里表示上传转码与点播分发的题型。候选系统不涉及直播、版权采购、DRM 设计或真实公司的编码参数。实验只验证任务和清单的发布关系,分段是协议测试字节,不能拿它们宣称完成真实视频解码或播放。

上传之后是一张依赖图

POST /videos 创建视频和上传会话,POST /videos/{id}/complete 确认原始对象可用,GET /videos/{id}/status 返回阶段状态,GET /videos/{id}/playback 只对授权用户返回已发布版本的主清单。视频元数据记录 video_id, owner, source_version, state, published_version。

转码任务键是 (video_id, source_version, profile, segment),不能只用视频 ID,因为不同原始文件版本和编码规格产生不同输出。任务表记录状态、尝试次数、租约和输出校验信息;发布表对 (video_id, release_version) 唯一,并以条件更新推进当前发布指针。工作节点租约过期后可能仍在运行,版本化输出键和提交条件需要防止旧工作者覆盖新结果。

处理 DAG 可以依次经过原文件探测、必要校验、分规格编码、分段、清单组装、发布。不同实现可能把编码与分段合并,核心是不让清单组装越过未满足的依赖。音轨、字幕、封面是否必需应写成集合,不用“所有任务都成功”这样的模糊条件,因为新增一个可选规格不应无意阻塞全部视频。

容量由观看时长主导

教学假设每天上传 10 万个视频,平均十分钟,原始平均码率 8 Mbit/s。单个源文件约 600 × 8 / 8 = 600 MB,每天原始新增约 60 TB。输出三档平均码率 0.8、2、5 Mbit/s,总和 7.8 Mbit/s,对应每视频 585 MB、每天 58.5 TB。两份副本、保留 30 天的源加输出约 118.5 × 2 × 30 = 7110 TB,未含临时文件和编码器日志。

若每档四秒一个分段,十分钟每档 150 段,三档为 450 段;每日约 4500 万个段对象。只盯 TB 容量会漏掉 PUT 请求数、清单引用数量和小对象管理开销。分段加长到八秒会减少对象数,但可能增加启动所需下载时间和码率切换粒度;低延迟场景还受独立分段和播放器策略影响,不能只根据对象数选长度。

假设峰值同时观看 10 万路、平均选择 2 Mbit/s,用户侧出口约 200 Gbit/s。CDN 字节命中率 95% 时,源站仍需约 10 Gbit/s;命中率降到 80%,源站增为 40 Gbit/s。新热门视频和随机拖动容易降低命中,单纯按全日平均命中率采购源站容量会低估峰值。

四秒、2 Mbit/s 的分段约 1 MB。播放器积累十二秒缓冲约需 3 MB。如果可用下载带宽 4 Mbit/s,理想下载这些字节需六秒;若启动必须先攒满十二秒缓冲,首帧目标就不可能是一秒。播放器可先以更低码率或更少缓冲启动,代价是画质或抗抖动能力。这是预算推导,不是实测播放器表现。

不可变输出与原子发布指针

flowchart LR
    U[上传完成] --> P[探测与校验]
    P --> A[低码率任务]
    P --> B[中码率任务]
    P --> C[高码率任务]
    A --> S[分段与校验]
    B --> S
    C --> S
    S --> M[清单组装 必需段全部就绪]
    M --> V[(发布版本指针)]
    V --> D[CDN与播放器]

工作节点重试相同任务时,先检查有效输出和完成记录。可以重复计算,却不能重复发布两个用户可见版本。输出写到带源版本、规格与任务代次的临时键,验证后记录最终键。确定性键便于复用,但编码器版本或参数变化可能改变结果,因此必须纳入键或任务版本,不能只比较文件名。

清单组装读取一份固定输出集合,校验段数、顺序、时长、必需音轨和对象可读性,再生成不可变清单。最后事务性更新发布指针。对象存在性预检不保证之后永远存在,因此发布后回收器还须把有效清单的引用视为根;“发布后立即清理临时目录”若误伤最终段,会把已经成功的视频破坏掉。

RFC 8216 定义了 HLS 清单中的分段 URI、时长等标签,协议规定与业务发布事务需要分开理解。EXTINF 描述分段时长,EXT-X-TARGETDURATION 给出约束,点播结束可用 EXT-X-ENDLIST。清单符合语法仍不代表分段字节可解码,也不代表每一段 URL 都可访问。

sequenceDiagram
    participant W as 转码工作节点
    participant D as 任务库
    participant O as 输出对象
    participant P as 发布器
    W->>O: 写指定版本的段
    W->>D: 提交任务完成
    Note over W,D: 响应丢失后任务重领
    W->>D: 查询同一任务键
    D-->>W: 已完成 复用输出
    P->>D: 核对必需任务集合
    P->>O: 检查清单与分段
    P->>D: 条件提交发布指针

故障恢复不能只重跑全片

原图对象丢失时,重新运行编码器无济于事,需要从副本恢复或要求重传。某个规格失败时重试该分支即可,不必让所有已完成规格重做;如果编码器升级导致结果不兼容,应创建新任务版本完整构建,再切换发布指针,而不是把一部分旧段和新段混入同一清单。

发布前崩溃留下的对象可以按任务引用和宽限期回收。发布后状态丢失则更危险,恢复发布库时必须核对清单集合,旧指针回退需要确保旧段还在。删除视频先关闭播放授权,再异步清理清单和分段;长寿命 CDN URL 的剩余有效期需要纳入删除承诺。

当转码积压增加,首先限制新上传或减少可选规格,观察源文件到首个可用发布的年龄。播放路径不应该同步等待转码队列。监控分支耗时、重试放大、发布失败、分段 404、首帧时间和缓冲停顿,后两个指标来自真实播放器,而非转码工作节点的 CPU 利用率。

本地 HTTP 证明清单能找到哪些字节

examples/system-design/labs/16/video.py 对三个分段任务各提交两次,SQLite 唯一键将完成记录保留为三行;发布器执行两次,发布表仍一行。生成包含三个相对分段 URI 的清单后,真实本地 HTTP 服务返回清单,客户端逐项下载并核对字节。随后删除第二段,访问得到 404,证明清单仍在不代表发布完整性仍在。

1
2
python3 examples/system-design/labs/16/video.py
python3 examples/system-design/labs/16/video.py --unsafe

默认退出 0,负例识别已发布清单存在缺段后退出 2。原始 HTTP 结果、任务数量、版本与源码哈希位于 examples/system-design/evidence/16/。实验绑定 127.0.0.1 的随机端口;沙箱禁止监听时需要允许这一局部测试。没有安装或运行编码器,.ts 文件是标有 PROTOCOL-FIXTURE 的字节,不是 MPEG-TS,原始输出明确标记 playback=NOT_TESTED_PROTOCOL_FIXTURES。

这份证据支持任务去重、唯一发布和引用完整性检测,不支持编码质量、播放器兼容、ABR 切换或 CDN 性能。真实转码验收还需要固定编码器版本、已知输入样本、解码校验和播放器测试;不能把清单文本展示冒充视频播放。

面试前五分钟确认点播范围,八分钟用观看时长和码率推容量,十二分钟画 DAG 与分发,十五分钟处理重试、半成品和回收,最后核对首帧与缓冲假设。追问“所有规格都完成才发布是否过慢”时,可以允许先发布明确的基础规格,再用新清单版本增加规格,但每次发布都必须是自洽的完整版本。

参考资料