系统设计(四) 设计Instagram

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

1.什么是Instagram?

Instagram是一项社交网络服务,用户可以上传照片和视频,并与其他用户分享。Instagram用户可以选择公开或私下分享信息。任何公开共享的内容都可以被任何其他用户看到,而私有共享内容只能由一组特定的人访问。Instagram还使用户能够通过Facebook、Twitter、Flickr和Tumblr等许多其他社交网络平台进行共享。
为了这个练习,我们计划设计一个更简单的Instagram版本,用户可以分享照片,也可以关注其他用户。为每个用户提供的“News Feed”将包含用户关注的所有用户的顶部照片。

2.系统的要求和目标

在设计Instagram时,我们将重点关注以下几组需求:

功能需求
1.用户应能上传/下载/查看照片。
2.用户可以根据照片/视频标题进行搜索。
3.用户可以关注其他用户。
4.系统应该能够生成和显示用户的News Feed,其中包括来自所有用户关注的人的顶部的照片。

非功能性需求
1.我们的服务必须高度可用。
2.系统的可接受延迟为200 ms。
3.如果用户一段时间内没有看到图片了,这时可以不那么考虑一致性了(考虑到可用性),;这样应该是好的。
4.系统应高度可靠;任何上传的照片或视频都不应丢失。

不在范围内:
在照片中添加标签,在标签上搜索照片,评论照片,把用户标记到图片上,关注谁等等。

3.一些设计上的考虑

该系统的读量很大,因此我们将集中精力构建一个能够快速检索照片的系统。
1.实际上,用户可以随心所欲地上传多少张照片。储存的有效管理在设计这个系统时应该是一个关键因素。
2.在观看照片时,预期延迟较低。
3.数据应100%可靠。如果用户上传照片,系统将保证永不丢失。

4.容量估计和制约因素

·假设我们拥有5亿总用户,每天有100万活跃用户。·每天200万张新照片,每秒23张新照片。
·平均照片文件大小=>200 KB
·1天所需的总空间2M*200 KB=>400 GB
·10年所需空间总数:400 GB*365(天)*10(年)~=1425 TB

5.高层系统设计

在高层次上,我们需要支持两种场景,一种是上传照片,另一种是查看/搜索照片。
我们的服务需要一些对象存储服务器来存储照片,也需要一些数据库服务器来存储有关照片的元数据信息。

在这里插入图片描述

6.数据库模式

在面试的早期阶段定义DB模式将有助于理解数据在不同组件之间的流向,以及在以后的流程将指导我们进行数据分区。

我们需要存储关于用户的数据,他们上传的照片,以及他们关注的人。Photo表将存储与照片相关的所有数据;我们需要一个索引(PhotoID,CreationDate),因为我们需要先获取最近的照片。

在这里插入图片描述
存储上述模式的一个简单方法是使用像MySQL这样的RDBMS,因为我们需要连接。但是关系数据库也伴随着它们的挑战,特别是当我们需要扩展它们的时候。有关详细信息,请查看SQLvs.NoSQL。
我们可以将照片存储在像HDFS或S3这样的分布式文件存储中。
我们也可以将上述模式存储在分布式键值存储中,以享受NoSQL提供的好处。所有与照片相关的元数据都可以转到一个表,其中‘key’将是‘PhotoID’,‘value’将是一个包含PhotoLocation、UserLocation、CreationTime戳等的对象。
我们需要存储用户和照片之间的关系,以知道谁拥有哪张照片。我们还需要存储用户的列表。对于这两个表,我们可以使用宽列数据存储(如Cassandra)。对于“UserPhoto”表,‘key’将是‘UserId’,‘value’将是用户拥有的“PhotoID”列表,存储在不同的列中。对于‘UserFollow’表也是类似的方案。
Cassandra或键值存储在一般情况下,始终保持一定数量的数据备份,以提供可靠性。而且,在这样的数据存储中,删除不会立即应用,在永久从系统中删除之前,数据会被保留几天(以支持撤销删除)。

7.数据大小估计

让我们估计每个表中将有多少数据,以及我们10年所需的总存储量。
User:假设每个“int”和“dateTime”为4个字节,用户表中的每一行将为68个字节:
UserID(4个字节)+Name(20个字节)+Email(32个字节)+DateOfBirth(4个字节)+CreationDate(4个字节)+LastLogin(4个字节)=68个字节
如果我们有5亿用户,我们将需要32 GB的总存储空间。
5亿*68~=32 GB

Photo:照片表中的每一行都是284字节:
PhotoID (4 bytes) + UserID (4 bytes) + PhotoPath (256 bytes) + PhotoLatitude (4 bytes) + PhotLongitude(4 bytes) + UserLatitude (4 bytes) + UserLongitude (4 bytes) + CreationDate (4 bytes) = 284 bytes
如果每天上传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:
32 GB+1.88TB+1.82TB~=3.7TB

8.组件设计

照片上传(或写)可能很慢,因为它们必须进入磁盘,而读则会更快,特别是当它们是从缓存提供的时候。用户可以使用所有可用的连接,因为上传是一个缓慢的过程。这意味着,如果系统忙于处理所有的写请求,就不能提供“读”。在设计我们的系统之前,我们应该记住Web服务器有一个连接限制。如果我们假设一个Web服务器可以在任何时候拥有最多500个连接,那么它不可能有超过500个并发上传或读取。为了处理这个瓶颈,我们可以将读和写拆分到不同的服务中。我们将有专门的读取服务器和不同的写入服务器,以确保上传不会占用系统。
分离照片的读和写请求也将允许我们独立地扩展和优化这些操作。
在这里插入图片描述

9.可靠性和冗余

丢失文件不是我们服务的一个选项。因此,我们将存储每个文件的多个副本,这样,如果一个存储服务器挂了,我们就可以从另一个存储服务器上的副本中检索照片。
同样的原则也适用于系统的其他组件。如果我们想要系统的高可用性,我们需要在系统中运行多个服务副本,这样如果有几个服务挂掉了,系统仍然可以使用和运行。冗余解决了系统中的单点故障。
如果在任何一点上只需要运行一个服务实例,我们就可以运行服务的冗余次要副本,该服务不提供任何通信,但当主故障发生问题时,它可以在故障转移后进行控制。
在系统中创建冗余可以解决单个故障问题,并在发生危机时提供备份或备用功能。例如,如果同一服务的两个实例正在运行,而其中一个实例出现故障或退化,则系统可能会故障转移到健康副本。故障转移可以自动发生,也可能需要手动干预。

在这里插入图片描述

10.数据分片

让我们讨论不同的元数据切分方案:
A.基于UserId的分区
假设我们基于‘UserId’进行分割,这样我们就可以将用户的所有照片保存在同一个切片上。如果一个DB切片是1TB,我们需要四个碎片来存储3.7TB的数据。让我们假设为了更好的性能和可伸缩性,我们保留了10个切分。
因此我们将根据UserId%10找到碎片编号,然后将数据存储在那里。为了唯一地识别系统中的任何照片,我们可以在每个PhotoID中附加切片号。

我们如何生成PhotoID?
每个DB切片都可以有自己的PhotoID自动增量序列,并且由于我们将为每个PhotoID追加ShardID,将使它在整个系统中唯一。

这种分区方案有哪些不同的问题?
1.如何处理热用户?有些人关注了热门用户,可以看到热门用户上传的任何照片。
2.有些用户与其他用户相比会有很多照片,从而造成不统一的存储分配。
3.如果我们不能把一个用户的所有图片都存储在一个切片上怎么办?如果我们把用户的照片分配到多个切片上,它会导致更高的延迟吗?
4.将用户的所有照片存储在一个切片上可能会导致问题,例如,如果切片不可用了或由于服务高负荷导致高延迟,所有的用户数据都不可用了

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

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

这个生成DB的key不是一个单点故障吗?
是的。解决方法可能是定义两个这样的数据库,其中一个生成偶数ID,另一个生成奇数ID。
对于MySQL,下面的脚本可以定义这种序列:

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

我们可以在这两个数据库的前面放置一个负载均衡器,以在它们之间循环运行,并处理停机问题。这两个服务器不同步,都可能一个比另一个生成更多的key,但这不会在我们的系统中造成任何问题。我们可以通过为用户对象、图片-评论对象或系统中的其他对象定义单独的ID表来扩展这个设计。
或者,我们可以实现一个“key”生成方案,类似于我们在设计像TinyURL这样的URL缩短服务时所讨论的那样。

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

11.排名和News Feed生成

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

预生成News Feed
我们可以有专门的服务器不断地生成用户的新闻提要,并将它们存储在“UserNewsFeed”表中。因此,每当任何用户需要News Feed的最新照片时,我们只需查询此表并将结果返回给用户。
当这些服务器需要生成用户的News Feed时,它们将首先查询UserNewsFeed表,以查找服务器最后一次为该用户生成的News Feed。然后,从那时开始生成新的News Feed数据(按照上述步骤)。

向用户发送News Feed内容的不同方法是什么?
1.拉
客户端可以定期或在需要时手动从服务器上提取News Feed内容。这种方法可能存在的问题是:
(A)在客户端这边发出拉请求
(B)大多数情况下,如果没有新数据,则拉请求将导致空响应。

2.推
一旦可用,服务器就可以将新数据推送给用户。为了有效地管理这一点,用户必须与服务器维护一个长的Poll请求以接收更新。
这种方法的一个可能的问题是,一个关注很多人的用户或者一个拥有数百万粉丝的名人用户;在这种情况下,服务器必须频繁地推送更新。

3.混合
我们可以采用混合方法。
我们可以将所有有大量关注者关注的用户移动到一个基于拉的模型,并且只将数据推送给那些有几百个(或上千个)关注者关注的用户。
另一种方法可能是服务器向所有用户推送更新的频率不超过一定的频率,允许大量关注/更新的用户定期拉数据。
要详细讨论新闻源生成,请看Facebook的Newsfeed的设计。

12.使用共享数据创建News Feed

为任何给定用户创建News Feed的最重要要求之一是从用户关注的所有人那里获取最新照片。
为此,我们需要一种机制来对照片的创建时间进行排序。为了有效地做到这一点,我们可以将照片创建时间作为PhotoID的一部分。由于我们将PhotoID作为主键索引,所以很快就能找到最新的PhotoID。
我们可以利用配置纪元(epoch)时间。假设我们的PhotoID有两部分,第一部分代表epoch时间,第二部分是一个自动递增的序列。因此,为了创建一个新的PhotoID,我们可以用当前epoch时间,加上从生成key的DB中拿到的自动递增ID,得到PhotoID。我们可以从这个PhotoID中找出DB切片号(PhotoID%10)并在对应的切片中储存照片

我们的PhotoID的大小可以是多少?
假设我们的epoch从今天开始,我们需要多少比特来存储未来50年的秒数?
86400秒/天*365(天)*50(年)=>16亿秒
我们需要31位来存储这个数字。
平均来说,我们期望23张新照片每秒,这样我们可以分配9位来存储自动递增序列。因此,我们每秒钟都可以存储2^9=>512新照片。我们可以每隔一秒钟重新设置我们的自动递增序列。
我们将在设计Twitter时在“数据分割”下讨论更多关于这种技术的细节。

13.缓存和负载平衡

我们的服务将需要一个大规模的照片传送系统,以服务于全球分布的用户。我们的服务应该使用大量地理分布的照片缓存服务器将其内容更接近用户,并使用CDN(有关详细信息,请参阅缓存)。
我们可以为元数据服务器引入缓存,以缓存热数据库行。我们可以使用memcache来缓存数据,在访问数据库之前,应用程序服务器可以快速检查缓存是否有所需的行。
对我们的系统来说,最近使用最少的(LRU)是一个合理的缓存驱逐策略。在这个策略下,我们先抛弃最近看过的行

怎样才能建立更多的智能缓存?
如果我们遵循80-20的规则,即照片的每日阅读量的20%会产生80%的流量,这意味着某些照片非常受欢迎,以至于大多数人都会阅读它们。这意味着我们可以尝试缓存20%的照片和元数据的每日阅读量

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值