cswork / 系统设计

第 9 章 / 共 57 章 · 约 8 分钟

案例 7:设计视频流平台(类 YouTube)

本页目录
  1. 1. 需求澄清
  2. 2. 量级估算
  3. 3. API 设计
  4. 4. 高层架构
  5. 5. 深入讨论
  6. 6. 取舍与常见追问
  7. 7. 面试答题清单
  8. 8. 回答分级
  9. 9. 不同级别的期望
  10. 章节测验

先花 10 分钟自己设计:需求、核心实体、API 和高层架构,各写几行。写完或者跳过之后,下面的讲解才会展开。

先写你的思路

只存在这台设备上;登录后会存进你的账号。

视频平台是面试里的高频题。它的难点不在 QPS,而在两件事:海量存储和出口带宽。答题时先把这两个数算出来,后面的设计(对象存储、转码、CDN)就都有了依据。

1. 需求澄清

功能需求(核心)

  • 用户上传视频,单个文件可达几 GB
  • 用户观看视频,播放器能根据网速自动切换清晰度
  • 查看视频元数据:标题、描述、缩略图、播放量

通常声明为范围外:搜索、推荐、评论、直播、广告。面试官追问时再展开。

非功能需求

  • 读多写少:播放次数比上传次数高约三个数量级(每天 10 亿次播放对 50 万个上传,约 2000 倍)
  • 起播快(首帧最好在 2 秒内),播放过程中少卡顿
  • 上传可靠:网络中断后能续传,已上传的视频不能丢
  • 高可用;播放量这类元数据允许短暂不一致
  • 用户分布在全球

2. 量级估算

项目假设结果
播放量DAU 2 亿,每人每天看 5 个10 亿次/天,平均约 1.2 万 QPS,峰值 3~5 万
上传量每天 50 万个,平均 300 MB原始文件约 150 TB/天
出口带宽每次平均看 5 分钟,平均码率 2 Mbps每次约 75 MB,每天约 75 PB,平均约 7 Tbps

结论很直接:元数据的 QPS 用常规的缓存加分库就能扛住;7 Tbps 的出口流量没有任何单个机房能承担,必须依靠 CDN。存储方面,原始文件加上多档转码结果,再乘以副本数,每天新增几百 TB,只能放对象存储,并且要考虑冷热分层。

3. API 设计

POST /videos                 -> {videoId, uploadId, partUrls[]}  创建视频,拿到分片上传地址
PUT  <presigned partUrl>     -> 客户端把分片直接传到对象存储
POST /videos/{id}/complete   -> 通知上传完成,触发转码
GET  /videos/{id}            -> 元数据 + manifest 地址
GET  <cdn>/{id}/master.m3u8  -> 各清晰度列表,之后按 segment 拉取

要点:视频字节流不经过应用服务器,API 只负责签发地址和记录状态。

4. 高层架构

视频平台:上传路径与观看路径
滚动到此处后加载图表…
图表可横向滚动,展开后可缩放查看。
查看图表源码
flowchart LR
  accTitle: 视频平台:上传路径与观看路径
  subgraph upload ["上传路径"]
    c1["Client"] -->|"(1) 创建"| api1["API Service"] --> meta[("Metadata DB")]
    c1 -->|"(2) 分片上传"| raw[("Object Store(原始)")]
    raw -->|"(3) 事件"| mq["Message Queue"] --> tw["Transcode Workers"]
    tw -->|"(4) 写入各清晰度"| proc[("Object Store(转码后)")]
    tw -->|"(5) status=READY"| meta
    proc --> cdn["CDN"]
  end
  subgraph watch ["观看路径"]
    c2["Client"] -->|"元数据 + manifest 地址"| api2["API Service"]
    c2 --> edgec["CDN Edge"] -->|"miss"| regional["Regional Cache"] -->|"miss"| proc2[("Object Store")]
  end

上传路径写入量大、耗时长、可以异步;观看路径读多、对延迟敏感。把两条路径分开设计,是这道题的骨架。

5. 深入讨论

5.1 上传:预签名 URL + 分片上传

  • 应用服务器签发有时效的预签名 URL,客户端直接把文件传到对象存储。几 GB 的流量不会占用应用服务器的带宽和连接。
  • 文件切成 5~10 MB 的分片并发上传。某一片失败只重传这一片;服务端记录已完成的分片号,客户端断网重连后从断点继续。
  • 视频状态用状态机管理:UPLOADING -> UPLOADED -> PROCESSING -> READY,任一步失败进入 FAILED,方便重试和排查。

5.2 转码流水线

为什么要转码:用户上传的容器格式和编码五花八门;播放端需要多种分辨率和码率;同时还要生成缩略图、做内容审核。

关键做法是先切片再并行:按关键帧(GOP)边界把视频切成若干秒的小段,每段独立转码。一个 1 小时的视频串行转码要很久,切成几百段分给几百个 worker,几分钟就能完成。

整个处理过程可以表示成一个 DAG:

转码 DAG
滚动到此处后加载图表…
图表可横向滚动,展开后可缩放查看。
查看图表源码
flowchart LR
  accTitle: 转码 DAG
  split["切片(split)"] --> video["视频:240p / 480p / 720p / 1080p(逐段)"]
  split --> audio["音频编码"]
  split --> thumb["缩略图 / 内容审核"]
  video --> pkg["封装(HLS/DASH)"]
  audio --> pkg
  thumb --> pkg
  pkg --> ready["READY"]

工程要点:

  • 任务通过消息队列分发,worker 无状态,按队列堆积量自动扩缩容
  • 任务幂等:每段的输出写到确定的 key,重做只是覆盖,不会产生脏数据
  • 关键帧对齐:所有档位都用 closed GOP 和固定的关键帧间隔(比如每 2 秒一个),保证各档的 segment 边界落在同一时间点。否则 ABR 切换清晰度时,新档位的 segment 无法从边界处独立解码,会花屏或卡顿
  • 调度器记录 DAG 中每个节点的状态,全部完成后才把视频标记为 READY
  • 控制成本:先用 H.264 产出基础档位,播放量上来以后再补 VP9、AV1 这类压缩率高但编码昂贵的格式

5.3 自适应码率(ABR)

HLS 和 DASH 的思路相同:每个清晰度(rendition)切成 2~6 秒的 segment,再用一个 manifest 列出所有档位和对应的 segment 地址。

档位分辨率参考码率
低426x2400.4 Mbps
中854x4801.2 Mbps
高1280x7202.5 Mbps
超高1920x10805 Mbps

选档由客户端决定:播放器根据最近几个 segment 的下载速度和缓冲区水位,决定下一个 segment 拉哪一档。缓冲快见底就降档,带宽富余就升档。服务端完全无状态,segment 只是普通的静态文件,这正是它适合放在 CDN 上的原因。

5.4 CDN 与缓存策略

  • 视频热度呈长尾分布:少数热门视频占了大部分流量,它们在 CDN 边缘的命中率很高
  • 冷门视频未命中时,先经过区域缓存再回源到对象存储;多级缓存能减轻源站压力
  • 大型平台会把缓存服务器直接放进运营商机房,进一步减少骨干网流量
  • segment 内容不可变,可以设置很长的 TTL;需要更新时换一个带版本号的 URL。代价是下架或版权删除时,边缘节点上的缓存不会自己消失:要么主动调用 CDN 的 purge 接口,要么给 segment 用带过期时间的签名 URL,过期后边缘直接拒绝请求
  • 预热:头部创作者发布新视频时,提前把前几个 segment 推到边缘节点

5.5 元数据与播放量

  • 元数据(标题、作者、状态、manifest 地址)存在关系库里,按 videoId 分片,前面加 Redis 缓存
  • 播放量不能每次播放都直接 UPDATE 数据库,热门视频会形成写热点。做法是:播放事件写入消息队列,流处理按时间窗口聚合后批量写回;或者先在 Redis 里计数,定期落盘
  • 防刷:按用户、设备、IP 去重,异步过滤异常流量。展示的数字晚几秒、略有误差都可以接受

6. 取舍与常见追问

  • 存储成本:几个月无人观看的视频转到低频存储层;极冷的视频可以只保留原始文件和少数档位,有人看时再补转码
  • 点播与直播:直播用 RTMP、SRT 等协议推流,转码必须实时完成,segment 更短(1~2 秒)以降低延迟,manifest 的 TTL 也要很短
  • 全球化:元数据在多个区域部署只读副本;上传就近接入最近区域的对象存储
  • 为什么不把视频存数据库:数据库适合小而结构化的数据。大文件放对象存储,数据库里只存路径

7. 面试答题清单

  1. 先算出口带宽,用数字说明 CDN 的必要性
  2. 上传路径和观看路径分开讲
  3. 讲清楚 segment + manifest + 客户端选档这套机制
  4. 常见失分点:视频流量经过应用服务器;同步转码,阻塞上传请求;每次播放直接写数据库计数

8. 回答分级

容量与带宽

  • Bad:只估算 QPS,不算出口带宽,CDN 只是一句“加个 CDN”。
  • Good:算出每天约 75 PB、平均约 7 Tbps 的出口流量,据此说明 CDN 必不可少;原始文件和转码结果放对象存储。
  • Great:用长尾分布解释边缘命中率,冷视频走边缘、区域缓存、源站三级回源;提到在运营商网络内部署缓存、大创作者发布时预热首批分片,以及冷视频降级存储、按需重新转码来控制成本。

上传与转码

  • Bad:视频字节经过应用服务器;同步转码,上传请求一直挂到转码结束。
  • Good:预签名 URL 加分片上传,支持断点续传;转码通过消息队列交给无状态 worker,状态走到 READY 才能播放。
  • Great:按 GOP 边界切片并行转码,任务幂等、输出写到确定的 key;所有清晰度用固定的关键帧间隔,让分片边界对齐,ABR 切换才不会卡顿;更省带宽的编码格式只给热门视频做。

播放与计数

  • Bad:由服务端为每个用户决定清晰度;每次播放执行 UPDATE views = views + 1。
  • Good:HLS/DASH 分片加 manifest,客户端按测得的吞吐和缓冲水位选清晰度;播放事件进队列,批量聚合后写入。
  • Great:说明分片不可变所以能长 TTL 缓存,同时给出下架和版权删除的办法(主动 purge,或带过期时间的签名 URL);异步按用户、设备、IP 去重,防止刷播放量。

9. 不同级别的期望

级别期望
Mid(L4)在提示下分开上传和播放两条路径,用上对象存储和 CDN,知道转码要异步
Senior(L5)先算出口带宽再推导架构,讲清分片上传、并行转码和客户端 ABR,处理好播放计数的热点
Staff(L6+)从成本出发:存储分层、编码格式的投入产出、运营商内缓存和预热;覆盖下架与 CDN 缓存的冲突、直播和点播的差异以及全球部署

章节测验

5 道选择题
  1. 为什么上传时让客户端通过预签名 URL 直接把文件传到对象存储?
  2. 按 GOP 边界把视频切成小段再转码,最主要的收益是什么?
  3. 在 HLS/DASH 自适应码率播放中,下一个 segment 用哪一档清晰度由谁决定?
  4. 统计热门视频的播放量,下面哪种做法最合理?
  5. 为什么视频 segment 可以在 CDN 上设置很长的 TTL?

章节讨论

正在加载讨论…