查看原文
其他

如何设计一个类似Youtube或Netflix的视频网站?

拾叁 更AI 2023-10-21

点击关注公众号,AI,编程干货及时送达   

让我们设计一个类似于Youtube的视频分享服务,用户可以上传/观看/搜索视频。

类似的服务:netflix.com, vimeo.com, dailymotion.com, veoh.com

难度等级:中等

1. 为什么是Youtube?

Youtube是世界上最受欢迎的视频分享网站之一。该服务的用户可以上传、观看、分享、评价和举报视频,也可以在视频上添加评论。

2. 系统的要求和目标

为了这个练习,我们打算设计一个更简单版本的Youtube,具有以下要求:

功能性要求:

  1. 1. 用户应能上传视频。

  2. 2. 用户应能分享和观看视频。

  3. 3. 用户应能根据视频标题进行搜索。

  4. 4. 我们的服务应能记录视频的统计数据,例如,点赞/点踩、总观看次数等。

  5. 5. 用户应能在视频上添加和查看评论。

非功能性要求:

  1. 1. 系统应非常可靠,任何上传的视频都不应丢失。

  2. 2. 系统应高度可用。为了保证可用性,可以牺牲一定的一致性;如果用户一段时间看不到视频,也是可以接受的。

  3. 3. 用户在观看视频时应有实时的体验,不应感觉到任何延迟。

不在考虑范围内:视频推荐,最热门的视频,频道,订阅,稍后观看,收藏等。

3. 容量估算和约束

假设我们有15亿的总用户,其中8亿是每日活跃用户。如果平均每个用户每天观看5个视频,那么每秒的总视频观看次数将是:

800M * 5 / 86400  => 46K 视频/秒

假设我们的上传:观看比例是1:200,即,每上传一个视频,我们有200个视频被观看,这就给我们每秒上传230个视频。

46K / 200 => 230 视频/秒

存储估算:假设每分钟有500小时的视频被上传到Youtube。如果平均每分钟的视频需要50MB的存储(视频需要以多种格式存储),那么每分钟上传的视频所需的总存储量将是:

500 小时 * 60 分钟 * 50MB => 1500 GB/分钟(25 GB/秒)

这些数字是在忽略视频压缩和复制的情况下估算的,这会改变我们的估算。

带宽估算:每分钟有500小时的视频上传,假设每个视频上传需要10MB/分钟的带宽,我们每分钟将获取到300GB的上传。

500 小时 * 60 分钟 * 10MB => 300GB/分钟(5GB/秒)

假设上传:观看的比例为1:200,我们将需要1TB/s的出站带宽。

4. 系统API

我们可以使用SOAP或REST API来公开我们的服务功能。以下可能是用于上传和搜索视频的API定义:

image-20230718210403028

参数: api_dev_key(字符串):注册账户的API开发者密钥。这将被用来,包括但不限于,根据他们分配的配额对用户进行限流。 video_title(字符串):视频标题。 vide_description(字符串):视频的可选描述。

tags(字符串数组):视频的可选标签。 category_id(字符串):视频的类别,例如,电影,歌曲,人物等。 default_language(字符串):例如英语,普通话,印地语等。 recording_details(字符串):视频录制的地点。 video_contents(流):要上传的视频。

返回:(字符串) 成功的上传将返回HTTP 202(请求已接受),一旦视频编码完成,用户将通过电子邮件获得一个链接来访问视频。我们还可以公开一个可查询的API,让用户知道他们上传的视频的当前状态。

image-20230718210419739

参数: api_dev_key(字符串):我们服务的注册账户的API开发者密钥。 search_query(字符串):包含搜索词的字符串。 user_location(字符串):进行搜索的用户的可选位置。 maximum_videos_to_return(数字):在一个请求中返回的最大结果数量。 page_token(字符串):这个令牌将指定结果集中应返回的页。

返回:(JSON) 一个包含匹配搜索查询的视频资源列表信息的JSON。每个视频资源都将有一个视频标题,一个缩略图,一个视频创建日期和一个观看计数。

image-20230718210427622

参数: api_dev_key(字符串):我们服务的注册账户的API开发者密钥。 video_id(字符串):用于识别视频的字符串。 offset(数字):我们应该能够从任何偏移处流式传输视频;这个偏移将是从视频开始的秒数。如果我们支持从多个设备播放/暂停视频,我们将需要在服务器上存储偏移。这将使用户能够在任何设备上从他们停止的地方开始观看视频。 codec(字符串) & resolution(字符串):我们应该在客户端的API中发送编解码器和分辨率信息,以支持从多个设备播放/暂停。想象一下,你在你的电视的Netflix应用上观看视频,暂停了,然后在你的手机的Netflix应用上开始观看。

返回:(STREAM) 从给定偏移处的媒体流(视频块)。

5. 高层次设计

在高层次上,我们需要以下组件:

  1. 1. 处理队列:每个上传的视频将被推到一个处理队列中,以便稍后进行编码,生成缩略图和存储。

  2. 2. 编码器:将每个上传的视频编码成多种格式。

  3. 3. 缩略图生成器:为每个视频生成一些缩略图。

  4. 4. 视频和缩略图存储:在一些分布式文件存储中存储视频和缩略图文件。

  5. 5. 用户数据库:存储用户的信息,如姓名,电子邮件,地址等。

  6. 6. 视频元数据存储:一个元数据数据库,用来存储所有关于视频的信息,如标题,系统中的文件路径,上传用户,总观看次数,点赞,点踩等。它也将用于存储所有的视频评论。

image-20230718210441732

6. 数据库架构

视频元数据存储 - MySql 视频元数据可以存储在SQL数据库中。每个视频应存储以下信息:

  • • 视频ID

  • • 标题

  • • 描述

  • • 大小

  • • 缩略图

  • • 上传者/用户

  • • 点赞总数

  • • 点踩总数

  • • 浏览总数 对于每个视频评论,我们需要存储以下信息:

  • • 评论ID

  • • 视频ID

  • • 用户ID

  • • 评论

  • • 创建时间

用户数据存储 - MySql

  • • 用户ID、姓名、电子邮件、地址、年龄、注册详情等。

7. 详细组件设计

服务将以读为主,所以我们将专注于建立一个可以快速检索视频的系统。我们可以预期我们的读:写比例为200:1,这意味着每上传一个视频,就有200次视频查看。

视频将存储在哪里?视频可以存储在分布式文件存储系统中,比如HDFS或GlusterFS。

如何有效地管理读取流量?我们应该将读取流量与写入流量分开。由于我们每个视频都会有多个副本,我们可以将读取流量分布在不同的服务器上。对于元数据,我们可以有主从配置,写入操作首先到主服务器,然后应用在所有的从服务器上。这样的配置可能会导致数据的陈旧性,例如,当添加新视频时,它的元数据首先插入主服务器,而在应用于从服务器之前,我们的从服务器将无法看到它;因此,它会向用户返回过时的结果。这种陈旧性在我们的系统中可能是可以接受的,因为它的持续时间非常短,用户在几毫秒后就能看到新的视频。

缩略图将存储在哪里?缩略图的数量将远多于视频。如果我们假设每个视频有五个缩略图,我们需要有一个非常高效的存储系统,可以应对巨大的读取流量。决定哪种存储系统应该用于缩略图之前,有两点要考虑:

  1. 1. 缩略图是小文件,每个文件大小假设为最大5KB。

  2. 2. 缩略图的读取流量将远超过视频。用户一次只会观看一个视频,但他们可能会查看一个页面,上面有其他视频的20个缩略图。

让我们评估一下将所有的缩略图存储在磁盘上。鉴于我们有大量的文件,我们必须在磁盘的不同位置执行大量的寻找操作来读取这些文件。这非常低效,并会导致更高的延迟。

Bigtable可能是一个合理的选择,因为它将多个文件合并成一个块存储在磁盘上,且在读取少量数据时非常高效。这两点都是我们服务的最重要的需求。将热门缩略图保存在缓存中也有助于改善延迟,并且,鉴于缩略图文件体积小,我们可以在内存中缓存大量这样的文件。

视频上传:由于视频可能很大,如果在上传过程中连接断开,我们应支持从同一点恢复。

视频编码:新上传的视频存储在服务器上,并在处理队列中添加新任务,将视频编码成多种格式。一旦所有的编码都完成,将通知上传者,视频就可以查看/分享了。

image-20230718210459264

8. 元数据分片

由于我们每天有大量新视频,而我们的读取负载极高,因此,我们需要将数据分布在多台机器上,以便我们能有效地进行读/写操作。我们有许多分片数据的选项。让我们逐一讨论这些数据的不同分片策略:

基于UserID的分片:我们可以尝试将特定用户的所有数据存储在一个服务器上。在存储时,我们可以将UserID传递给我们的哈希函数,该函数将用户映射到一个数据库服务器,我们将在该服务器上存储该用户视频的所有元数据。在查询用户的视频时,我们可以让我们的哈希函数找到保存用户数据的服务器,然后从那里读取数据。要通过标题搜索视频,我们将不得不查询所有服务器,每个服务器都将返回一组视频。一个集中的服务器将在返回给用户之前汇总和排列这些结果。

这种方法有几个问题:

  1. 1. 如果用户变得受欢迎怎么办?保存该用户的服务器可能会有很多查询,这可能会创建性能瓶颈。这也会影响我们服务的整体性能。

  2. 2. 随着时间的推移,一些用户可能比其他用户存储了更多的视频。维持增长的用户数据的均匀分布是相当棘手的。

要从这些情况中恢复,我们要么必须重新分区/重新分配我们的数据,要么使用一致性哈希来在服务器之间平衡负载。

基于VideoID的分片:我们的哈希函数将把每个VideoID映射到一个随机服务器,我们将在那里存储该视频的元数据。要查找用户的视频,我们将查询所有服务器,每个服务器都会返回一组视频。一个集中的服务器会在返回给用户之前汇总和排列这些结果。这种方法解决了我们的热门用户问题,但将问题转移到了热门视频上。

我们可以通过在数据库服务器前引入缓存来存储热门视频,以进一步提高我们的性能。

9. 视频去重

随着大量用户上传大量视频数据,我们的服务将必须处理广泛的视频重复。重复的视频通常在宽高比或编码上有所不同,可以包含覆盖或额外的边框,或者可能是从一个更长的原始视频中提取的片段。重复视频的增多会对许多层面产生影响:

  1. 1. 数据存储:我们可能在存储相同视频的多个副本而浪费存储空间。

  2. 2. 缓存:重复视频会导致缓存效率降低,因为它占用了可能用于唯一内容的空间。

  3. 3. 网络使用:重复视频也会增加必须通过网络发送到网络缓存系统的数据量。

  4. 4. 能源消耗:更高的存储、低效的缓存和网络使用可能导致能源浪费。

对于终端用户,这些低效将以重复的搜索结果、较长的视频启动时间和中断的流媒体播放的形式体现出来。

对于我们的服务来说,去重最有意义的时候是早期;当用户正在上传视频时,比起后期处理来找到重复的视频。在线去重将节省我们很多资源,这些资源可以用来编码、传输和存储视频的重复副本。只要有任何用户开始上传视频,我们的服务就可以运行视频匹配算法(例如,块匹配、相位相关等)来查找重复。如果我们已经有了正在上传的视频的副本,我们可以选择停止上传并使用现有的副本,或者如果新上传的视频质量更高,我们可以继续上传并使用新上传的视频。如果新上传的视频是现有视频的一部分,或者反之亦然,我们可以智能地将视频划分为更小的块,这样我们只需要上传缺失的部分。

10. 负载均衡

我们应该在我们的缓存服务器之间使用一致性哈希,这也将帮助在缓存服务器之间平衡负载。由于我们将使用静态哈希方案将视频映射到主机名,因此由于每个视频的受欢迎程度不同,可能导致逻辑副本的负载不均。例如,如果一个视频变得流行,对应那个视频的逻辑副本将经历比其他服务器更多的流量。这些逻辑副本的负载不均然后可以转化为对应物理服务器的负载分布不均。为了解决这个问题,任何一个位置的繁忙服务器都可以将客户端重定向到同一缓存位置的不那么繁忙的服务器。我们可以在这种情况下使用动态HTTP重定向。

然而,使用重定向也有它的缺点。首先,由于我们的服务尝试在本地进行负载均衡,如果接收重定向的主机无法服务视频,可能会导致多次重定向。此外,每次重定向都需要客户端发出额外的HTTP请求;这也导致视频播放开始前的延迟增加。此外,跨层(或跨数据中心)重定向会将客户端引导到远离的缓存位置,因为高层缓存只存在于少数地方。

11. 缓存

为了服务全球分布的用户,我们的服务需要一个大规模的视频传输系统。我们的服务应该使用大量的地理分布式视频缓存服务器,将其内容推送到用户更近的地方。我们需要有一种策略,既能最大化用户的性能,又能均匀地分布其缓存服务器上的负载。

我们可以为元数据服务器引入一个缓存,用来缓存热门数据库行。在访问数据库之前,使用Memcache缓存数据和应用服务器可以快速检查缓存是否有所需的行。最近最少使用(LRU)可以是我们系统的一个合理的缓存驱逐策略。在这个策略下,我们先丢弃最近最少查看的行。

我们如何建立更智能的缓存?如果我们遵循80-20的规则,即视频的每日读取量的20%产生了80%的流量,这意味着某些视频非常受欢迎,大多数人都会观看;由此我们可以尝试缓存视频和元数据的每日读取量的20%。

12. 内容分发网络(CDN)

CDN是一个分布式服务器系统,根据用户的地理位置、网页的起源和内容分发服务器,将网页内容提供给用户。可以参考我们的'缓存'章节中的'CDN'部分。

我们的服务可以将流行视频移动到CDN:

  • • CDN在多个地方复制内容。视频更有可能靠近用户,通过更少的跳转,视频将从更友好的网络进行流媒体播放。

  • • CDN机器大量使用缓存,大部分可以从内存中服务视频。

不那么流行的视频(每天1-20次观看)如果没有被CDN缓存,可以由我们在各个数据中心的服务器提供。

13. 容错

我们应该使用一致性哈希进行数据库服务器间的分配。一致性哈希不仅能帮助替换死掉的服务器,还能帮助在服务器之间分配负载。

推荐阅读

··································

你好,我是拾叁,7年开发老司机、互联网两年外企5年。怼得过阿三老美,也被PR comments搞崩溃过。这些年我打过工,创过业,接过私活,也混过upwork。赚过钱也亏过钱。一路过来,给我最深的感受就是不管学什么,一定要不断学习。只要你能坚持下来,就很容易实现弯道超车!所以,不要问我现在干什么是否来得及。如果你还没什么方向,可以先关注我,这里会经常分享一些前沿资讯和编程知识,帮你积累弯道超车的资本。

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存