0098 系统设计——Instagram设计

设计Instagram

让我们设计一个像Instagram这样的照片共享服务,用户可以在其中上传照片以与其他用户共享。类似服务:Flickr,Picasa难度级别:中

1.什么是Instagram?#

Instagram是一项社交网络服务,可让其用户上传和与其他用户共享他们的照片和视频。Instagram用户可以选择公开或私下共享信息。任何其他用户都可以看到任何公开共享的内容,而私有共享的内容只能由一组指定的人访问。Instagram还允许其用户通过许多其他社交网络平台进行共享,例如Facebook,Twitter,Flickr和Tumblr。

为了进行此练习,我们计划设计一个更简单的Instagram版本,一个用户可以共享照片,也可以关注其他用户。每个用户的“新闻提要”将由该用户关注的所有人的顶照组成。

2.系统的要求和目标#

设计Instagram时,我们将重点关注以下要求:

功能要求

  1. 用户应该能够上传/下载/查看照片。
  2. 用户可以根据照片/视频标题进行搜索。
  3. 用户可以关注其他用户。
  4. 该系统应该能够生成并显示用户的新闻提要,其中包含来自用户关注的所有人的热门照片。

非功能需求

  1. 我们的服务需要高度可用。
  2. 对于新闻提要生成,系统可接受的延迟时间为200毫秒。
  3. 如果用户有一段时间没有看到照片,一致性可能会受到影响(为了可用性)。应该没问题
  4. 该系统应高度可靠;任何上传的照片或视频都不会丢失。

**不在范围内:**在照片中添加标签,在标签中搜索照片,在照片中添加评论,在照片中为用户添加标签,关注对象等。

3.一些设计考虑#

该系统将是繁重的读取工作,因此我们将集中精力构建一个可以快速检索照片的系统。

  1. 实际上,用户可以上传任意数量的照片。在设计此系统时,有效的存储管理应该是一个关键因素。
  2. 观看照片时,期望低延迟。
  3. 数据应该是100%可靠的。如果用户上传了照片,系统将保证照片不会丢失。

4.容量估算和约束#

  • 假设我们有5亿总用户,每天有100万活跃用户。
  • 每天有200万张新照片,每秒有23张新照片。
  • 照片平均大小=> 200KB
  • 1天照片所需的总空间

2M * 200KB => 400 GB

  • 10年所需的总空间:

400GB * 365(每年的天数)* 10(年)〜= 1425TB

5.高级系统设计#

0098 系统设计——Instagram设计_第1张图片
从高层次上讲,我们需要支持两种方案,一种方案是上传照片,另一种方案是查看/搜索照片。我们的服务将需要一些对象存储服务器来存储照片,还需要一些数据库服务器来存储有关照片的元数据信息。

6.数据库架构编号

in 在访谈的早期阶段定义数据库模式将有助于理解各个组件之间的数据流,稍后将指导数据分区。
0098 系统设计——Instagram设计_第2张图片
我们需要存储有关用户,他们上传的照片以及他们关注的人的数据。照片表将存储与照片有关的所有数据;我们需要在(PhotoID,CreationDate)上建立索引,因为我们需要先获取最近的照片。

存储上述模式的一种直接方法是使用像MySQL这样的RDBMS,因为我们需要连接。但是关系数据库面临着挑战,尤其是当我们需要扩展它们时。有关详细信息,请看一下SQL vs. NoSQL。

我们可以将照片存储在HDFS或S3等分布式文件存储中。

我们可以将上述架构存储在分布式键值存储中,以享受NoSQL提供的好处。与照片相关的所有元数据都可以转到一个表,其中“键”将是“ PhotoID”,“值”将是一个包含PhotoLocation,UserLocation,CreationTimestamp等的对象。

我们需要存储用户和照片之间的关系,以了解谁拥有哪张照片。我们还需要存储用户关注的人员列表。对于这两个表,我们都可以使用像Cassandra这样的宽列数据存储。对于“ UserPhoto”表,“键”为“ UserID”,“值”为用户拥有的“ PhotoID”列表,存储在不同的列中。对于“ UserFollow”表,我们将有一个类似的方案。

通常,Cassandra或键值存储区始终维护一定数量的副本以提供可靠性。另外,在此类数据存储中,删除不会立即应用,数据会保留几天(以支持取消删除),然后才能从系统中永久删除。

7.数据大小估计#

让我们估计每张表中将要存储多少数据,以及十年后我们将需要多少存储空间。

**用户:**假设每个“ int”和“ dateTime”为四个字节,则用户表中的每一行将为68个字节:

用户ID(4个字节)+名称(20个字节)+电子邮件(32个字节)+ DateOfBirth(4个字节)+ CreationDate(4个字节)+ LastLogin(4个字节)= 68个字节

如果我们有5亿用户,那么我们将需要32GB的总存储空间。

5亿* 68〜= 32GB

**照片:“**照片”表中的每一行将为284个字节:

PhotoID(4个字节)+ UserID(4个字节)+ PhotoPath(256个字节)+ PhotoLatitude(4个字节)+ PhotLongitude(4个字节)+ UserLatitude(4个字节)+ UserLongitude(4个字节)+ CreationDate(4个字节)= 284个字节

如果每天上传200万张新照片,则一天将需要0.5GB的存储空间:

2M * 284字节〜=每天0.5GB

10年后,我们将需要1.88TB的存储空间。

UserFollow: UserFollow表中的每一行将由8个字节组成。如果我们有5亿用户,并且平均每个用户关注500个用户。UserFollow表需要1.82TB的存储空间:

5亿用户* 500位关注者* 8字节〜= 1.82TB

所有表在10年内所需的总空间将为3.7TB:

32GB + 1.88TB + 1.82TB〜= 3.7TB

8.组件设计#

照片上载(或写入)可能会很慢,因为它们必须进入磁盘,而读取会更快(尤其是从缓存中获取照片时)。

上载用户会消耗所有可用的连接,因为上载是一个缓慢的过程。这意味着,如果系统忙于所有写请求,则无法提供“读”服务。我们应该记住,在设计系统之前,Web服务器有连接限制。如果我们假设一个Web服务器在任何时候最多可以有500个连接,那么它最多不能有500个并发上载或读取。为了解决这个瓶颈,我们可以将读取和写入分为单独的服务。我们将有专用的服务器用于读取,而有不同的服务器用于写入,以确保上传不会占用系统。

分离照片的读写请求也将使我们能够分别缩放和优化这些操作。
0098 系统设计——Instagram设计_第3张图片

9.可靠性和冗余#

丢失文件不是我们服务的一种选择。因此,我们将存储每个文件的多个副本,以便如果一个存储服务器死了,我们可以从另一台存储服务器上存在的另一副本中检索照片。

同样的原理也适用于系统的其他组件。如果要使系统具有较高的可用性,则需要在系统中运行多个服务副本,以便在少数服务中断时,系统仍保持可用并正在运行。冗余消除了系统中的单点故障。

如果在任何时候只需要运行一个服务的实例,我们就可以运行该服务的冗余辅助副本,该副本不提供任何流量,但是当主节点出现问题时,它可以在故障转移之后获得控制权。

在系统中创建冗余可以消除单点故障,并在危机中需要时提供备份或备用功能。例如,如果生产中正在运行同一服务的两个实例,而一个实例发生故障或降级,则系统可以故障转移到正常副本。故障转移可以自动发生或需要手动干预。
0098 系统设计——Instagram设计_第4张图片

10.数据分片#

让我们讨论元数据分片的不同方案:

一个。基于UserID进行分区 假设我们基于“ UserID”进行分片,以便将用户的所有照片保留在同一分片上。如果一个数据库分片为1TB,我们将需要四个分片来存储3.7TB的数据。为了获得更好的性能和可伸缩性,我们保留10个分片。

因此,我们将通过UserID%10查找分片号,然后将数据存储在那里。为了唯一地标识我们系统中的任何照片,我们可以在每个PhotoID后面添加分片号。

**我们如何生成PhotoID?**每个数据库分片都可以具有自己的PhotoID自动递增序列,并且由于我们将ShardID与每个PhotoID附加在一起,因此它将在整个系统中变得唯一。

此分区方案有哪些不同的问题?

  1. 我们将如何处理热门用户?有好几个人关注这些热门用户,还有很多其他人看到他们上传的任何照片。
  2. 与其他用户相比,某些用户将拥有很多照片,因此存储分配不均匀。
  3. 如果我们无法将用户的所有图片存储在一个分片上怎么办?如果我们将用户的照片分发到多个分片上,会导致更高的延迟吗?
  4. 将用户的所有照片存储在一个分片上可能会引起问题,例如,如果该分片掉线,则该用户的所有数据均不可用;如果该分片服务于高负载,则可能导致更高的延迟等。

b。基于PhotoID的分区 如果我们可以先生成唯一的PhotoID,然后通过“ PhotoID%10”找到一个碎片号,则上述问题将得到解决。在这种情况下,我们不需要在ShardID后面附加PhotoID,因为PhotoID本身在整个系统中都是唯一的。

**我们如何生成PhotoID?**这里我们不能在每个分片中都有一个自动递增的序列来定义PhotoID,因为我们需要首先知道PhotoID才能找到将其存储在其中的分片。一种解决方案是我们专用一个单独的数据库实例来生成自动递增的ID。如果我们的PhotoID可以容纳64位,则可以定义一个仅包含64位ID字段的表。因此,每当我们要在系统中添加照片时,都可以在此表中插入新行,并将该ID用作新照片的PhotoID。

**生成密钥的数据库不是单点故障吗?**是的,会的。一种解决方法是定义两个这样的数据库,其中一个生成偶数编号的ID,另一个生成奇数编号的。对于MySQL,以下脚本可以定义此类序列:

KeyGeneratingServer1:
auto-increment-increment = 2
auto-increment-offset = 1
 
KeyGeneratingServer2:
auto-increment-increment = 2
auto-increment-offset = 2

我们可以在这两个数据库的前面放置一个负载均衡器,以在两个数据库之间进行轮循并处理停机时间。这两个服务器可能都不同步,其中一个服务器比另一个服务器生成更多的密钥,但这不会对我们的系统造成任何问题。我们可以通过为用户,照片注释或系统中存在的其他对象定义单独的ID表来扩展此设计。

**或者,**我们可以实现类似于在设计URL缩短服务(如TinyURL)中讨论的“密钥”生成方案。

我们如何计划系统的未来发展? 我们可以有大量的逻辑分区来适应将来的数据增长,这样一开始,多个逻辑分区将驻留在单个物理数据库服务器上。由于每个数据库服务器上都可以有多个数据库实例,因此对于任何服务器上的每个逻辑分区,我们都可以有单独的数据库。因此,只要我们感觉到特定的数据库服务器具有大量数据,就可以将一些逻辑分区从该数据库迁移到另一台服务器。我们可以维护一个配置文件(或单独的数据库),该配置文件可以将逻辑分区映射到数据库服务器。这将使我们能够轻松移动分区。每当我们要移动分区时,只需更新配置文件即可宣布更改。

11.排名和新闻提要生成#

要为任何给定的用户创建新闻提要,我们需要获取该用户关注的人的最新,最受欢迎和相关的照片。

为简单起见,假设我们需要为用户的新闻提要获取前100张照片。我们的应用程序服务器将首先获取用户关注的人员列表,然后从每个用户获取最新的100张照片的元数据信息。在最后一步,服务器会将所有这些照片提交给我们的排名算法,该算法将确定前100张照片(基于新近度,相似度等)并将其返回给用户。这种方法可能出现的问题是更高的延迟,因为我们必须查询多个表并对结果执行排序/合并/排序。为了提高效率,我们可以预先生成News Feed,并将其存储在单独的表格中。

**预先生成新闻提要:**我们可以拥有专用的服务器,这些服务器可以连续生成用户的新闻提要并将它们存储在“ UserNewsFeed”表中。因此,只要任何用户需要其新闻源的最新照片,我们都将简单地查询该表并将结果返回给用户。

每当这些服务器需要生成用户的新闻提要时,它们将首先查询UserNewsFeed表以查找上一次为该用户生成新闻提要的时间。然后,从该时间起将生成新的新闻提要数据(遵循上述步骤)。

将新闻摘要内容发送给用户的方法有哪些?

**1.拉取:**客户端可以定期或从需要时手动从服务器拉取News Feed的内容。这种方法可能的问题是:a)在客户端发出拉取请求之前,可能不会向用户显示新数据。b)在大多数情况下,如果没有新数据,则拉取请求将导致响应为空。

**2.推送:**服务器可以在可用时立即将新数据推送给用户。为了有效地进行管理,用户必须与服务器维护长轮询请求以接收更新。这种方法可能存在的问题是,关注很多人的用户或拥有数百万关注者的名人用户;在这种情况下,服务器必须非常频繁地推送更新。

**3.混合:**我们可以采用混合方法。我们可以将所有关注度很高的用户转移到基于拉动的模型,而只能将数据推送给关注度数百(或千)的用户。另一种方法可能是服务器将更新推送给所有用户的频率不超过某个特定频率,从而使用户可以进行大量关注/更新以定期提取数据。

有关生成News Feed的详细讨论,请参阅Designing Facebook’s Newsfeed。

12.使用分片数据创建新闻提要#

为任何给定用户创建新闻提要的最重要要求之一是从该用户关注的所有人那里获取最新照片。为此,我们需要一种机制来对照片的创建时间进行排序。为了有效地做到这一点,我们可以将照片创建时间作为PhotoID的一部分。由于我们将拥有有关PhotoID的主要索引,因此可以很快找到最新的PhotoID。

我们可以为此使用纪元时间。假设我们的PhotoID有两个部分;第一部分代表纪元时间,第二部分代表自动递增序列。因此,要制作一个新的PhotoID,我们可以采用当前的时间,并从生成密钥的数据库中添加一个自动递增的ID。我们可以从此PhotoID中找出分片号(PhotoID%10),并将照片存储在那里。

PhotoID的大小可能是多少?假设我们的时代开始于今天,那么接下来的50年我们需要多少位来存储秒数?

86400秒/天* 365(一年中的天)* 50(年)=> 16亿秒

我们将需要31位存储该数字。由于平均而言,我们期望每秒23张新照片;我们可以分配9位来存储自动递增的序列。因此,我们每秒可以存储(2 ^ 9 => 512)张新照片。我们可以每秒重置一次自动递增序列。

我们将在“ 设计Twitter ”中的“数据分片”下讨论有关此技术的更多详细信息。

13.缓存和负载平衡#

我们的服务将需要大规模的照片传送系统来服务于全球分布的用户。我们的服务应该使用大量按地理位置分布的照片缓存服务器并使用CDN将其内容推近用户,有关详细信息,请参阅Caching。

我们可以为元数据服务器引入缓存,以缓存热数据库行。在命中数据库之前,我们可以使用Memcache来缓存数据,而Application Server可以快速检查缓存是否具有所需的行。对于我们的系统,最近最少使用(LRU)可能是合理的缓存逐出策略。在此政策下,我们将首先丢弃最近浏览最少的行。

**我们如何建立更智能的缓存?**如果我们遵循80到20条规则,即照片的每日阅读量的20%产生了80%的访问量,这意味着某些照片非常受欢迎,以至于大多数人都阅读它们。这表明我们可以尝试缓存每日读取的照片和元数据的20%。

你可能感兴趣的:(System,Design)