专业书籍精读 · System Design Interview 卷一 · 第 14 章
System Design Interview Vol 1 · Ch 14 · Alex Xu · 2020
你点开一个视频,一两秒就开始播,中途从 wifi 切到 4G 也不断。这一章要问的是:要把这件事做给几百万人用,背后得建些什么?答案有点出人意料——难的不是「播」,是「把片子准备好」和「把片子送到你身边」。
你辛辛苦苦上传的那个文件,观众一秒都没看过。平台收到它之后会另做十几个版本:给手机的小份、给电视的大份、给信号差时的超小份,还有封面图和音轨。观众看的永远是这些「复制品」之一,原片只是躺在仓库里当母带。
因为观众的设备和网络差得太远。同一部片,家里电视机能吃下的量是地铁上手机的十几倍。只发一个版本的话:发大的,一半人看不动;发小的,另一半嫌糊。只能事先把每种份量都做好,让播放器自己挑。可「做好」非常费机器——一部一小时的高清片,一台机器从头做到尾可能要跑几个小时。
① 中央厨房出多种份量。片子进来先预处理,再照一张菜单做出一整套份量不同的成品。这道工序叫转码。
② 切成小段,一百个厨师同时做。视频有天然的切点——每隔几秒有一个「完整画面」,从那里能独立开始播。于是把一小时的片切成几百个几秒的小块,几百台机器同时做,做完按顺序拼回去。总时间不再取决于片子多长,而取决于最慢那一块。
③ 把火的片子提前送到你家小区。成品放在中心仓库,可用户离仓库可能隔着半个地球。所以平台把最火的那些提前铺到离用户几十公里的「便利店」(就是 CDN)。你点开时是从便利店取的,所以快。
取舍全在钱上。把每部片的每个版本都铺到全球每家便利店,账单立刻爆掉——这类平台最大的开销就是「把视频送出去」的流量费。所以真实做法是分层:热门的进便利店,冷门的从中心仓库现取,甚至连全套份量都不提前做,等真有人看了再做。
代价很诚实:你刚传完的视频,得等几分钟甚至更久才有高清版本——它正在那条流水线上排队。
视频平台真正难的两件事:把一个源片变成一整套不同份量的成品(靠切小块并行做),和把成品送到用户身边(靠只给热门片铺货)。播放本身反而是这行里最成熟的一段。
想进到具体机制、量级和示意图? → 切到精读版
你以为设计 YouTube 难在播放,其实播放是这行里最成熟、最标准化的一段(CDN + 自适应码流,照抄即可);真正要设计的是两样东西——一条能并行、能容错、能改造的转码流水线,和一张每天六位数美元的分发账单怎么压下去。这一章的价值不在「YouTube 怎么做」,在于它示范了:当某个环节的成本随业务量线性增长时,架构决策就会全部被成本牵着走。
.mp4/.mkv 是「盒子」,管音视频轨怎么装进一个文件;H.264/VP9/AV1 是「压缩算法」,决定画面怎么被压小。两者可自由组合。Mbps;越高越清晰、也越吃带宽。本章属于卷一 Part II「系统案例」的后段(Ch14),是全卷数据体量最大、成本压力最直白的一题。它与 Ch15「设计 Google Drive」是一对——同样处理大文件,但 Drive 关心同步与冲突,YouTube 关心加工与分发。现实里对应的是 YouTube、Netflix、Bilibili、TikTok,以及任何带「上传后要处理」环节的平台(云相册、播客、录屏工具)。
先把量级摆出来,否则所有讨论都是空的。书里给的设定是:500 万日活(DAU)、人均每天看 5 个视频,于是播放请求约 5,000,000 × 5 ÷ 86,400 ≈ 289 次/秒——这个数字其实不大,单看它你会以为这题很轻松。真正吓人的是另外两个:
300 MB,那就是 50 万 个上传/天 = 每天新增 150 TB 原片,而且只进不出。$0.02/GB 粗算,2500 万 次观看 × 0.3 GB × $0.02 ≈ 每天 15 万美元、一年 5000 万美元以上——而这还只是 500 万日活。于是三个痛点浮出来。第一,源片不能直接播:未压缩的 1080p、30 帧/秒、每像素 3 字节,粗算是 186 MB/秒、一小时约 670 GB;即使源片已压过,它的码率对地铁上的 4G 手机仍是灾难,设备支持的格式又五花八门。第二,转码是 CPU 巨兽:一台机器编完一个 1 小时 4K 片可能要几小时——用户传完等一下午,产品就死了。第三,分发成本随观看量线性涨,是这类系统里唯一「越成功越贵」的一项。全章的设计动作都在回应这三条。
最先要建立的认知是:视频的字节流和视频的元数据(metadata,即标题、描述、封面等描述性信息)走两条完全不同的路,把它们混在一起是新手最常犯的错。
上传路径:客户端先向 API 服务器要一个预签名 URL,然后把原片分片直接 PUT 进对象存储——这几百 MB 从头到尾不经过应用服务器。原片落地触发转码流水线,产出一整套码率档写进另一个桶,热门内容再预热到 CDN;流水线完成后往完成队列丢一条事件,由 completion handler 回写元数据库,视频状态才从「处理中」变成「可播放」。元数据则走轻量路径:客户端 → API 服务器 → 元数据库 + 缓存。
播放路径短得多:播放器就近打到 CDN 边缘节点,先拉 manifest(清单),再按当前带宽逐段拉分片。正常情况下,播放请求根本不碰你的后端。
转码要解决三件事:源片体积大、设备支持的格式各异、用户带宽随时在变。做法是照一张码率阶梯把源片编成若干档——常见 240p/360p/480p/720p/1080p 五六档,再加音轨、缩略图、字幕。一个源片会膨胀成十几个产物,全套存储通常是最高档的 1.5~2 倍。
播放器拿到清单后每隔几秒测一次实际吞吐,决定下一段拉哪档。这也是为什么每一档都必须切成时间对齐的小段(HLS/DASH 一般 2~10 秒):只有段边界对齐,切档时画面才不会跳。带宽掉下去时用户看到的是「糊了一下」而不是「转圈卡住」——这就是自适应码流的全部价值。
这是本章最值得带走的设计。书里明确说这个模型借鉴自 Facebook 的流式视频引擎:不要把转码写成一条从头跑到尾的脚本,而要写成一张有向无环图。源片进来先拆成视频轨、音频轨、元数据三条轨(track);视频轨上再挂质量检查、按各档编码、抽缩略图、打水印等任务,最后汇总打包生成清单。任务之间的依赖关系就是图的边。
为什么值得这么绕?因为需求永远在加。今天加一档 AV1、明天用模型自动挑封面帧、后天法务要求给某类内容打水印——脚本形态下每次都要改主流程、重跑全量;DAG 形态下只是往图里挂一个节点,重跑受影响的子图即可。这就是「可演化性」在一个具体系统里长什么样。
图之外还有一套调度骨架:预处理器切 GOP、按视频特征生成这张图;DAG 调度器把图按依赖切成阶段;资源管理器维护任务队列与空闲 worker 队列并派活;worker 干活、中间结果落临时存储。好处是各层能独立扩容——编码慢就加 worker。
前面所有结构最终都是为了做成这一件事。视频每隔几秒有一个可独立解码的关键帧,从这里下刀切出的 GOP 块能单独编码。于是一小时的片被切成几百个 5~10 秒的块,几百台机器同时编,编完按序号拼回去。
端到端时间从「与片长成正比」变成「≈ 最慢那一块 + 拼接」——这是数量级的改变,也是「传完几分钟能看」和「传完等一下午」的分界线。同一把刀还能往前伸到上传侧:客户端按 GOP 边界切好分片再传,失败只重传一块,而且上传与转码可以重叠。老客户端不支持切分?服务端收到整片后自己切,代价是失去重叠的那部分收益。
回到那 15 万美元/天。书里的省钱清单全建立在长尾分布这个观察上:少数视频拿走绝大部分观看量。于是——只把热门视频推进 CDN,长尾从自建的高容量存储服务器直供;冷门视频不预生成全套档位,有人看再编;按区域分发,只在某地区火就别全球铺;量足够大之后自建 CDN,把机器直接放进运营商机房。
另有两条不是省钱但同样是本章要点。安全:上传用预签名 URL(只在几分钟内、只对那一个 key 有效),播放侧按内容价值分层——DRM、AES 加密、可见水印,代价依次递减也依次变弱。错误处理:分成可恢复(某块转码失败 → 重试几次,仍失败则整体报错)与不可恢复(格式根本不支持 → 立刻停止、返回明确错误),别让重试逻辑掩盖真正的坏输入。
表 1 · 上传的字节走哪条路
| 方案 | 怎么走 | 好处 | 代价 |
|---|---|---|---|
| 预签名 URL 直传 | 客户端拿一次性授权地址,分片直接 PUT 进对象存储 | API 层零字节流量,带宽压力落在存储服务上 | URL 泄露 = 上传权限泄露,必须短时效 + 限定单个 key;校验只能后置 |
| 经 API 服务器中转 | 客户端 POST 给应用服务器,服务器再写存储 | 上传当场就能鉴权、校验、打标 | 一个 300 MB 的 body 长时间占住连接与内存;API 层被迫按上传带宽扩容,几乎必然先挂 |
| 客户端切片并行直传 | 按 GOP 边界切成 5–10 MB 分片并发传 | 失败只重传一块;上传与转码可重叠,端到端最快 | 客户端复杂度高;老客户端不支持,服务端要留兜底切片路径 |
表 2 · 转码的并行粒度
| 切法 | 端到端时间 | 适用 | 代价 |
|---|---|---|---|
| 整片串行 | 与片长成正比 | 极短视频、离线批量补编 | 1 小时 4K 要跑几小时,交互式产品用不了 |
| 按 GOP 切小块 (跨时间并行) | ≈ 最慢一块 + 拼接 | 点播主路径 | 要额外做调度器、临时存储与拼接对齐;块切得太小,调度开销会盖过并行收益 |
| 只跨码率档并行 | 仍与片长成正比,但一次出全套档 | 直播——未来的帧还没产生,没法跨时间切 | 并行度上限就是档位数,延迟压不下去;直播只能改从 GOP 更短、缓冲更浅入手 |
表 3 · CDN 账单的四种压法(书里给的省钱清单)
| 手段 | 做法 | 省下什么 | 代价 |
|---|---|---|---|
| 冷热分层 | 只有热门视频进 CDN,长尾从自建高容量存储服务器直供 | 要铺货的内容集小一个数量级,流量费大降 | 长尾内容首播延迟明显变高 |
| 按需转码 | 冷门视频不预生成全套档位,有人看再编 | 存储与转码算力 | 第一位观众要等;冷门视频突然爆火时有一段抖动 |
| 按区域分发 | 只在真有观众的区域铺,不做全球副本 | 跨区回源流量与冗余副本存储 | 猜错区域就得跨洲回源,给首播加几百毫秒 |
| 自建 CDN | 自研缓存设备放进运营商机房(Netflix Open Connect 即是) | 长期看单位分发成本最低 | 前期资本开支 + 长期运维团队;流量不够大时完全不划算 |
这一题是所有「上传 → 加工 → 分发」类系统的母题:换成音频是播客平台,换成图片是云相册,换成短视频是 TikTok,换成文件就是下一章的 Google Drive。面试里它考的从来不是「你知不知道 HLS」,而是你会不会主动把成本当成约束条件来做设计——能报出「CDN 每天多少钱」的候选人和只会画框图的,差距就在这里。
5800 kbps 降到 2000 kbps 而画质无可见损失——说明本章的固定阶梯只是起点,真实系统会按内容复杂度重算。Netflix TechBlog《Per-Title Encode Optimization》, 2015 ↗① 核心命题:视频平台难的不是播放(CDN + ABR 已是成熟方案),而是转码流水线与分发成本。
② 量级先行:500 万 DAU 下播放才约 289 次/秒,但每天新增 150 TB 原片、CDN 一天约 15 万美元——成本才是这题的主约束。
③ 两条路分开:几百 MB 的字节靠预签名 URL 直传对象存储,只有元数据走 API 服务器;播放由 CDN 边缘闭环。
④ 转码 = 按码率阶梯把一个源片变成十几个产物,全套约为最高档的 1.5–2 倍存储。
⑤ 用 DAG 描述转码而非写一条脚本:加一档格式只是往图里挂一个节点,这就是可演化性的具体形态。
⑥ 最重要的一刀是按 GOP 切块并行:端到端时间从「∝ 片长」变成「≈ 最慢一块 + 拼接」,并让上传与转码重叠。
⑦ 省钱四招都建立在长尾分布上:冷热分层、按需转码、按区域分发、量够大就自建 CDN。
⑧ 错误分可恢复 / 不可恢复两类处理;安全靠预签名 URL + DRM/加密/水印分层。直播则是另一道题——延迟更紧、GOP 更短,且没法跨时间并行。