设计类Google Drive服务:打造千万日活用户的云存储服务

本文首发于公众号:更AI (power_ai),欢迎关注,编程、AI干货及时送!

近年来,诸如 Google Drive、Dropbox、Microsoft OneDrive 和 Apple iCloud 这样的云存储服务变得非常流行。在本章中,你被要求设计 Google Drive。

在深入设计之前,让我们花点时间理解 Google Drive。Google Drive 是一个文件存储和同步服务,它可以帮助你在云中存储文档、照片、视频和其他文件。你可以从任何电脑、智能手机和平板电脑访问你的文件。你可以轻松地与朋友、家人和同事分享这些文件[1]。图 15-1 和 15-2 分别展示了浏览器和移动应用上的 Google Drive 的样子。

image-20230525205244319

image-20230525205255735

第一步 - 理解问题并确定设计范围

设计 Google Drive 是一个大项目,因此提出问题以缩小范围是非常重要的。

候选人:最重要的功能是什么?
面试官:上传和下载文件,文件同步,以及通知。

候选人:这是一个移动应用,还是网页应用,或者两者都是?
面试官:两者都是。

候选人:支持的文件格式有哪些?
面试官:任何类型的文件。

候选人:文件需要进行加密吗?
面试官:是的,存储中的文件必须加密。

候选人:是否存在文件大小的限制?
面试官:是的,文件必须小于或等于10GB。

候选人:这个产品有多少用户?
面试官:每日活跃用户为1000万。

在本章中,我们将关注以下功能:

  • 添加文件。添加文件最简单的方式就是将文件拖放到 Google Drive 中。
  • 下载文件。
  • 在多个设备间同步文件。当一个设备上添加了文件,它会自动同步到其他设备。
  • 查看文件的历史版本。
  • 与你的朋友,家人和同事分享文件。
  • 当文件被编辑、删除,或者有人与你共享文件时,发送通知。

本章未讨论的功能包括:

  • Google文档的编辑和协作。Google文档允许多人同时编辑同一文档。这超出了我们的设计范围。

除了明确需求外,理解非功能性需求也非常重要:

  • 可靠性。对于存储系统来说,可靠性极其重要。数据丢失是无法接受的。
  • 快速同步速度。如果文件同步花费太多时间,用户会变得不耐烦并放弃产品。
  • 带宽使用。如果产品占用了大量不必要的网络带宽,用户会不高兴,尤其是他们使用移动数据计划时。
  • 可扩展性。系统应能处理高流量。
  • 高可用性。当一些服务器离线、速度降低或出现意外网络错误时,用户仍应能够使用系统。

粗略估算

  • 假设该应用有5000万注册用户和1000万日活跃用户。

  • 用户获得10GB免费空间。

  • 假设用户每天上传2个文件,平均文件大小为500KB。

  • 读写比例为1:1。

  • 总共分配的空间:5000万 * 10GB = 500PB

  • 上传API的每秒查询数 (QPS):1000万 * 2次上传 / 24小时 / 3600秒 = ~240

  • 峰值QPS = QPS * 2 = 480

第二步 - 提出高级设计并获得认可

我们将采取一种略有不同的方式,而不是一开始就展示高级设计图。我们将从简单的事情开始:在单个服务器中构建一切。然后,逐渐扩大规模,以支持百万用户。通过这个练习,你会重新熟悉本书中涉及的一些重要话题。

让我们从以下列出的单服务器设置开始:

  • 一个用于上传和下载文件的web服务器。
  • 一个数据库,用于跟踪元数据,如用户数据,登录信息,文件信息等。
  • 一个存储系统,用于存储文件。我们分配1TB的存储空间来存储文件。

我们花了几个小时来设置一个Apache web服务器,一个MySql数据库,以及一个名为*drive/的目录作为根目录来存储上传的文件。在drive/*目录下,有一系列的目录,也称为命名空间。每个命名空间包含该用户上传的所有文件。服务器上的文件名保持与原始文件名相同。通过连接命名空间和相对路径,每个文件或文件夹可以被唯一标识。

图15-3显示了左侧的*/drive*目录的样子和右侧的扩展视图。

image-20230525205317891

API接口

API接口是什么样的?我们主要需要3个API接口:上传文件,下载文件,获取文件修订版本。

1. 上传文件至 Google Drive

支持两种类型的上传:

  • 简单上传。当文件大小较小时,使用这种上传类型。
  • 可恢复上传。当文件大小较大且网络中断的可能性较高时,使用这种上传类型。

以下是一个可恢复上传API的示例:

https://api.example.com/files/upload?uploadType=resumable

参数:

  • uploadType=resumable
  • data:需要上传的本地文件。

通过以下3个步骤实现可恢复上传[2]:

  • 发送初始请求以获取可恢复的URL。
  • 上传数据并监视上传状态。
  • 如果上传被中断,恢复上传。

2. 从 Google Drive 下载文件

API示例:https://api.example.com/files/download

参数:

  • path:下载文件的路径。

参数示例:
{
“path”: “/recipes/soup/best_soup.txt”
}

3. 获取文件修订版本

API示例:https://api.example.com/files/list_revisions

参数:

  • path:你希望获取修订历史的文件路径。
  • limit:返回的修订版本的最大数量。

参数示例:
{
“path”: “/recipes/soup/best_soup.txt”,
“limit”: 20
}

所有的API接口都需要用户认证并使用HTTPS。安全套接字层 (SSL) 保护客户端和后端服务器之间的数据传输。

脱离单服务器模式

随着更多文件的上传,你最终会看到如图 15-4 所示的空间已满警告。

image-20230525205344911

只剩下10 MB的存储空间!这是一个紧急情况,因为用户无法再上传文件。首先想到的解决方案是分片数据,使其存储在多个存储服务器上。图15-5显示了基于user_id的分片示例。

image-20230525205355571

你彻夜未眠地设置数据库分片并密切监控。一切又开始顺利运作。你已经扑灭了火,但你仍然担心在存储服务器出现故障的情况下可能出现的数据丢失。你询问周围的人,你的后端大师朋友Frank告诉你,许多像Netflix和Airbnb这样的领先公司都使用Amazon S3进行存储。 “Amazon Simple Storage Service (Amazon S3)是一种对象存储服务,提供业界领先的可扩展性,数据可用性,安全性和性能”[3]。你决定做一些研究,看看它是否合适。

经过大量阅读,你对S3存储系统有了深入的理解,并决定将文件存储在S3中。Amazon S3支持同区域和跨区域复制。区域是Amazon Web Services (AWS)设有数据中心的地理区域。如图15-6所示,数据可以在同一区域(左侧)和跨区域(右侧)复制。为防止数据丢失并确保可用性,冗余文件存储在多个区域。存储桶就像文件系统中的文件夹。

image-20230525205416700

将文件放入S3后,你终于可以安心地睡个好觉,而不必担心数据丢失。为了防止将来发生类似的问题,你决定对可以改进的领域进行进一步的研究。以下是你发现的一些领域:

  • 负载均衡器:添加负载均衡器以分配网络流量。负载均衡器确保流量均匀分布,如果某个Web服务器宕机,它将重新分配流量。

  • Web服务器:添加负载均衡器后,可以根据流量负载轻松地添加/删除更多Web服务器。

  • 元数据数据库:将数据库移出服务器,以避免单点故障。同时,设置数据复制和分片,以满足可用性和可扩展性的要求。

  • 文件存储:Amazon S3用于文件存储。为确保可用性和耐久性,文件在两个独立的地理区域中复制。

在应用了以上改进之后,你已经成功地将Web服务器、元数据数据库和文件存储从单个服务器中解耦。更新的设计如图15-7所示。

image-20230525205441151

同步冲突

对于像Google Drive这样的大型存储系统,同步冲突时有发生。当两个用户同时修改同一个文件或文件夹时,就会发生冲突。我们如何解决这个冲突呢?我们的策略是:首先被处理的版本会胜出,而后被处理的版本会收到冲突。图15-8展示了一个同步冲突的例子。

image-20230525205450653

在图15-8中,用户1和用户2试图同时更新同一个文件,但是用户1的文件先被我们的系统处理了。用户1的更新操作顺利进行,但用户2却遇到了同步冲突。我们如何解决用户2的冲突呢?我们的系统呈现出同一文件的两份副本:用户2的本地副本和来自服务器的最新版本(图15-9)。用户2可以选择合并两个文件,或者用一个版本覆盖另一个版本。

image-20230525205501137

当多个用户同时编辑同一份文档时,保持文档同步是具有挑战性的。感兴趣的读者可以参考参考资料[4][5]。

高层设计

图15-10展示了我们提出的高层设计。让我们来研究一下系统的每一个组件。

image-20230525205518122

用户:用户通过浏览器或移动应用程序使用应用。

块服务器:块服务器将块上传到云存储。块存储,也被称为块级存储,是一种在云环境中存储数据文件的技术。一个文件可以被分割成若干个块,每个块都有一个唯一的哈希值,存储在我们的元数据数据库中。每个块都被视为一个独立的对象,存储在我们的存储系统(S3)中。为了重构一个文件,块会按照特定的顺序被拼接起来。关于块的大小,我们参考Dropbox:它设定了块的最大大小为4MB[6]。

云存储:一个文件被分割成更小的块并存储在云存储中。

冷存储:冷存储是一种用于存储不活跃数据的计算机系统,意味着文件在很长一段时间内都没有被访问。

负载均衡器:负载均衡器在API服务器之间均匀分配请求。

API服务器:这些服务器负责几乎所有除了上传流程之外的事情。API服务器用于用户认证,管理用户配置文件,更新文件元数据等。

元数据数据库:它存储用户、文件、块、版本等的元数据。请注意,文件存储在云中,元数据数据库只包含元数据。

元数据缓存:一些元数据被缓存以便快速检索。

通知服务:这是一个发布者/订阅者系统,允许数据在某些事件发生时从通知服务传送到客户端。在我们的特定案例中,当文件在其他地方被添加/编辑/移除时,通知服务通知相关客户端,这样他们可以拉取最新的更改。

离线备份队列:如果客户端离线并且无法拉取最新的文件更改,离线备份队列会存储这些信息,以便当客户端在线时同步这些更改。

我们已经讨论了Google Drive的高层设计。其中一些组件是复杂的,值得仔细研究;我们将在深度解析中详细讨论这些。

步骤3 - 深度设计

在本节中,我们将仔细研究以下内容:块服务器、元数据数据库、上传流程、下载流程、通知服务、节省存储空间以及故障处理。

块服务器

对于经常更新的大文件,每次更新都发送整个文件会消耗大量的带宽。我们提出了两项优化措施,以尽量减少传输的网络流量:

  • 增量同步。当文件被修改时,只同步修改过的块,而不是使用同步算法 [7] [8] 同步整个文件。
  • 压缩。对块进行压缩可以显著减少数据大小。因此,我们根据文件类型使用压缩算法对块进行压缩。例如,我们使用 gzip 和 bzip2 来压缩文本文件。压缩图像和视频则需要不同的压缩算法。

在我们的系统中,块服务器承担了上传文件的重要工作。块服务器将从客户端传来的文件进行处理,将文件分割成块,压缩每一个块,并对它们进行加密。而不是将整个文件上传到存储系统,只有修改过的块会被传输。

图 15-11 展示了当添加新文件时,块服务器如何工作。

image-20230525205536338

  • 文件被切割成更小的块。
  • 使用压缩算法对每个块进行压缩。
  • 为了保证安全性,在将每个块发送到云存储之前,每个块都会被加密。
  • 将块上传到云存储。

图 15-12 展示了增量同步,这意味着只有修改过的块被传输到云存储。突出显示的 “块2” 和 “块5” 代表了已改变的块。使用增量同步,只有这两个块被上传到云存储。

image-20230525205547497

通过提供增量同步和压缩,块服务器让我们能够节省网络流量。

高一致性需求

我们的系统默认要求强一致性。不同的客户端在同一时间显示不同的文件是无法接受的。系统需要为元数据缓存和数据库层提供强一致性。

内存缓存默认采用最终一致性模型,这意味着不同的副本可能有不同的数据。为了实现强一致性,我们必须确保以下内容:

  • 缓存副本和主数据库中的数据一致。
  • 在数据库写入时使缓存失效,以确保缓存和数据库持有相同的值。

在关系数据库中实现强一致性很容易,因为它维护了ACID(原子性、一致性、隔离性、持久性)属性 [9]。然而,NoSQL数据库默认不支持ACID属性。ACID属性必须在同步逻辑中通过编程方式进行引入。在我们的设计中,我们选择关系数据库,因为它原生支持ACID。

元数据数据库

图 15-13 展示了数据库模式设计。请注意,这是一个高度简化的版本,因为它只包括最重要的表和有趣的字段。

image-20230525205603475

User :用户表包含关于用户的基本信息,如用户名、电子邮件、个人资料照片等。

Device :设备表存储设备信息。Push_id 用于发送和接收移动推送通知。请注意,一个用户可以有多个设备。

Namespace :命名空间是用户的根目录。

File :文件表存储与最新文件相关的所有内容。

File_version :它存储了文件的版本历史。现有的行是只读的,以保持文件修订历史的完整性。

Block :它存储了与文件块相关的所有内容。任何版本的文件都可以通过按正确顺序连接所有块来重构。

上传流程

我们来讨论当客户端上传文件时发生了什么。为了更好地理解这个流程,我们绘制了图15-14所示的序列图。

image-20230525205617620

在图15-14中,两个请求并行发送:添加文件元数据和将文件上传到云存储。这两个请求都来自客户端1。

  • 添加文件元数据。
    1. 客户端1发送请求,添加新文件的元数据。
    2. 在元数据数据库中存储新文件的元数据,并将文件上传状态更改为“待处理”。
    3. 通知通知服务正在添加一个新文件。
    4. 通知服务通知相关客户端(客户端2)正在上传文件。
  • 将文件上传到云存储。
    2.1 客户端1将文件内容上传到块服务器。
    2.2 块服务器将文件切割成块,压缩、加密这些块,并将它们上传到云存储。
    2.3 一旦文件上传完成,云存储触发上传完成回调。请求被发送到API服务器。
    2.4 在元数据数据库中将文件状态更改为“已上传”。
    2.5 通知通知服务文件状态已更改为“已上传”。
    2.6 通知服务通知相关客户端(客户端2)文件已完全上传。

当一个文件被编辑时,流程是类似的,所以我们不会重复描述。

下载流程

当文件在其他地方被添加或编辑时,会触发下载流程。客户端如何知道一个文件被另一个客户端添加或编辑了呢?客户端有两种方式可以知道:

  • 如果客户端A在线,而文件被另一个客户端改变,通知服务将通知客户端A,告知有地方发生了变化,因此它需要拉取最新的数据。

  • 如果客户端A在文件被另一个客户端改变时离线,数据将被保存到缓存中。当离线客户端再次上线时,它会拉取最新的变化。

一旦客户端知道文件已经被更改,它首先通过API服务器请求元数据,然后下载块以构造文件。图15-15展示了详细的流程。请注意,由于空间限制,图中只显示了最重要的组件。

image-20230525205656698

  1. 通知服务告知客户端2某个地方的文件已经更改。
  2. 一旦客户端2知道有新的更新可用,它就会发送一个请求来获取元数据。
  3. API服务器调用元数据数据库来获取变化的元数据。
  4. 元数据返回给API服务器。
  5. 客户端2获取元数据。
  6. 一旦客户端接收到元数据,它就会向块服务器发送请求,下载块。
  7. 块服务器首先从云存储下载块。
  8. 云存储将块返回给块服务器。
  9. 客户端2下载所有新的块来重构文件。

通知服务

为了保持文件的一致性,任何本地执行的文件变更都需要通知给其他客户端,以减少冲突。通知服务就是为此目的而建立的。从高层来看,通知服务允许数据在事件发生时传输给客户端。这里有几个选项:

  • 长轮询。Dropbox使用长轮询[10]。
  • WebSocket。WebSocket在客户端和服务器之间提供了一个持久的连接。通信是双向的。

尽管这两个选项都能很好地工作,但我们选择长轮询,原因有两个:

  • 通知服务的通信不是双向的。服务器将文件更改的信息发送给客户端,但不是反过来。
  • WebSocket适用于实时双向通信,比如聊天应用。对于Google Drive,通知是不频繁的,没有大量的数据流。

使用长轮询,每个客户端都会与通知服务建立一个长轮询连接。如果检测到对文件的更改,客户端将关闭长轮询连接。关闭连接意味着客户端必须连接到元数据服务器下载最新的更改。收到响应后或连接超时后,客户端立即发送新的请求以保持连接开放。

节省存储空间

为了支持文件版本历史并确保可靠性,相同文件的多个版本会存储在多个数据中心。如果频繁备份所有文件的修订版本,存储空间可能会很快被填满。我们提出三种技术来减少存储成本:

  • 数据块去重。消除账户级别的冗余块是节省空间的简单方法。如果两个块具有相同的哈希值,那么它们就是相同的。
  • 采用智能数据备份策略。可以应用两种优化策略:
    • 设置限制:我们可以设定存储版本的数量限制。如果达到限制,最旧的版本将被新版本替换。
    • 仅保存有价值的版本:有些文件可能被频繁编辑。例如,对一个大量修改的文档保存每个编辑过的版本可能意味着在短时间内该文件被保存了超过1000次。为了避免不必要的副本,我们可以限制保存的版本数量。我们给予最近版本更多的权重。实验可以帮助找出保存版本的最佳数量。
  • 将不常用的数据移动到冷存储。冷数据是指几个月或几年未被活动的数据。像Amazon S3 Glacier[11]这样的冷存储比S3便宜很多。

故障处理

在大规模系统中可能会出现故障,我们必须采用设计策略来处理这些故障。你的面试官可能对你如何处理以下系统故障感兴趣:

  • 负载均衡器故障:如果负载均衡器出现故障,备用负载均衡器将变为活动状态并接管流量。负载均衡器通常使用心跳进行监控,这是在负载均衡器之间发送的周期性信号。如果一个负载均衡器一段时间内没有发送心跳,就被认为是故障。

  • 块服务器故障:如果一个块服务器出现故障,其他服务器会接管未完成或待处理的任务。

  • 云存储故障:S3桶在不同区域内多次复制。如果在一个区域内无法获取文件,可以从其他区域获取。

  • API服务器故障:它是一个无状态服务。如果API服务器出现故障,负载均衡器会将流量重定向到其他API服务器。

  • 元数据缓存故障:元数据缓存服务器会被复制多次。如果一个节点挂了,你仍然可以访问其他节点获取数据。我们会启动一个新的缓存服务器来替换失败的节点。

  • 元数据数据库故障。

    • 主节点故障:如果主节点故障,提升其中一个从节点作为新的主节点,并启动一个新的从节点。
    • 从节点故障:如果从节点故障,你可以使用另一个从节点进行读取操作,并启动另一个数据库服务器来替换故障的节点。
  • 通知服务故障:每个在线用户都与通知服务器保持长轮询连接。因此,每个通知服务器都与许多用户连接。根据Dropbox在2012年的演讲[6],每台机器都有超过100万个打开的连接。如果服务器出现故障,所有的长轮询连接都会丢失,所以客户端必须重新连接到另一个服务器。尽管一个服务器可以保持许多打开的连接,但它不能一次性重新连接所有丢失的连接。与所有丢失的客户端重新建立连接是一个相对较慢的过程。

  • 离线备份队列故障:队列被复制多次。如果一个队列失败,队列的消费者可能需要重新订阅备份队列。

第4步 - 总结

在本章中,我们提出了一种支持Google Drive的系统设计。强一致性、低网络带宽和快速同步使这个设计很有趣。我们的设计包含两个流程:管理文件元数据和文件同步。通知服务是系统的另一个重要组成部分。它使用长轮询来使客户端与文件变更保持同步。

就像任何系统设计面试问题一样,没有完美的解决方案。每个公司都有其独特的约束,你必须设计一个适合这些约束的系统。了解你的设计和技术选择的权衡是很重要的。如果还有几分钟的时间,你可以谈谈不同的设计选择。

例如,我们可以直接从客户端将文件上传到云存储,而不是通过块服务器。这种方法的优点是它使文件上传更快,因为文件只需要被传输一次到云存储。在我们的设计中,文件首先被传输到块服务器,然后再传输到云存储。然而,新的方法有一些缺点:

  • 首先,相同的分块、压缩和加密逻辑必须在不同的平台(iOS、Android、Web)上实现。这很容易出错,需要大量的工程努力。在我们的设计中,所有这些逻辑都在一个集中的地方实现:块服务器。
  • 其次,由于客户端很容易被黑客攻击或操纵,因此在客户端实现加密逻辑并不理想。

系统的另一个有趣的发展是将在线/离线逻辑移动到一个单独的服务。我们把它叫做在线状态服务。通过将在线状态服务移出通知服务器,其他服务可以轻松地整合在线/离线功能。

恭喜你走到这一步!现在给自己一个鼓励。做得好!

参考资料

[1] Google Drive:https://www.google.com/drive/

[2] 上传文件数据:https://developers.google.com/drive/api/v2/manage-uploads

[3] Amazon S3:https://aws.amazon.com/s3

[4] 差分同步:https://neil.fraser.name/writing/sync/

[5] 差分同步 YouTube 讲解:https://www.youtube.com/watch?v=S2Hp_1jqpY8

[6] 如何扩展 Dropbox:https://youtu.be/PE4gwstWhmc

[7] Tridgell, A., & Mackerras, P. (1996). The rsync algorithm.

[8] Librsync. (n.d.). Retrieved April 18, 2015, from https://github.com/librsync/librsync

[9] ACID:https://en.wikipedia.org/wiki/ACID

[10] Dropbox 安全白皮书:https://www.dropbox.com/static/business/resources/Security_Whitepaper.pdf

[11] Amazon S3 Glacier:https://aws.amazon.com/glacier/faqs/

你好,我是拾叁,7年开发老司机、互联网两年外企5年。怼得过阿三老美,也被PR comments搞崩溃过。这些年我打过工,创过业,接过私活,也混过upwork。赚过钱也亏过钱。一路过来,给我最深的感受就是不管学什么,一定要不断学习。只要你能坚持下来,就很容易实现弯道超车!所以,不要问我现在干什么是否来得及。如果你还没什么方向,可以先关注我[公众号:更AI (power_ai)],这里会经常分享一些前沿资讯和编程知识,帮你积累弯道超车的资本。

猜你喜欢

转载自blog.csdn.net/smarter_AI/article/details/131798205