需求
让我们设计一个像Youtube这样的视频共享服务,用户可以上传/查看/搜索视频。类似服务:netflix.com、vimeo.com、dailymotion.com、veoh.com
难度等级:中等
1.为什么是Youtube?
Youtube是世界上最流行的视频分享网站之一。该服务的用户可以上传、查看、共享、评分和报告视频,以及对视频添加评论。
2.系统的要求和目标
为了进行此练习,我们计划设计一个更简单的Youtube版本,并满足以下要求:
功能要求:
1.用户应该能够上传视频。
2.用户应该能够共享和查看视频。
3.用户应该能够根据视频标题进行搜索。
4.我们的服务应该能够记录视频的统计数据,例如喜欢/不喜欢、总浏览量等
5.用户应该能够添加和查看视频评论。
非功能性要求:
1.系统应高度可靠,上传的任何视频都不应丢失。
2.该系统应具有高可用性。一致性可能会受到影响(为了可用性);如果一个用户有一段时间没有看到视频,那应该没问题。
3.用户在观看视频时应具有实时体验,并且不应感到任何滞后。不在范围内:视频推荐、最受欢迎的视频、频道、订阅、稍后观看、收藏夹等。
3.容量估计和限制
假设我们有15亿总用户,其中8亿是日常活跃用户。如果用户平均每天观看五个视频,则每秒的总视频观看量为:
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或RESTAPI来公开服务的功能。以下可能是用于上传和搜索视频的API的定义:
上传 API:
代码语言:javascript复制uploadVideo(api_dev_key, video_title, vide_description, tags[], category_id,
default_language,recording_details, video_contents)
参数:
代码语言:javascript复制api_dev_key(string):注册帐户的api开发者密钥。除其他外,这将用于根据分配的配额限制用户。
video_title (string):视频的标题。
vide_description(string):视频的可选描述。
tags (string[]):视频的可选标签。
category_id (string):视频的类别,例如电影、歌曲、人物等。
default_language (string):例如英语、普通话、印地语等。
recording_details (string):录制视频的位置。
video_contents (stream):要上传的视频。
返回结果:
成功上传将返回HTTP 202(请求已接受),视频编码完成后,通过电子邮件通知用户访问视频的链接。我们还可以公开一个可查询的API,让用户知道他们上传视频的当前状态。
搜索API:
代码语言:javascript复制searchVideo(api_dev_key, search_query, user_location, maximum_videos_to_return,
page_token)
参数:
代码语言:javascript复制api_dev_key(string):我们服务的注册帐户的api开发者密钥。
search_query (string):包含搜索词的字符串。
user_location (string):执行搜索的用户的可选位置。
maximum_videos_to_return (number):一个请求中返回的最大结果数。
page_token (string):此标记将在结果集中指定应返回的页面。
返回结果:
包含与搜索查询匹配的视频资源列表信息的JSON。每个视频资源都有视频标题、缩略图、视频创建日期和视图计数。
代码语言:javascript复制streamVideo(api_dev_key, video_id, offset, codec, resolution)
参数:
api_dev_key(string):我们服务的注册帐户的api开发者密钥。
video_id (string):用于标识视频的字符串。
offset (number):我们应该能够从任何偏移量流式传输视频;该偏移量是从视频开始的时间(以秒为单位)。如果我们支持播放/暂停来自多个设备的视频,我们将需要在服务器上存储偏移量。这将使用户能够从停止的同一点开始在任何设备上观看视频。
codec (string) & resolution(string) :我们应该从客户端发送API中的编解码器和分辨率信息,以支持从多个设备播放/暂停。假设您正在电视的Netflix应用程序上观看视频,暂停视频,然后开始在手机的Netflix应用程序上观看视频。在这种情况下,您将需要编解码器和分辨率,因为这两个设备具有不同的分辨率并使用不同的编解码器。
返回结果:
来自给定偏移量的媒体流(视频块)。
5.高级设计
在高层,我们需要以下组件:
1.处理队列:每个上传的视频将被推送到一个处理队列,稍后将被取消队列,以进行编码、缩略图生成和存储。
2.编码器:将每个上传的视频编码为多种格式。
3.缩略图生成器:为每个视频生成几个缩略图。
4.视频和缩略图存储:将视频和缩略图文件存储在某个分布式文件中存储
5.用户数据库:存储用户信息,如姓名、电子邮件、地址等。
6.视频元数据存储:一个元数据数据库,用于存储有关视频的所有信息,如标题、系统中的文件路径、上载用户、总视图、好恶等。它还将用于存储所有视频评论。
6.数据库模式
视频元数据存储-MySql
视频元数据可以存储在SQL数据库中。
每个视频应存储以下信息:
•视频ID
•头衔
•说明
•尺寸
•缩略图
•上传者/用户
•喜欢的总数
•不喜欢的总数
•视图总数
对于每个视频评论,我们需要存储以下信息:
•评论ID•视频ID
•用户ID
•评论
•创建用户数据存储的时间-MySql
•用户名、姓名、电子邮件、地址、年龄、注册详情等。
7.详细部件设计
这项服务的阅读量会很大,因此我们将重点构建一个能够快速检索视频的系统。我们可以预期我们的读写比为200:1,这意味着每次上传视频都有200个视频视图。
视频将存储在哪里?
视频可以存储在分布式文件存储系统中,如HDFS或GlusterFS。
我们应该如何有效地管理读取流量?我们应该将读流量与写流量分开。因为每个视频都有多个副本,所以我们可以在不同的服务器上分配读取流量。对于元数据,我们可以采用主从配置,写入操作将首先进入主服务器,然后应用于所有从服务器。这种配置可能会导致数据过时,例如,当添加新视频时,其元数据将首先插入主视频中,在将其应用于从视频之前,我们的从视频将无法看到它;因此,它将向用户返回过时的结果。在我们的系统中,这种陈旧性可能是可以接受的,因为它会非常短暂,用户可以在几毫秒后看到新的视频。
缩略图将存放在哪里?
缩略图将比视频多得多。如果我们假设每个视频都有五个缩略图,那么我们需要一个非常高效的存储系统,能够为巨大的读取流量提供服务。在决定应将哪个存储系统用于缩略图之前,需要考虑两个因素:
1.缩略图是一种小文件,比如说,每个缩略图的最大容量为5KB。
2.与视频相比,缩略图的阅读流量将是巨大的。用户将观看一个视频
一次,但他们可能会看到一个有20个其他视频缩略图的页面。
如何将所有缩略图存储在磁盘上。
考虑到我们有大量的文件,我们必须对磁盘上的不同位置执行大量搜索以读取这些文件。这是非常低效的,会导致更高的延迟。
Bigtable在这里是一个合理的选择,因为它将多个文件组合成一个块存储在磁盘上,并且在读取少量数据时非常高效。这两项都是我们服务中最重要的两项要求。在缓存中保留热缩略图也将有助于改善延迟,并且,由于缩略图文件的大小很小,我们可以轻松地在内存中缓存大量此类文件。
视频上传:由于视频可能很大,如果上传时连接中断,我们应该支持从同一点恢复。
视频编码:新上传的视频存储在服务器上,并将新任务添加到处理队列中,以将视频编码为多种格式。完成所有编码后,将通知上传者,视频可供查看/共享。
8.元数据分片
由于我们每天都有大量的新视频,而且我们的读取负载非常高,因此,我们需要将数据分发到多台机器上,以便高效地执行读取/写入操作。我们有很多选择来共享数据。让我们逐一介绍不同的数据分片策略:
基于UserID的分片:
我们可以尝试将特定用户的所有数据存储在一台服务器上。在存储过程中,我们可以将UserID传递给hash函数,该函数将用户映射到数据库服务器,在那里我们将存储该用户视频的所有元数据。在查询用户的视频时,我们可以要求哈希函数找到保存用户数据的服务器,然后从那里读取数据。要按标题搜索视频,我们必须查询所有服务器,每个服务器将返回一组视频。然后,集中式服务器将对这些结果进行聚合和排序,然后再将它们返回给用户。
这种方法有两个问题:
1.如果用户变得流行怎么办?在容纳该用户的服务器上可能有很多查询;这可能会造成性能瓶颈。这也会影响我们服务的整体表现。
2.随着时间的推移,与其他用户相比,一些用户最终可以存储大量视频。保持不断增长的用户数据的统一分布是相当棘手的。
要从这些情况中恢复,我们必须重新分区/重新分发数据,或者使用一致的哈希来平衡服务器之间的负载。
基于VideoID的分片:
我们的散列函数将把每个VideoID映射到一个随机服务器,我们将在那里存储该视频的元数据。要查找用户的视频,我们将查询所有服务器,每个服务器将返回一组视频。在将结果返回给用户之前,集中式服务器将对这些结果进行聚合和排序。这种方法解决了流行用户的问题,但将其转移到流行视频。
我们可以通过在数据库服务器前面引入缓存来存储热门视频,从而进一步提高性能
9、视频重复数据消除
随着大量用户上传大量视频数据,我们的服务将不得不处理广泛的视频复制。重复的视频通常在纵横比或编码上有所不同,可以包含重叠或附加边框,也可以是较长原始视频的摘录。重复视频的激增会在多个层面上产生影响:
1.数据存储:保存同一视频的多个副本可能会浪费存储空间。
2.缓存:重复的视频会占用存储空间,从而降低缓存效率
可用于独特的内容。
3.网络使用:重复的视频也会增加必须通过网络发送的数据量
网络缓存系统中的网络到。
4.能耗:较高的存储、低效的缓存和网络使用可能导致能源浪费。
对于最终用户,这些低效率将以重复搜索结果、更长的视频启动时间和中断流媒体的形式实现。
对于我们的服务,重复数据消除在早期最有意义;当用户上传视频时,将其与后期处理进行比较,以便稍后查找重复的视频。内联重复数据消除将为我们节省大量资源,用于编码、传输和存储视频的重复副本。一旦任何用户开始上传视频,我们的服务就可以运行视频匹配算法(例如块匹配、相位相关等)来查找重复。如果我们已经有一份正在上传的视频副本,我们可以停止上传并使用现有副本,或者继续上传并使用新上传的质量更高的视频。如果新上传的视频是现有视频的一个子部分,或者是现有视频的一个子部分,我们可以智能地将视频分成更小的块,这样我们就只上传缺失的部分。
10、负载平衡
我们应该在缓存服务器之间使用一致的哈希,这也有助于平衡缓存服务器之间的负载。由于我们将使用基于静态哈希的方案将视频映射到主机名,因此由于每个视频的受欢迎程度不同,可能会导致逻辑副本上的负载不均匀。例如,如果某个视频变得流行,则与该视频相对应的逻辑副本将经历比其他服务器更多的流量。然后,逻辑副本的这些不均匀负载可以转化为相应物理服务器上的不均匀负载分布。若要解决此问题,请在一台服务器上安装任何忙碌的服务器
位置可以将客户端重定向到同一缓存位置中不太繁忙的服务器。我们可以在这个场景中使用动态HTTP重定向。
然而,使用重定向也有其缺点。首先,由于我们的服务试图在本地实现负载平衡,因此如果接收重定向的主机无法为视频提供服务,则会导致多个重定向。此外,每个重定向都需要客户端发出额外的HTTP请求;在视频开始播放之前,它还会导致更高的延迟。此外,层间(或跨数据中心)重定向会将客户端引导到远程缓存位置,因为较高层缓存仅存在于少量位置。
11、缓存
为了服务全球分布的用户,我们的服务需要一个大规模的视频传输系统。我们的服务应该使用大量地理分布的视频缓存服务器将其内容推近用户。我们需要一种策略,该策略将最大限度地提高用户性能,并在缓存服务器上均匀分配负载。
我们可以为元数据服务器引入缓存来缓存热数据库行。在访问数据库之前使用Memcache缓存数据和应用程序服务器,可以快速检查缓存是否具有所需的行。对于我们的系统来说,最近最少使用(LRU)是一种合理的缓存逐出策略。在此策略下,我们首先放弃最近查看最少的行。
我们如何构建更智能的缓存?如果我们遵循80-20规则,即每天20%的视频阅读量产生80%的流量,这意味着某些视频非常受欢迎,以至于大多数人都会观看它们;因此,我们可以尝试缓存每天20%的视频和元数据读取量。
12、内容交付网络(CDN)
CDN是一个分布式服务器系统,根据用户的地理位置、网页的来源和内容交付服务器向用户交付web内容。请看缓存一章中的“CDN”部分。
我们的服务可以将流行视频移动到CDN:
•CDN在多个位置复制内容。视频更接近用户的可能性更大,跳数更少,视频将从更友好的网络中传输。
•CDN机器大量使用缓存,并且大部分可以在内存不足的情况下提供视频。
CDN未缓存的不太受欢迎的视频(每天1-20次)可以由我们的服务器在各种数据中心。
13、容错性
我们应该在数据库服务器之间使用一致的散列。一致性哈希不仅有助于替换死机服务器,而且有助于在服务器之间分配负载。
参考资料
grok_system_design_interview.pdf