系统设计
嘿,欢迎来到本课程。希望本课程能给您带来良好的学习体验。
这门课程也可以在我的网站上找到,也可以在leanpub上找到电子书。如果觉得有帮助,请留下⭐作为鼓励!
目录
-
入门
-
第一章
-
第二章
-
第三章
-
第四章
- …
让我们设计一个类似Netflix的视频流服务,类似于Amazon Prime Video、Disney Plus、Hulu、Youtube、Vimeo等服务。
Netflix 是一项基于订阅的流媒体服务,允许其会员在联网设备上观看电视节目和电影。它可在 Web、iOS、Android、电视等平台上使用。
我们的系统应满足以下要求:
让我们从估计和约束开始。
注意:请务必与面试官核对任何与规模或交通相关的假设。
这将是一个阅读量很大的系统。假设我们拥有 10 亿用户,其中每日活跃用户 (DAU) 为 2 亿,平均每位用户每天观看 5 个视频。这意味着每天观看的视频数量为 10 亿。
200:1
读写比率为 5000 万,每天将上传约 5000 万个视频。
每天 10 亿个请求相当于每秒 12000 个请求。
如果我们假设每个视频平均为 100 MB,那么我们每天将需要大约 5 PB 的存储空间。
由于我们的系统每天要处理 5 PB 的入口数据,因此我们需要每秒约 58 GB 的最低带宽。
以下是我们的高级估计:
类型 | 估计 |
---|---|
每日活跃用户(DAU) | 2亿 |
每秒请求数 (RPS) | 12K/秒 |
存储(每天) | ~5 PB |
储存(10年) | ~18,250 PB |
带宽 | ~58 GB/秒 |
这是反映我们要求的通用数据模型。
我们有下表:
用户
该表将包含用户的信息,例如,,,name
和其他详细信息。email
dob
视频
顾名思义,该表将存储视频及其属性,例如title
,,等streamURL
。tags
我们还将存储相应的userID
。
标签
该表将仅存储与视频相关的标签。
视图
该表帮助我们存储视频收到的所有观看次数。
评论
该表存储了有关视频(如 YouTube)的所有评论。
虽然我们的数据模型看起来相当相关,但我们不一定需要将所有内容存储在单个数据库中,因为这会限制我们的可扩展性并很快成为瓶颈。
我们将数据拆分到不同的服务,每个服务拥有特定表的所有权。然后,我们可以将关系数据库(例如PostgreSQL)或分布式 NoSQL 数据库(例如Apache Cassandra)用于我们的用例。
让我们为我们的服务做一个基本的 API 设计:
通过给定字节流,此 API 可以将视频上传到我们的服务。
uploadVideo(title: string, description: string, data: Stream<byte>, tags?: string[]): boolean
参数
标题(string
):新视频的标题。
描述 ( string
):新视频的描述。
数据(Byte[]
):视频数据的字节流。
标签(string[]
):视频的标签(可选)。
返回
结果(boolean
):表示操作是否成功。
此 API 允许我们的用户使用首选的编解码器和分辨率来流式传输视频。
streamVideo(videoID: UUID, codec: Enum<string>, resolution: Tuple<int>, offset?: int): VideoStream
参数
视频ID(UUID
):需要流式传输的视频的ID。
编解码器(Enum<string>
):请求视频所需的编解码器h.265
,例如、、等h.264
。VP9
分辨率(Tuple<int>
):请求的视频的分辨率。
偏移量(int
):视频流与视频中任意点之间的数据流偏移量(以秒为单位)(可选)。
返回
流(VideoStream
):请求的视频数据流。
该 API 将使我们的用户能够根据标题或标签搜索视频。
searchVideo(query: string, nextPage?: string): Video[]
参数
查询(string
):来自用户的搜索查询。
下一页(string
):下一页的令牌,可用于分页(可选)。
返回
视频(Video[]
):符合特定搜索查询的所有视频。
此 API 将允许我们的用户对视频发表评论(如 YouTube)。
comment(videoID: UUID, comment: string): boolean
参数
VideoID(UUID
):用户想要评论的视频的ID。
评论(string
):评论的文本内容。
返回
结果(boolean
):表示操作是否成功。
现在让我们对我们的系统进行高层设计。
我们将使用微服务架构,因为它可以更轻松地水平扩展和解耦我们的服务。每个服务都拥有自己的数据模型。让我们尝试将系统划分为几个核心服务。
用户服务
该服务处理与用户相关的问题,例如身份验证和用户信息。
流服务
推文服务将处理与视频流相关的功能。
搜索服务
该服务负责处理与搜索相关的功能。我们将另行详细讨论。
媒体服务
该服务将负责视频的上传和处理。具体细节我们将另行讨论。
分析服务
该服务将用于指标和分析用例。
服务间通信和服务发现怎么样?
由于我们的架构基于微服务,因此服务之间也会相互通信。通常,REST 或 HTTP 的性能表现良好,但我们可以使用更轻量、更高效的gRPC来进一步提升性能。
服务发现是我们需要考虑的另一件事。我们还可以使用服务网格,实现各个服务之间可管理、可观察且安全的通信。
注意:了解有关REST、GraphQL、gRPC 的更多信息以及它们之间的比较。
在处理视频时,变量非常多。例如,高端摄像机拍摄的两小时原始 8K 视频的平均数据量很容易就达到 4 TB,因此我们需要进行某种处理来降低存储和传输成本。
以下是内容团队(或 YouTube 的用户)上传视频后,我们在消息队列中排队等待处理时的处理方式。
让我们讨论一下它是如何工作的:
这是我们处理流程的第一步。文件分块是将文件分割成更小的块(称为“块”)的过程。它可以帮助我们消除存储中重复数据的重复副本,并通过仅选择已更改的块来减少通过网络发送的数据量。
通常,可以根据时间戳将视频文件分割成大小相等的块,但 Netflix 却根据场景分割块,这种细微的变化成为改善用户体验的一个重要因素,因为每当客户端从服务器请求一个块时,中断的可能性就会降低,因为会检索到完整的场景。
此步骤检查视频是否符合平台的内容政策,对于 Netflix 来说,可以根据媒体的内容评级进行预先批准,或者可以像 YouTube 一样严格执行。
整个步骤由机器学习模型完成,该模型会执行版权、盗版和 NSFW 检查。如果发现问题,我们会将任务推送到死信队列 (DLQ),以便审核团队进行进一步检查。
转码是将原始数据解码为中间未压缩格式,然后将其编码为目标格式的过程。此过程使用不同的编解码器来执行比特率调整、图像下采样或媒体重新编码。
这样可以生成更小的文件,并针对目标设备提供更优化的格式。您可以使用FFmpeg等独立解决方案或AWS Elemental MediaConvert等云端解决方案来实现流水线的这一步骤。
这是处理管道的最后一步,顾名思义,此步骤处理上一步转码媒体转换为不同分辨率,例如 4K、1440p、1080p、720p 等。
这使我们能够根据用户的要求获取所需的视频质量,并且一旦媒体文件完成处理,它将被上传到分布式文件存储(如HDFS、GlusterFS)或对象存储(如Amazon S3 ) ,以便在流式传输期间稍后检索。
注意:我们可以添加其他步骤(例如字幕和缩略图生成)作为管道的一部分。
我们为什么要使用消息队列?
将视频处理作为一项长期运行的任务更加合理,而且消息队列还能将视频处理流程与上传功能解耦。我们可以使用Amazon SQS或RabbitMQ之类的工具来支持这一点。
无论从客户端还是服务器的角度来看,视频流传输都是一项极具挑战性的任务。此外,不同用户的网络连接速度差异很大。为了确保用户不会重复获取相同的内容,我们可以使用内容分发网络 (CDN)。
Netflix 通过其Open Connect计划更进一步。通过这种方式,他们与数千家互联网服务提供商 (ISP) 合作,实现流量本地化,并更高效地交付内容。
Netflix 的 Open Connect 与传统内容分发网络 (CDN) 有何区别?
Netflix Open Connect 是我们专门构建的内容分发网络 (CDN),负责服务 Netflix 的视频流量。全球约 95% 的流量是通过 Open Connect 与其客户用于访问互联网的 ISP 之间的直接连接传输的。
目前,Netflix 在全球 1000 多个不同地点部署了 Open Connect Appliances(OCA)。一旦出现问题,Open Connect Appliances(OCA)可以进行故障转移,并将流量重新路由到 Netflix 服务器。
此外,我们可以使用自适应比特率流协议,例如HTTP 实时流 (HLS),该协议专为可靠性而设计,可通过优化播放以适应可用的连接速度来动态适应网络条件。
最后,为了从用户离开的地方播放视频(我们的扩展要求的一部分),我们可以简单地使用offset
存储在views
表中的属性来检索特定时间戳的场景块并为用户恢复播放。
有时,传统的 DBMS 性能不够强,我们需要一些能够快速、近乎实时地存储、搜索和分析海量数据,并在几毫秒内给出结果的工具。Elasticsearch可以帮助我们实现这一目标。
Elasticsearch是一个分布式、免费且开放的搜索和分析引擎,适用于所有类型的数据,包括文本、数字、地理空间、结构化和非结构化数据。它构建于Apache Lucene之上。
我们如何识别热门内容?
趋势功能将基于搜索功能。我们可以缓存过去几秒内搜索最频繁的查询,并使用某种批处理机制N
每秒更新一次。M
共享内容是任何平台的重要组成部分,为此,我们可以提供某种 URL 缩短服务,为用户生成短 URL 以供共享。
更多详细信息,请参考URL Shortener系统设计。
现在是时候详细讨论我们的设计决策了。
为了扩展数据库,我们需要对数据进行分区。水平分区(又称分片)是一个很好的第一步。我们可以使用以下分区方案:
上述方法仍然会导致数据和负载分布不均匀,我们可以使用一致性哈希来解决这个问题。
Netflix 和 YouTube 等平台使用地理封锁功能来限制特定地理区域或国家/地区的内容。这主要是因为 Netflix 与制作和发行公司签订协议时必须遵守合法的发行法规。对于 YouTube 而言,这将由用户在内容发布期间控制。
我们可以使用用户个人资料中的IP或区域设置来确定用户的位置,然后使用支持地理限制功能的Amazon CloudFront等服务或使用Amazon Route53 的地理位置路由策略来限制内容,如果内容在特定地区或国家/地区不可用,则将用户重新路由到错误页面。
Netflix 使用机器学习模型,该模型利用用户的观看历史来预测用户接下来可能想看什么,可以使用协同过滤之类的算法。
然而,Netflix(与 YouTube 类似)使用自己的算法,称为 Netflix 推荐引擎,该算法可以跟踪多个数据点,例如:
有关更多详细信息,请参阅Netflix 推荐研究。
记录分析和指标是我们的扩展需求之一。我们可以从不同的服务中捕获数据,并使用Apache Spark(一个用于大规模数据处理的开源统一分析引擎)对数据进行分析。此外,我们可以将关键元数据存储在视图表中,以增加数据中的数据点。
在流媒体平台中,缓存至关重要。为了提升用户体验,我们必须尽可能多地缓存静态媒体内容。我们可以使用Redis或Memcached等解决方案,但哪种缓存驱逐策略最适合我们的需求呢?
使用哪种缓存驱逐策略?
对我们的系统来说,最近最少使用(LRU)策略可能是一个不错的选择。在这个策略中,我们首先丢弃最近最少使用的键。
如何处理缓存未命中?
每当出现缓存未命中时,我们的服务器可以直接访问数据库并使用新条目更新缓存。
有关详细信息,请参阅缓存。
由于我们的大部分存储空间将用于存储缩略图和视频等媒体文件。根据我们之前的讨论,媒体服务将负责媒体文件的上传和处理。
我们将使用分布式文件存储(例如HDFS、GlusterFS)或对象存储(例如Amazon S3)来存储和传输内容。
内容分发网络 (CDN)可以提高内容可用性和冗余度,同时降低带宽成本。通常,静态文件(例如图像和视频)由 CDN 提供。对于这种情况,我们可以使用Amazon CloudFront或Cloudflare CDN等服务。
让我们识别并解决设计中的单点故障等瓶颈:
为了使我们的系统更具弹性,我们可以执行以下操作:
本文是我在 Github 上提供的开源系统设计课程的一部分。
嘿,欢迎来到本课程。希望本课程能给您带来良好的学习体验。
这门课程也可以在我的网站上找到,也可以在leanpub上找到电子书。如果觉得有帮助,请留下⭐作为鼓励!
入门
第一章
第二章
第三章
第四章