系统设计:如何设计Youtube?


Youtube 是世界上最受欢迎的视频分享网站之一。该服务的用户可以上传、查看、分享、评价和报告视频以及添加对视频的评论。
 
系统的要求和目标
为了这个练习,我们计划设计一个更简单的 Youtube 版本,具有以下要求:
功能要求:

  1. 用户应该能够上传视频。
  2. 用户应该能够分享和观看视频。
  3. 用户可以根据视频标题进行搜索。
  4. 我们的服务应该能够记录视频的统计数据,例如喜欢/不喜欢、总观看次数等。
  5. 用户应该能够添加和查看视频评论。

非功能性要求:
  1. 该系统应该是高度可靠的,任何上传的视频都不会丢失。
  2. 系统应该是高度可用的。一致性可能会受到影响(为了可用性),如果用户有一段时间没有看到视频,那应该没问题。
  3. 用户在观看视频时应该有实时体验,并且不应该感到任何延迟。

不在范围内:视频推荐、最受欢迎的视频、频道和订阅、稍后观看、收藏等。
  
容量估计和约束
假设我们有 15 亿总用户,其中 8 亿是每日活跃用户。如果平均而言,用户每天观看五个视频,则每秒总视频观看次数为:
800M * 5 / 86400 sec => 46K videos/sec
假设我们的上传:观看比率为 1:200,即对于每个视频上传,我们有 200 个视频观看,我们每秒上传 230 个视频。
500 hours * 60 min * 50MB => 1500 GB/min (25 GB/sec)
存储估算:假设每分钟有 500 小时的视频上传到 Youtube。如果平均而言,一分钟的视频需要 50MB 的存储空间(视频需要以多种格式存储),则一分钟内上传的视频所需的总存储空间为:
500 hours * 60 min * 50MB => 1500 GB/min (25 GB/sec)
这些数字是估计的,忽略视频压缩和复制,这将改变我们的估计。
带宽估计:每分钟上传 500 小时的视频,假设每个视频上传需要 10MB/分钟的带宽,我们每分钟将获得 300GB 的上传量。
500 hours * 60 mins * 10MB => 300GB/min (5GB/sec)
假设上传:观看比率为 1:200,我们将需要 1TB/s 的传出带宽。
  
系统API
我们可以使用 SOAP 或 REST API 来公开我们服务的功能。以下可能是用于上传和搜索视频的 API 的定义:

uploadVideo(api_dev_key, video_title, vide_description, tags[], category_id, default_language, 
                        recording_details, video_contents)
Parameters:
api_dev_key (string): The API developer key of a registered account. This will be used to, among other things, 
throttle users based on their allocated quota.


video_title (string): Title of the video.
vide_description (string): Optional description of the video.
tags (string[]): Optional tags for the video.
category_id (string): Category of the video, e.g., Film, Song, People, etc.
default_language (string): For example English, Mandarin, Hindi, etc.
recording_details (string): Location where the video was recorded.
video_contents (stream): Video to be uploaded.


Returns: (string)
一个成功的上传将返回HTTP 202(请求被接受),一旦视频编码完成。 
一旦视频编码完成,就会通过电子邮件通知用户,并提供一个访问视频的链接。我们还可以公开一个可查询的API,让用户知道他们上传视频的当前状态。 
让用户知道他们上传视频的当前状态。
 
searchVideo(api_dev_key, search_query, user_location, maximum_videos_to_return, page_token)
Parameters:
api_dev_key (string): The API developer key of a registered account of our service.
search_query (string): A string containing the search terms.
user_location (string): Optional location of the user performing the search.
maximum_videos_to_return (number): Maximum number of results returned in one request.
page_token (string): This token will specify a page in the result set that should be returned.

Returns: (JSON)
一个包含符合搜索查询的视频资源列表信息的JSON。
每个视频资源将有一个视频标题、一个缩略图、一个视频创建日期和它的浏览次数。

 

高级设计
在高层次上,我们需要以下组件:
处理队列:
  1. 每个上传的视频都会被推送到一个处理队列中,稍后从队列中取出以进行编码、缩略图生成和存储。
  2. 编码器: 将每个上传的视频编码为多种格式。
  3. 缩略图生成器: 我们需要为每个视频制作一些缩略图。
  4. 视频和缩略图存储: 我们需要将视频和缩略图文件存储在一些分布式文件存储中。
  5. 用户数据库: 我们需要一些数据库来存储用户的信息,例如姓名、电子邮件、地址等。
  6. 视频元数据存储: 元数据数据库将存储有关视频的所有信息,如标题、系统中的文件路径、上传用户、总观看次数、喜欢、不喜欢等。此外,它还将用于存储所有视频评论。

  
数据库模式
视频元数据存储 - MySql视频元数据可以存储在 SQL 数据库中。每个视频都应存储以下信息:
  • 视频ID
  • 标题
  • 描述
  • 尺寸
  • 缩略图
  • 上传者/用户
  • 总赞数
  • 不喜欢的总数
  • 总观看次数

对于每条视频评论,我们需要存储以下信息:
  • 评论ID
  • 视频ID
  • 用户身份
  • 评论
  • 创作时间

用户数据存储——MySql
  • 用户 ID、姓名、电子邮件、地址、年龄、注册详细信息等。

 
详细的组件设计
该服务的读取量很大,因此我们将专注于构建一个可以快速检索视频的系统。我们可以预期我们的读写比率为 200:1,这意味着每个视频上传有 200 个视频观看。

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

我们应该如何有效地管理读取流量?我们应该将读取流量与写入流量分开。由于我们将拥有每个视频的多个副本,因此我们可以将读取流量分布在不同的服务器上。对于元数据,我们可以有主从配置,其中写入将首先发送到主服务器,然后在所有从服务器上重放。这样的配置可能会导致数据过时,例如当添加一个新视频时,它的元数据将首先插入到主服务器中,并且在它被从服务器重放之前,我们的从服务器将无法看到它,因此将返回过时的结果给用户。这种陈旧性在我们的系统中可能是可以接受的,因为它的寿命很短,用户将能够在几毫秒后看到新视频。

缩略图将存储在哪里?缩略图将比视频多得多。如果我们假设每个视频都有五个缩略图,那么我们需要一个非常高效的存储系统来服务于巨大的读取流量。在决定将哪个存储系统用于缩略图之前,有两个考虑因素:

  1. 缩略图是小文件,每个最大 5KB。
  2. 与视频相比,缩略图的阅读流量将是巨大的。用户将一次观看一个视频,但他们可能正在查看一个包含 20 个其他视频缩略图的页面。

让我们评估将所有缩略图存储在磁盘上。鉴于我们有大量文件;要读取这些文件,我们必须对磁盘上的不同位置执行大量搜索。这是非常低效的,并且会导致更高的延迟。

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

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

视频编码:将新上传的视频存储在服务器上,并在处理队列中添加一个新任务,将视频编码为多种格式。一旦所有编码完成;上传者收到通知,视频可供查看/共享。
 
元数据分片
由于我们每天都有大量的新视频,并且我们的读取负载也非常高,因此我们需要将数据分布到多台机器上,以便我们可以高效地执行读/写操作。我们有很多选择来分片我们的数据。让我们逐一介绍对这些数据进行分片的不同策略:
基于 UserID 的分片:我们可以尝试将特定用户的所有数据存储在一台服务器上。在存储时,我们可以将 UserID 传递给我们的哈希函数,该函数会将用户映射到数据库服务器,我们将在其中存储该用户视频的所有元数据。在查询用户的视频时,我们可以要求我们的哈希函数找到保存用户数据的服务器,然后从那里读取它。要按标题搜索视频,我们必须查询所有服务器,每个服务器都会返回一组视频。然后,集中式服务器将汇总这些结果并对其进行排名,然后再将它们返回给用户。
这种方法有几个问题:

  1. 如果用户变得流行怎么办?持有该用户的服务器上可能会有很多查询,从而造成性能瓶颈。这将影响我们服务的整体表现。
  2. 随着时间的推移,与其他用户相比,一些用户最终可能会存储大量视频。保持不断增长的用户数据的均匀分布是相当困难的。

为了从这些情况中恢复,我们必须重新分区/重新分配我们的数据,或者使用一致的散列来平衡服务器之间的负载。
基于 VideoID 的分片:我们的哈希函数会将每个 VideoID 映射到一个随机服务器,我们将在其中存储该视频的元数据。要查找用户的视频,我们将查询所有服务器,每个服务器将返回一组视频。集中式服务器将在将这些结果返回给用户之前对其进行汇总和排名。这种方法解决了我们的热门用户问题,但将其转移到了热门视频。
我们可以通过在数据库服务器前引入缓存来存储热门视频来进一步提高我们的性能。
 

视频去重
拥有大量用户,上传大量视频数据,我们的服务将不得不处理广泛的视频复制。重复的视频通常在宽高比或编码方面有所不同,可能包含叠加层或额外的边框,或者可能是较长的原始视频的摘录。重复视频的泛滥可能会在多个层面产生影响:
  1. 数据存储:我们可能会通过保留同一视频的多个副本来浪费存储空间。
  2. 缓存:重复的视频会占用可用于独特内容的空间,从而导致缓存效率下降。
  3. 网络使用:增加必须通过网络发送到网络内缓存系统的数据量。
  4. 能源消耗:更高的存储、低效的缓存和网络使用会导致能源浪费。

对于最终用户而言,这些低效率将以重复搜索结果、更长的视频启动时间和流式传输中断的形式实现。
对于我们的服务,重复数据删除在用户上传视频的早期最有意义;与对它进行后期处理以稍后查找重复视频相比。内联重复数据删除将为我们节省大量可用于编码、传输和存储视频副本的资源。一旦任何用户开始上传视频,我们的服务就可以运行视频匹配算法(例如,块匹配相位相关等)来查找重复项。如果我们已经有正在上传的视频的副本,我们可以停止上传并使用现有的副本,或者使用新上传的视频(如果它的质量更高)。如果新上传的视频是现有视频的子部分,反之亦然,我们可以智能地将视频分成更小的块,这样我们只上传那些丢失的部分。
  
负载均衡
我们应该在缓存服务器之间使用一致性哈希,这也有助于平衡缓存服务器之间的负载。由于我们将使用基于静态哈希的方案将视频映射到主机名,因此由于每个视频的受欢迎程度不同,可能会导致逻辑副本上的负载不均。例如,如果一个视频变得流行,则与该视频对应的逻辑副本将比其他服务器经历更多的流量。然后,逻辑副本的这些不均匀负载可以转化为相应物理服务器上的不均匀负载分布。要解决此问题,一个位置的任何繁忙服务器都可以将客户端重定向到同一缓存位置中不太繁忙的服务器。对于这种情况,我们可以使用动态 HTTP 重定向。
然而,使用重定向也有它的缺点。首先,由于我们的服务尝试在本地进行负载平衡,如果接收重定向的主机无法提供视频,则会导致多次重定向。此外,每次重定向都需要客户端发出额外的 HTTP 请求;在视频开始播放之前,它还会导致更高的延迟。此外,层间(或跨数据中心)重定向会将客户端引导到较远的缓存位置,因为较高层缓存仅存在于少数位置。
 
缓存
为了服务全球分布的用户,我们的服务需要一个大规模的视频传输系统。我们的服务应该使用大量地理分布的视频缓存服务器将其内容推向更靠近用户的地方。我们需要有一个策略来最大化用户性能,并在其缓存服务器上平均分配负载。

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

我们如何构建更智能的缓存?如果我们遵循 80-20 规则,即 20% 的视频每日阅读量产生 80% 的流量,这意味着某些视频非常受欢迎,以至于大多数人观看它们;因此,我们可以尝试缓存 20% 的每日视频和元数据读取量。
 
内容交付网络 (CDN)
CDN 是一个分布式服务器系统,它根据用户的地理位置、网页的来源和内容交付服务器向用户交付 Web 内容。看看我们缓存章节中的“CDN”部分。

我们的服务可以将最受欢迎的视频移至 CDN:

CDN 在多个位置复制内容。视频更有可能更接近用户并且跳数更少,视频将从更友好的网络流式传输。
CDN 机器大量使用缓存,并且主要可以提供内存不足的视频。
不被 CDN 缓存的不太受欢迎的视频(每天 1-20 次观看)可以由我们在各个数据中心的服务器提供。
 
容错
我们应该使用一致性哈希在数据库服务器之间进行分配。一致的哈希不仅有助于替换死服务器,还有助于在服务器之间分配负载。

点击标题