📜  设计像Youtube一样的视频分享服务系统

📅  最后修改于: 2021-09-10 02:44:21             🧑  作者: Mango

视频共享服务系统的目的
Youtube 是基于广告的视频共享服务,允许用户上传基于视频的媒体内容。用户可以上传、观看、搜索、喜欢、不喜欢视频,对视频添加评论。用户只能以注销用户的身份上传/删除视频,但他们可以以注销用户的身份搜索/观看视频。该服务是基于广告的服务,并且是免费的,因此用户在观看视频时会看到广告。此外,用户可以通过他们的帐户关注其他用户或频道。此外,用户只有在登录系统时才能对视频添加评论。
在开始设计任何像照片和视频共享社交网络服务系统这样的系统之前,建议详细考虑系统边界和需求,并尝试了解未来(例如 5 年或 10 年)系统容量是多少。这一点很关键,因为如果系统的用户数量呈指数增长,系统的容量将不足以提供快速响应。在架构设计背后,您必须考虑五个(可用性、可靠性、弹性、耐用性、性价比)支柱。这些是我们应该一起考虑的支柱,因为它们是相互耦合的。简而言之,可用性意味着系统应该始终可用。可靠性意味着系统应该按预期工作。弹性意味着如果出现任何问题,系统将如何以及何时恢复自身。持久性是系统每个部分应该存在的支柱,直到我们删除。性价比也是一个重要的话题,基本上与在成本效率下使用服务有关。可以这样说,如果系统将建立在AWS上并且使用t2微型EC2实例就足够了,那么就没有任何理由使用更大的EC2实例并支付额外费用。
系统的要求和目标
Youtube 是最大的视频共享服务之一,它现在有很多组件,但过去不是这样创建的。每个系统都越来越关注增长,系统将拥有比它们的第一个版本更多的组件。现在我们将设计 Youtube 的主要功能。推荐系统、频道背景、搜索系统我们就不详细介绍了,因为它们都是单独的讨论话题。
所以,如果你想设计一个系统,你必须首先定义需求和系统边界。可能您将拥有一份服务设计文档,您将在此服务设计文档中定义需求、边界、架构决策和其他内容。 Youtube 是一种视频共享服务,用户可以上传/查看视频。所以你的系统将支持这些功能;
– 用户必须能够创建帐户。
– 每个注册用户必须有自己的个人账户页面。
– 用户必须能够登录系统和退出系统。
– 用户必须能够在登录时上传/删除系统中的视频。
– 用户必须能够在登录时对系统中的视频添加评论。
– 用户必须能够在登录或注销时观看系统中的视频。
– 用户必须能够在登录或注销时搜索视频/用户/群组。
– 用户必须能够关注频道。
– 用户必须能够喜欢/不喜欢视频。
– 用户可以喜欢或不喜欢视频,在这种情况下,系统应保留喜欢,不喜欢,评论,观看次数,以将这些数字呈现给用户。
– 系统必须能够监控。
– 系统必须能够支持公共和私人帐户。
– 系统必须能够支持公共和私人视频。
在定义系统边界和功能要求时,需要考虑云或承诺选项。你的系统可以;
– %100 承诺(您自己的数据中心/服务器)
– %100 云(AWS、谷歌云、Azure)
– 承诺和云的混合(您可以在迁移过程中同时拥有)
如今,云服务凭借着云机制的优势而大受欢迎。这些优势;
– 成本效益
– 高速
– 安全
– 备份解决方案
– 无限存储容量
– 许多不同的服务选项。你不需要从头开始创造世界
– 可靠性
– 耐用性
– 弹性
– 监控几乎所有服务
– 与其他服务的轻松软件集成
如果我们谈论 Youtube、Netflix 或其他大型可扩展系统,这意味着这些系统将面临大量流量。系统中可能同时有大量请求,系统应该倾向于实时响应所有请求。复制、分片和负载均衡器有助于系统高度可用。稍后我们将讨论所有三个功能。
此外,系统应以最小的延迟响应。你想象你是一个用户,你想在 Youtube 上做任何事情,你真的想等待很长时间才能得到回应吗?我认为您不想等待太多时间,而是想实时体验系统。为了说明这一点,当您在系统中搜索视频时,系统应尽快推荐相关视频。
让我们考虑一下设计边界;
由于很多用户将在系统中观看视频,因此服务将是大量读取的,因此系统将保持一致和可靠,这意味着不应该有任何数据丢失。此外,服务将是持久的,这意味着系统的所有部分都应该存在,直到它们被手动删除。在考虑容量之前,您必须定义服务的目的是什么。即使它对于承诺服务更重要,它对于承诺服务和云服务也是必不可少的,因为您可以根据目的选择正确的服务,根据可用区域定位它们并定义容量。这样的例子是;
– 创建比写服务更多的读服务。
– 根据操作类型选择服务器类型。
– 根据您的容量估计定义缓存策略。
– 根据您的要求选择数据库类型(SQL、NoSQL)。
– 根据您的容量估计定义备用解决方案。
– 根据您的要求等定义数据分片策略……
假设您的总用户数为 100M,我们假设读取流量大于写入流量,假设下载和上传数据的比例为 25:1。
我们将假设视频的平均大小为 250 MB,因此系统将具有;
5年内的视频容量;
– 5 * 100M * 1 * 250 MB = 120 PB。 (假设每个用户每年将上传 1 个视频)。
– 360 PB,带复制和备份。
此计算只是如何定义系统容量的一个简短示例,我们不会计算每日下载/上传容量和元数据容量,但您应该考虑此计算(以及每日读/写容量估计)以进行服务/数据库扩展。
此外,缓存是返回数据的基本系统。如果您正在构建大型可扩展系统,您必须考虑不同的缓存策略。您可以使用 CDN 作为视频内容缓存,使用 Redis/Memcache 作为元数据缓存。缓存的最大问题将是扩展(全局缓存机制)和缓存驱逐策略。如果您在 AWS 上构建您的服务,您可以使用 Cloudfront 作为 CDN(内容缓存),并且您可以使用 AWS 上的 elasticache 服务进行元数据缓存。请注意,Cloudfront 是高度可扩展的 AWS 服务,您无需处理维护/可扩展性。 AWS 将自行维护和扩展 Cloudfront 服务。
API设计
在当今世界,很多系统都支持移动平台,因此 API 是能够区分开发人员并支持移动支持的最佳选择。我们可以使用 REST 或 SOAP。许多大公司根据他们的系统更喜欢 REST 或 SOAP。我们将在下面提到三个主要的 API:
1- UploadVideo(apiKey, title, description, categoryID, language)
上传视频是我们应该提到的第一个 API。这个 API 基本上有五个主要属性。您可以向 UploadVideo API 添加更多属性。需要注意的是,apiKey是服务注册账号的开发者密钥。感谢 apiKey 我们可以消除黑客攻击。 UploadVideo 返回表明视频上传成功与否的 HTTP 响应。
2-删除视频(apiKey,videoID)
检查用户是否有权删除视频。如果操作已排队,它将返回 HTTP 响应 200(OK)、202(已接受)或基于您的响应的 204(无内容)。
3- GetVideo (apiKey, query, videoCountToReturn, pageNumber)
返回包含有关视频和频道列表的信息的 JSON。每个视频资源都会有标题、创建日期、喜欢计数、总观看次数、所有者和其他元信息。
**有更多的 API 来设计视频共享服务,但是,这三个 API 比其他 API 更重要。其他 API 会像视频、添加评论、搜索、推荐等……
数据库设计
定义数据库模式有两种选择。它们是 SQL 和 NoSQL。我们可以使用传统的数据库管理系统如 MsSQL 或 MySQL 来保存数据。如您所知,我们应该将有关视频和用户的信息保存在 RDBMS 中。其他有关视频的信息(称为元数据)也应保留。现在我们有主要的三个表来保存数据。 (请注意,我们只考虑了 Youtube 的基本属性。我们可以忘记推荐系统)。
用户
– 用户 ID(主键)
– 名称 (nvarchar)
– 年龄(整数)
– 电子邮件 (nvarchar)
– 地址 (nvarchar)
– 注册日期(DateTime)
– 上次登录(日期时间)
视频
– VideoID(主密钥 – 由 KGS 生成 – 密钥生成服务)
– 视频标题 (nvarchar)
– 尺寸(浮动)
– 用户 ID(带有用户表的外键)
– 描述 (nvarchar)
– CategoryID (int) : 注意我们可以创建 Category Table 来定义类别
– 点赞数 (int)
– 不喜欢的数量 (int)
– 显示数量 (int) – 我们可以使用 big int 来保持显示数量
– 上传日期 (DateT?me)
视频评论
– CommentID(主键)
– 用户 ID(带有用户表的外键)
– VideoID(带视频表的前键)
– 评论 (nvarchar)
– 评论日期(日期时间)
系统设计考虑
在基于 Web 的系统中可以找到一些基本功能。主要是客户端、Web 服务器、应用程序服务器、数据库和缓存系统。根据系统流量的强度,服务器或服务的数量会增加,负载均衡器会在这些服务器或服务之间分配传入的请求。此外,可以根据传入请求者的密度使用队列。队列操作可帮助用户避免等待更多时间来获得响应。在我们的 Youtube 服务中;
– 客户
– 网络服务器
– 应用服务器
– 数据库
– 视频存储
– 编码服务
– 队列
– 复制
– 冗余
– 负载均衡
– 分片
我们可以将服务分成三个部分以减少响应时间,因为视频上传需要更多时间来下载视频。可以从缓存中下载视频,从缓存中获取数据是一种快捷方式。客户端基本上是使用系统的用户。 Web Server 是第一个满足请求的实体。传入请求可以发生在上传服务、搜索服务或下载服务中。如果我们给异步下载视频的用户一个机会,系统流量就会得到缓解。编码器是将上传的视频编码成多种格式。共有三种类型的数据库,即视频内容数据库、用户数据库和视频元数据存储。队列过程发生在应用服务器和编码之间。
我们的 Youtube 服务会大量读取,我们在构建系统时应该小心。我们的主要目标应该是快速返回视频。我们可以在不同的服务器上保留视频的副本来处理流量问题。此外,这确保了该系统的安全性。我们可以使用负载平衡器将流量分配到不同的服务器。系统可以保留元数据库、用户数据库和视频内容数据库的两个副本。我们可以使用 CDN 来缓存流行数据。
系统流程图;
1- 客户端发送请求。
2- 请求满足来自网络服务器。
3- Web 服务器控制缓存。系统上可以有另外两个缓存数据库。
4- 如果请求发生在缓存中,则响应被重定向到客户端。
5- 否则,Web 服务器将请求重定向到应用程序服务器。
6- Web 服务器和应用程序服务器之间可以有负载均衡器。
** 如果此请求是搜索或查看服务,它会尝试通过查看元数据数据库和视频内容数据库来查找请求。负载均衡器可以放置在系统的每一层,例如应用服务器和视频内容存储之间、应用服务器和元数据数据库之间等等……
当服务器向客户端响应请求时,根据缓存过程缓存相关数据。
众所周知,Youtube 是一个巨大的视频共享系统。用户可以上传视频,上传视频的数量每天呈指数级增长。根据上传的视频,系统中可能还会多出一个相同的视频。为了消除视频的重复,我们可以实现一种智能算法。例如,当一个视频进入系统时,算法会自动检查该视频是否已经保存在系统中。如果系统已经有了这个视频,那么我们就不需要保留重复的数据了。它使我们免于不必要地使用空间。此外,如果传入的视频包含系统中保存的视频,那么我们可以将视频分成小块,我们只提供对相同视频块的唯一引用来处理重复问题。
复制和备份是提供我们之前提到的支柱的两个重要概念。复制是处理服务或服务器故障的一个非常重要的概念。复制可以是应用数据库服务器、Web 服务器、应用服务器、媒体存储等。实际上我们可以复制系统的所有部分。 (某些 AWS 服务,例如 Route53,它们本身具有高可用性,因此您无需处理 Route53、负载均衡器等的复制。)请注意,复制还有助于系统缩短响应时间。你想象一下,如果我们将传入的请求分成更多的资源而不是一个资源,系统可以轻松满足所有传入的请求。此外,每个资源的最佳副本数为 3 个或更多。您可以通过将数据保存在 AWS 中的不同可用区或不同区域来提供冗余。
对于缓存策略,我们可以通过使用缓存服务器来使用全局缓存机制。我们可以使用 Redis 或 memcache,但缓存策略最重要的部分是如何提供缓存驱逐。如果我们使用全局缓存服务器,我们会保证每个用户在缓存中看到相同的数据,但是如果我们使用全局缓存服务器,就会有时间延迟。作为缓存策略,我们可以使用 LRU(最近最少使用)算法。
对于媒体文件缓存,正如我们之前提到的,我们将使用 CDN。 CDN 位于不同的边缘位置,因此响应时间将比直接从 AWS S3 获取媒体内容要短。
负载均衡器允许根据特定标准将传入请求重定向到资源。我们可以在系统的每一层使用负载均衡器。如果我们想使用 AWS 负载均衡器服务,AWS 将支持三种不同的负载均衡器类型:
– 网络负载均衡器
– 经典负载均衡器(已弃用)
– 应用程序负载均衡器
对于此服务,应用程序负载均衡器将适合我们的服务,它还将自行处理 AZ 分配。否则你可以使用NGinx,但你必须实现算法,如果我们想使用NGinx,你必须提供维护。
我们可以使用负载均衡器;
– 在请求和 Web 服务器之间。
– 在 Web 服务器和应用程序服务器之间。
– 应用服务器和数据库之间
– 在应用服务器和图像存储之间。
– 在应用服务器和缓存数据库之间。
– 我们可以对负载均衡器使用轮询方法。 Round Robin 方法可以防止请求进入死服务器,但 Round Robin 方法不处理任何服务器流量大的情况。我们可以修改 Round Robin 方法,使其成为一种更智能的方法来处理这个问题。
视频上传是一个很大的过程。它应该与分片机制一起工作,当它失败时,系统应该确保它应该继续从失败点上传视频。
视频编码过程应该包括队列操作。当上传的视频到来时,这个新任务被添加到一个队列中,队列中的所有任务都会从队列中一一取出。
系统的基本编码

Java
// Java program for design
public enum AccountStatus{
  PUBLIC,
  PRIVATE,
  CLOSED
}
 
public enum VideoContentStatus {
  PENDING,
  PROCESSED,
  FAIL,
  REJECTED
}
 
public enum VideoStatus {
  PUBLIC,
  PRIVATE,
  DELETED
}
 
public enum VideoQuality {
  LOW,
  MIDDLE,
  HIGH
}
 
public class AddressDetails {
  private String street;
  private String city;
  private String country;
  ...
}
 
public class AccountDetails {
  private Date createdTime;
  private AccountStatus status;
  private boolean updateAccountStatus(AccountStatus accountStatus);
  ...
}
 
public class Comment {
  private Integer id;
  private User addedBy;
  private Date addedDate;
  private String comment;
 
  public boolean updateComment(String comment);
  ...
}
 
public class Video {
  private Integer id;
  private User createdBy;
  private String path;
  private VideoStatus videoStatus;
  private VideoContentStatus videoContentStatus;
  private int viewsCount;
 
  private HashSet userLikes;
  private HashSet userDisLikes;
  private HashSet userComments;
  ...
}
 
public class User {
  private int id;
  private String password;
  private String nickname;
  private String email;
  private AddressDetails addressDetails;
  private AccountDetails accountDetails;
  private UserRelations userRelations;
  private HashSet invitationsByMe;
  private HashSet invitationsToMe;
 
  private boolean updatePassword();
  public boolean uploadVideo(Video video);
  public List getVideos();
  ...
}