深入了解Amazon S3
关键字: [Amazon Web Services re:Invent 2024, 亚马逊云科技, Amazon S3, Amazon S3, Erasure Coding, Shuffle Sharding, Fault Tolerance, Random Choices]
导读
Amazon S3为开发人员和IT团队提供云对象存储服务,具有行业领先的可扩展性、持久性、安全性和性能。Amazon S3平均每秒处理超过1.5亿个请求,跨越数百万个硬盘,以应对世界上一些最大规模的工作负载。在本次会议中,来自Amazon S3团队的工程领导将深入探讨Amazon S3的内部运作机制。了解大规模运营存储系统背后的工程原理,以及这如何使各种规模的客户受益。
演讲精华
以下是小编为您整理的本次演讲的精华。
欢迎各位。我是Seth Markle。今天我和James Bourne Holt在一起。Alan稍后会加入,这是关于亚马逊S3的深入探讨。我认为今年我们有一个非常有趣的话题。
作为背景介绍,我是Seth Markle,大约15年来一直是S3的高级主要工程师,我的15周年纪念即将在2月到来。在我的任职期间,我曾在S3堆栈的各个组件上工作,包括对象索引、磁盘机群、前端系统,最近则是在Matt Garman今天的主题演讲中预览的S3元数据系统。
James Thornholt是一位主要工程师,他将很快加入我们。他一直在从事我们的磁盘机群工作,并与开源社区合作开发连接器,如Mount Point、Pie、Torch,最近他还参与了我们今天宣布的S3 Tables的推出。
我们今天讨论的重点将是利用规模为我们和最终为客户带来优势。我们的意思是,不是从如何构建系统来容忍规模的角度来审视规模,而是如何让规模使S3能够为客户提供更有效的服务。
在深入探讨这个主题之前,让我们先看一些统计数据,以说明S3的规模。作为一个对象存储,S3拥有超过4000万亿个对象,数据量达到艾字节。平均每天处理超过1.5亿个请求,发送超过2000亿个事件通知,在高峰时期,通过我们的前端机群在全球范围内提供每秒超过1PB的流量。
我们将通过三种方式来说明利用规模为您带来优势的概念。首先,我们将讨论数据的物理特性,研究硬件和软件的现实属性,以及它们如何决定系统行为。如果您在本次会议之前参加了Andy Warfield的主题演讲,您可能已经听到他提到我们将磁盘机群视为一个大型热力学系统的方法。我们将探讨这些属性如何随着系统规模的增长而得到改善。
其次,James将研究S3的规模如何使我们能够在面临现实世界的故障场景时提供一致的性能和运营体验。
最后,我们将总结讨论我们的规模如何让我们能够以一种最终有利于为客户更快交付的方式来设计容错。
让我们从讨论数据的物理特性开始。作为一名软件工程师,我经常思考算法效率、测试和文档。然而,鉴于今天上午一些Q&A开发人员的公告,我可能需要减少对测试和文档的思考。尽管如此,这些方面对我来说仍然是重中之重。
作为一名存储工程师,我经常考虑底层硬件的现实物理属性,这些属性无法仅通过更好的软件来解决。我们将逐一介绍这些物理属性,并展示存储系统如何随着规模的增长而得到改善。如前所述,我们通常将规模视为需要容忍规模增长,但今天我们将讨论系统实际上是因为更大的规模而得到改善。
S3已经发展到拥有数千万块硬盘,这些硬盘在极端的工作负载下运行,我们将进一步探讨。硬盘能够承受的工作量是有限的,如果我们无法将流量分布到这数千万块硬盘上,尽管我们有能力提供这种流量,但我们仍然会限制客户的成就。
然而,在大规模情况下,一些非常有用的模式开始显现,帮助我们平衡工作负载。我们将通过考虑系统中的三个参与者来研究这是如何运作的:您(客户)、系统中的硬件和系统中的软件。这三个元素将共同帮助我们描绘我所说的画面。
让我们从讨论硬件的物理特性开始。这里有一块硬盘。硬盘是机械组件,需要两个主要运动来读取数据。盘片绕着一个称为主轴的中心组件旋转,通常在讨论系统中的驱动器数量时会提到主轴。
除了旋转的盘片之外,还有一个称为执行器的机械臂,可以来回移动。它的末端有一个读写头,必须在盘片上移动以访问存储数据的特定区域。当您读取数据时,您会提供一个地址,这本质上是从0到驱动器容量的偏移量。这被称为逻辑块地址或LBA,它指示驱动器所需数据位于哪个盘片以及盘片上的何处。
然后,执行器移动到位,作为一个物理组件来回摆动。这种运动并不是瞬间发生的。一旦执行器位于正确的磁道上,所需数据很可能不会直接位于它下方,您必须等待一个旋转将数据带到盘片下方。
平均而言,这种旋转延迟大约是半个旋转,因为有时您刚刚错过了数据,而有时它就在您面前。执行器移动的动作称为寻道,移动所需的时间称为寻道时间。然后您必须等待旋转,这称为旋转延迟。
这两个现实世界的物理因素——寻道时间和旋转延迟——导致驱动器在读取数据时需要一些时间,这些时间是可测量的。这就是我们所说的硬盘有物理限制的原因。在固定的时间段内,只能进行有限的来回摆动。
接下来,让我们来看看我们的软件。当您在S3中存储一个对象时,我们会将该对象分解为我们称为分片的部分,并使用一种称为纠删码的算法将这些分片存储在我们的磁盘机群中的存储节点上。James将在他的部分中提供有关纠删码的更多细节。
这里重要的一点是,我们将您发送给我们的数据分块,并以冗余的方式存储在一组驱动器上。这些驱动器运行着一个名为Shard Store的软件,这是我们开发并在存储节点主机上运行的文件系统。有关Shard Store的公开论文,James将在他的部分中讨论。
从本质上讲,Shard Store是一种日志结构文件系统。这意味着当新的分片被写入时,我们会将它们连续地放置在一起。如果您还记得我描述驱动器操作的方式,使用驱动器的一部分开销和延迟是由执行器来回移动造成的。通过将数据并排放置在驱动器上,我们可以消除一些这种成本。因此,对于日志结构文件系统而言,写入变得更加高效。
然而,从读取的角度来看,我们没有这种奢侈。我们无法控制客户何时想要读取哪一部分数据,因此对于读取操作,我们不得不跳到盘片上任何数据所在的位置。读取是最糟糕的工作负载,我们必须为寻道和旋转延迟付出代价,在驱动器上四处跳跃。这种I/O类型通常被称为随机I/O,通常涉及寻道和驱动器的一些旋转。
所以,我们有硬件和软件。让我们来看一些工作负载。单个存储工作负载往往是非常突发性的。这意味着它们长时间处于空闲状态,然后突然变得非常繁重。一个很好的例子就是FINRA。
FINRA是亚马逊云科技和S3的一个重要客户。他们监管会员经纪自营商和证券市场,负责收集、处理和分析股票和期权市场的每一笔交易,以查找不当行为。
这涉及每天摄取数千亿条记录。8月份,他们连续两天都有一个峰值日,处理了9060亿条记录。平均而言,这相当于大约30TB的数据,他们必须在一天内处理这些数据,并且他们有一个严格的4小时SLA来处理这些数据。
他们的工作负载是我们所说的突发性工作负载,因为他们在相对较短的时间内(这4小时窗口期)密集访问他们的数据,然后在一天中的其余20小时处于空闲状态。在这20小时内,他们并非完全空闲,因为他们正在摄取新的记录,但他们将所有的批量处理都集中在4小时的窗口期内,留下20小时的相对轻量级活动。
让我们做一些数学计算,了解如果您自己构建存储系统,像FINRA这样的工作负载示例将需要多少驱动器。在这个例子中,这些并非FINRA的实际数字。我们有一个客户想要存储1PB的数据,并在1小时内处理这些数据。我使用这些数字来说明这一点。
如果我必须在1小时内访问1PB的数据,那么预期的访问速率将是每秒275GB,跨越这些数据。如果我们假设每个对象大约1MB,那就是大约10亿个对象,这是一个相当大的工作负载。
我们从讨论硬盘开始,如果您还记得的话,有两个主要操作:寻道和旋转。一个7200转/分钟的硬盘每分钟旋转7200次,计算出来大约是每次旋转8毫秒。由于平均而言您必须做半个旋转才能找到您的数据,因此平均读取的旋转延迟将是4毫秒。
寻道时间平均需要移动盘片半径的一半,所以另外4毫秒。还记得我们讨论过对象被分块为分片以实现冗余存储吗?在这个例子中,让我们假设1MB的对象被分块为0.5MB的分片,为了读取这0.5MB,我们必须从磁盘上扫描大约0.5MB。让我们再加上2毫秒用于这一部分。
总的来说,这次随机读取需要10毫秒 - 寻道时间、旋转延迟和传输时间。一秒有1000毫秒,如果一次读取需要10毫秒,那么每秒可以进行大约100次读取。而这些都是半兆字节的读取。所以如果每秒可以进行100次半兆字节的读取,那么从这个驱动器每秒可以读取50兆字节。
这种随机I/O的性质确实增加了大量开销 - 每个驱动器每秒50兆字节。回到最初的数字并将其联系起来,我有一个需要以275千兆字节/秒的峰值速率访问的一个拍字节的数据。如果只是存储这些数据而不访问它们,如果每个驱动器有20TB,我们现在通常使用的驱动器大小,我只需要50个驱动器。
然而,为了以每个驱动器50兆字节/秒的速率访问275千兆字节/秒,我的系统中需要超过5000个驱动器来支持这种工作负载。如果考虑访问速率而不是存储速率,驱动器数量会增加100倍。而且在这个特定的例子中,这些驱动器每天有23个小时处于空闲状态,因为我只在一天中的一个小时内使用这种I/O能力,从而导致了巨大的效率低下。你付费购买了100倍的容量,但有95%的时间都没有使用。
你们中的一些人可能会想,如果这只是一个单一工作负载可能出现的情况,而S3有数百万活跃客户,那么对于S3这个服务来说,这个问题是不是实际上要严重1,000,000倍?
这正是大规模运营的真正酷炫之处,因为单个工作负载实际上是非常突发的,但彼此无关的独立工作负载不会同时突发。在总体层面上,存储温度实际上变得出奇地可预测。是的,较新的数据往往很热,较小的对象往往很热,而我们有大量年轻和小的对象 - 超过400万亿个对象。我们也有大量旧的和大的对象。
当你将这些工作负载层层叠加时,整个系统突然变得更加可预测。当我们将它们聚合时,即使我们的峰值需求逐步增长,峰值与平均值之比也在下降,工作负载变得更加可预测。
这对于我们刚刚讨论的物理方程式来说实际上是非常棒的,因为我们可以将客户分散到比他们通常所需的存储空间要多得多的驱动器上。我们可以这样做,因为一些工作负载的峰值映射到其他工作负载的安静期。
这是我们的规模如何为你带来优势的第一部分。我们有成千上万的客户,可能就在这个房间里,他们的数据和工作负载分散在超过100万个驱动器上,尽管他们的存储占用空间远远不需要那么多。这样做有两个优势,因为你能够分散到大量的旋转磁盘上 - 记住,旋转磁盘是硬盘的旋转单元。你能够突发到这些旋转磁盘所提供的总吞吐量,即使你占用的空间并没有填满驱动器。而且由于任何单个客户只占据给定磁盘的一小部分,我们就获得了上一张幻灯片中所展示的良好的工作负载隔离,其中不同客户的峰值和谷值是不相关的。所有这一切都正是因为我们拥有如此巨大的规模才成为可能。
我花了相当多的时间来说明客户如何能够利用远远超出他们数据通常所需的驱动器的吞吐量。工作负载在总体上的可预测性只是故事的一部分。我们仍然必须关注流量在整个车队中的分布情况,以防止磁盘上出现热点和冷点。
这并不会自动发生。对象在总体上遵循一些可预测的模式,但随着时间的推移,我提到过较新的数据是较热的数据。所以如果我正在向一个驱动器写入数据,我写入的数据是新的,但一年后它就不太新了,两年后就更不新了。这些驱动器随着时间的推移会变得越来越冷,当然,删除操作可能会在那些数据中打些洞,我会在那里放入更多新的数据,它会变得稍微热一些,但远远没有新驱动器那么热。
如果不加以纠正,我们的系统将趋于冷却,记住,我们希望在所有旋转磁盘上平衡流量。拥有一堆已满但也无法提供我们所需吞吐量的旋转磁盘对任何人都没有好处。
shuffle sharding的想法实际上很简单。它不是静态地将工作负载分配给驱动器或任何其他资源(如CPU、GPU等),而是随机地将工作负载分散到整个车队,分散到我们的驱动器上。
当你执行put操作时,我们会随机选择一组驱动器来存放这些字节。也许我们选择这两个。下次你执行put操作时,即使是对同一个bucket,甚至是对同一个key,也无关紧要。我们会为该put操作选择一组不同的驱动器 - 它们可能会重叠。我们会随机选择使用哪些驱动器进行该put操作。
这是有意识地在系统中引入去相关性,对吗?这些对象来自同一个工作负载,同一个客户的同一个bucket,但我们强制它们在你的bucket内去相关。这意味着你不再受静态资源的约束了,对吗?
如果你有一个拍字节的数据,你实际上可以分散到比一个拍字节驱动器容量还要多得多的驱动器上。你可以获得Seth所说你的工作负载所需的5500个驱动器。如果你是一个小客户,你就可以免受噪音邻居效应的影响,因为如果有一个客户非常活跃,频繁接收对象,那个对象可能会分散在与你不同的一组驱动器上。即使你可能在一个驱动器上重叠,你也可以访问其他包含该数据的驱动器。
我们可以让你与你所竞争的高强度工作负载去相关。shuffle sharding的这个想法实际上非常酷,而且对于团队思考S3资源的方式来说是非常基础的。我们对此的心智模型是,当我们说S3是弹性的时候,我们的意思是任何工作负载都应该能够突发到使用我们所有的驱动器,我们拥有数千万个驱动器,只要它们不会相互干扰。
这意味着如果你是FINRA,你可以在一天中的那一个小时内突发到每秒数TB的带宽,如果你必须这样做的话,远远超出你的存储占用空间或容量可能允许的范围。shuffle sharding是一种强大的机制,可以高效地共享资源,并有意识地在工作负载之间创建去相关性。
一旦你了解了shuffle sharding的这个想法,它实际上就会无处不在,不仅仅是在驱动器上。让我给你举另一个S3中shuffle sharding的例子。让我们稍微谈谈DNS。当你向S3发送请求时,你可能知道你实际上是向这个DNS地址发送请求,你的bucket名称,s3.amazonaws.com或者如果你在其他区域,则为其他地址。
当你解析这些名称中的一个时,你过去会得到一个IP。我们去年推出的一个非常酷的功能是为S3添加了多答复DNS支持。现在当你执行这些查找时,你实际上会得到8个IP。为什么这很重要?为什么我要谈论这么多IP?
这些IP中的每一个都指向我们车队中的一组不同服务器,这就是S3进行负载均衡的方式。当你执行DNS查找时,那就是我们决定应该将你的请求发送到哪里的时候,而且它可能与其他客户不同。
这对你有几个优势。第一个优势是,作为单个客户,这很棒,因为你再次可以将你的请求分散到许多主机上,跨越许多这样的服务器。这最小化了与其他客户共享同一台服务器时出现热点的影响。但这也意味着你可以突发到比你只有一个地方访问数据时可能获得的带宽要大得多。
这里也存在shuffle sharding。如果Seth和我同时为同一个主机执行DNS解析,同一个bucket名称,我们实际上会得到不同的前端主机集合,不同的IP集合。这意味着即使我们中有一个是非常活跃的客户,即使我们向从该DNS解析获得的任何IP发送大量流量,我们也不会相互竞争,因为我们几乎肯定会访问不同的主机集合。
现在,重要的是,我们设计这些前端主机,这些你从DNS获得的IP,是无状态的。无论你向哪个主机发送请求,你都能够访问所有字节,访问我们为你提供的所有随机驱动器。再次体现了去相关性的理念。
DNS shuffle sharding对于容错也是非常重要的。我们有大量这样的服务器,有时服务器会失败 - 这是完全正常的事情。如果你访问数据的服务器存在某种相关性,这对你来说就不太好了,因为如果其中一台服务器发生故障,就好像你的bucket中发生了系统性故障一样。你将无法再访问那些字节。
无状态性让我们能够限制这种故障的影响。重试之所以如此重要,也正是由于这种故障。单个故障几乎总是会在第二次尝试时成功,因为它几乎总是会转到另一台主机。
所有AWSSDK都会自动实现重试机制,这是出于以下原因:如果其中一台主机发生故障,我们希望能够将请求转移到其他主机。事实上,SDK在这方面相当智能。它们不仅仅是在失败时重试,而是根据返回的HTTP错误码来判断是服务器问题还是身份验证问题等,从而智能地将重试请求转移到其他主机。它们能够区分403(身份验证问题)和实际的服务器错误,并在发生服务器错误时,将请求转移到不同的IP地址。这是因为S3的无状态特性,任何请求都不会被映射到特定的服务器。
总的来说,shuffle sharding是一种优秀的去相关性设计模式。我们在S3、客户端SDK、前端服务器和驱动器上都采用了这种模式,通过去相关性来提供弹性和稳定性。这种模式只有在大规模系统中才能实现。
shuffle sharding是一种很棒的技术。我还想讲一个关于shuffle sharding的小细节,当团队告诉我时,我觉得太棒了,忍不住想分享一下。
亚马逊云科技 Common Runtime(CRT)是一个低级软件库,我们用它来处理与S3相关的工作。它实现了S3的所有最佳实践,比如重试、自动并行化请求等,可以从单个主机获得数百Gbps的高吞吐量。
大多数客户不会直接与CRT交互,他们会通过Java SDK、Python SDK或一些开源连接器(如Mount Point for S3)来使用CRT。
CRT有一个非常棒的功能,不仅可以利用shuffle sharding提供与所有SDK一样的容错能力,还可以在与S3通信时提高性能,具体如下:
这是一个显示S3请求延迟的图表,横轴表示延迟,纵轴表示延迟分布。大多数请求都很快,但你可以看到在右边有一些离群值,也就是一些很慢的请求。这在分布式系统中是正常的。
CRT会动态跟踪这个分布,实时估计请求的尾部延迟。假设它正在跟踪95百分位数(P95),也就是从S3返回的延迟的95%分位数。
一旦知道了这个值,CRT就会做一件很有趣的事情。它会主动取消那些超过P95的慢请求,然后重试。
表面上看,这可能有些令人惊讶。为什么要取消慢请求呢?它们可能就快要响应了,这不是在浪费工作吗?但实际上,shuffle sharding使这种做法行之有效。
新的重试请求很可能会被发送到不同的前端主机,也很可能会被分配到不同的驱动器上。所以,重试请求很可能会非常快。
最终,你会得到一个类似这样的延迟分布:左边的部分基本保持不变,但在P95之后的那部分尾部延迟都被拉了回来,因为大多数请求在重试时都变得很快。
需要明确的是,这是一种赌博行为。我们赌重试请求会更快,但实际上并不总是如此。不过在实践中,这确实是一种非常有效的技术,不仅可以从shuffle sharding中获得容错能力,还可以提高性能。
shuffle sharding无处不在,是一种很棒的设计模式。接下来我会讲一下它是如何工作的。
我们喜欢shuffle sharding,喜欢将数据分散到尽可能多的驱动器上。但是我们如何实现这一点呢?当你执行put操作时,我们必须选择一组驱动器来存储你的数据。我们如何做出这个决定呢?
一种方法是查看所有驱动器,找出最优的一组驱动器,可能是选择最空闲的驱动器。我们可以在每次put时,通过查看所有驱动器做出明智的决定。
不幸的是,我们拥有大量的驱动器,数以百万计。我们不可能在每次put时都查看所有这些驱动器,因为S3每秒要处理数百万个请求,所有这些请求都需要做出同样的计算。这里还存在一个协调问题:如果我们同时查看这些数据,如何确保每个人都做出正确的决定?
我们无法做到完全正确。那么完全随机选择驱动器如何?这是一个不错的策略。不要试图做到完美,只需随机选择一个驱动器存储数据。这在shuffle sharding的精神下是一种非常有效的技术。
但是,完全随机选择也存在一个问题。我将通过一个模拟来解释。这是一个显示驱动器容量使用情况的图表,横轴表示容量均衡程度。如果我们完全随机分配,随机选择驱动器进行put操作,这个图显示了容量使用的分布,也就是容量均衡程度的分布。
从图中可以看出,如果我们完全随机分配,一段时间后,一些驱动器的使用率会比其他驱动器低10%,甚至20%。这并不理想,因为我们浪费了大量容量,没有考虑哪个驱动器最满、哪个最空闲。
完全随机分配是可以的,但存在风险。这种不均衡最终会带来成本问题,我们不得不购买额外的容量来弥补这种不均衡。接下来我要讲一个非常酷的小技巧,叫做“两个随机选择的力量”。
如果你想优化某个指标,比如这里我们想优化容量使用,与其完全随机选择,不如这样做:随机选择两个驱动器,而不是全部驱动器,然后比较它们的使用情况,选择使用率较低的那个。你不需要查看所有数百万个驱动器,只需随机选择两个,选择其中较优的那个即可。
这看起来似乎没什么大不了的。在有数百万个驱动器的情况下,随机选择两个有什么帮助呢?但事实证明,这种方法非常有效。这是使用该技术后的容量分布图。与完全随机选择相比,这种方式的分布要好得多。
这种效果非常显著。你可以兼得两全其美:几乎和查看所有驱动器一样优秀,但又具有随机选择的高效性和无需协调的优势。换句话说,只需对系统有一点了解,查看两个驱动器就足以大大改善数据分布。
“两个随机选择的力量”是一种非常棒的模式,在任何系统中都值得关注。正是这样的技术使我们能够大规模有效地实现shuffle sharding。
我们有意识地在整个S3堆栈中创建去相关性,不仅在驱动器和前端,而是整个系统。我们使用shuffle sharding来有意识地将数据分散到不同的流中,以防将来相互干扰,然后使用“两个随机选择的力量”等算法来在不牺牲性能的情况下获得规模和容错能力。
Amazon Builders Library上有一篇关于shuffle sharding的很棒的文章,如果你感兴趣的话可以去看看。我真诚地建议你在自己的系统中寻找实现shuffle sharding的机会。这是一种低成本的方式来提高系统的弹性,而且通常很容易实现。
好的,我们已经讨论了相关性和去相关性工程的概念。我想告诉你的最后一件事是我们如何为速度而设计,而在为S3工作时,我真正令人惊讶的是,我们为实现容错性所做的所有工作实际上非常有助于让我们的团队更快地前进并更快地交付产品。
让我们来谈谈这是什么意思。显然,在大规模环境下,故障是不可避免的。硬盘会随机发生故障。这归根结底是一个分布式系统问题,每个分布式系统都存在这个问题。事物会发生故障。不仅仅是硬盘,而且准确地描述故障也很重要。
当我们谈论故障时,当然,硬盘会单独发生故障。我们会看到硬盘故障,机架故障,但通常也会有硬件问题,比如风扇损坏、电缆损坏等。但我们也为更大的故障域进行了设计,对吗?不仅仅是硬盘,特别是S3存储类使用多个可用区域,可以防止整个数据中心永久性丢失。
这些可用区域中的任何一个都可以永久消失,而您的所有数据仍然是完整的。我们在整个re:Invent会议上都讨论了如何实现这一点。这并不容易,对吗?不仅仅是架构问题,不仅仅是在系统中有冗余。这种规模的容错能力不仅取决于工程文化,也取决于团队如何思考系统,而不仅仅是首先了解可能发生的故障。
关于容错性需要知道的另一个非常重要的事情是,它不仅仅是为了健壮性,不仅仅是“哦,我们会有一些冗余,一切都很好”。它不仅仅与我们系统的形状有关。在处理容错性时,最困难和最微妙的问题之一就是首先知道什么时候发生了故障,这听起来很疯狂,但在我们的规模下,如何知道系统已经发生故障是一个令人惊讶的难题。
举个例子,S3一直在做的事情之一是,我们不断扫描存储在所有数百万硬盘上的每一个字节,以检查其完整性,或确保它仍然存在于我们预期的位置并且是完整的。
如果您列出我们最大的客户,那些每秒向我们发送最多请求的客户,这种后台扫描工作,用于查找哪些数据是完整的或是否有任何数据丢失,将会名列前茅。我们经常这样做。从运营角度来看,我们也非常重视这一点。我们会对p100最长时间设置警报,即一个字节在未被此进程扫描的最长时间。如果该指标过高,我们会在半夜叫醒工程师。我们确实非常重视确保我们的整个系统中的每一个字节都以一定的频率被扫描。
但这一点都不应该让人感到惊讶。容错性是我们在S3中一直在做的事情。让我告诉你一点关于我们如何实现这一点的情况,对吗?在这个世界上,你如何实现容错性?这是一个我们称之为“删除编码”的概念。
这里有一个非常简单的方法。如果你想获得持久性,想要对单个驱动器故障具有弹性,你可以做的一件事就是复制你的对象。我们可以将对象放在多个驱动器上,这里有一个1兆字节的对象,我可以复制它。假设复制两份,将它们存储在不同的驱动器上。现在,如果一个驱动器发生故障,我仍然有两个副本。如果两个驱动器发生故障,我仍然有一个副本,所以我可以在这个世界中存活一些驱动器故障。
我们还可以通过在不同的可用区域放置这些副本来获得对整个可用区域故障模型的弹性。现在,如果一个可用区域发生故障,另外两个仍然有数据的副本,我们仍然可以获取对象。
复制确实有很多优点,特别是它非常简单,而我们喜欢简单。我们在系统中确实使用了复制。它只有一个缺点,那就是为了存储1字节的客户数据,成本非常高昂。在这个模型中,我们现在必须存储3个物理字节。为了获得这种容错性,我们有3倍的字节放大。
实际上,为了达到S3设计的11个9的持久性,你需要超过3个副本,所以比幻灯片上看起来更糟糕。我们还能做什么呢?我很喜欢谈论删除编码。这是我有机会与你分享我们在S3中真正喜欢使用的一些非常酷的数学知识。这是个概念。我要从我的1兆字节对象开始。我将把它分成5个部分。到目前为止,我还没有获得任何好处。我只是把同一个对象分成了5个块,我们称之为分片。诀窍在于,我们进行一些非常复杂的数学运算来计算一些额外的奇偶校验分片。这些不仅仅是任何分片。它们具有一个非常有趣的特性。我们用来计算这些分片的数学方法赋予了它们这样一个特性:任何5个分片就足以恢复原始对象。
显然,如果我只看前5个——我将原始数据分成5个分片,我可以恢复对象,只需将它们重新粘合在一起。这非常简单,但我也可以用不同的方式做到这一点。比如说,我可以取2个奇偶校验分片和3个原始分片,我就能够恢复整个原始对象。我使用哪种组合实际上并不重要。这9个分片中的任何5个组合都足以恢复整个对象,因此,如果我将这些分片分散在多个驱动器上,我就获得了容错能力。一定数量的驱动器仍然可以发生故障,我们将能够恢复原始对象。而且,重要的是,失败的驱动器是哪些并不重要。任何4个驱动器都可以发生故障,我们都没问题。我们仍然能够恢复对象。
这种删除编码再次也可以使我们对可用区域故障具有容错能力。我们必须将分片分散在可用区域中,所以这里的数学计算非常好。对吗?我在幻灯片上有9个分片。在大多数区域,我们有3个可用区域,所以我在每个可用区域放置3个分片,然后即使一个可用区域发生故障,我仍然有6个分片。我仍然有足够的分片来恢复对象。丢失的是哪些分片并不重要。
神奇之处在于,与复制相比,开销要小得多,对吧?在我在幻灯片上绘制的版本中,我们可以容忍丢失任何4个9个驱动器中的一个,对吧?要用复制获得这一点,我必须存储5个副本,4个将发生故障的副本,再加上一个额外的副本,以便仍然拥有对象。
所以我必须为每个逻辑字节存储5个物理字节。使用删除编码,我们创建了4个奇偶校验分片。每个奇偶校验分片的大小与原始对象的5个分片之一相同,因此我们最终只有80%的开销——每个逻辑字节我们想要存储,只需存储1.8个物理字节。
删除编码是非常神奇的数学。它比我在这里解释的要深入得多,但它为我们带来了低开销的容错能力,所以真的很酷。但你可能以前听说过删除编码。这是一种相当常见的存储技术。对于我们的存储系统来说,这是基本要求。
我实际上想告诉你的是一些关于删除编码的想法,它们不仅仅是为了容错性。我特别想讨论两个例子。我想告诉你的第一件事是,删除编码是一种非常强大的机制,可以提高开发速度,因为我一直在告诉你不同类型的故障域。我一直在告诉你,驱动器可能会发生故障,机架可能会发生故障,可用区域可能会发生故障。系统被设计为使用诸如删除编码之类的技术来容忍这些故障,但是一旦你为这样的故障进行了工程设计,故障的原因实际上并不重要。对吗?如果由于驱动器上的电缆断开而导致单个驱动器发生故障,那与由于运行在驱动器上的软件存在错误而导致单个驱动器发生故障,对系统的影响是一样的。
因此,我们可以利用我们的容错能力来更快地部署东西并更快地对变更建立信心。也许我们首先只在一个主机或一个机架上部署新的软件。也许我们获得了一种新的硬盘类型,我们只在一个、10个或100个驱动器上部署。一开始,我们只有一小部分存储在S3中的数据容量会暴露在这个新的故障域中,因此,如果发现该软件无法工作或该硬盘存在问题,也没关系,我们的原始容量只有一小部分受到影响。我们的容错性意味着我们可以容忍那个驱动器消失或那部分软件消失。
换句话说,我们可以在预期新软件和新硬件会发生故障的情况下进行部署。这并不意味着我们可以不考虑持久性,也不意味着系统没有bug。我们的团队在引入新组件时非常谨慎,所以在实践中,这种故障是罕见的,但像这样持悲观态度有助于团队有信心部署新想法。他们不会因为“但如果这个驱动器发生故障怎么办?”而陷入僵局。一个驱动器发生故障是可以的。
这种将系统尽快投入生产的经验对于开发S3来说是非常宝贵的,因为即使我们只允许比如说这些分片中的一个暴露于新硬件或新软件,在我们的规模下,这意味着暴露于艾字节的数据和每秒百万次的请求,而我们可以在不对数据持久性产生任何风险的情况下获得这种经验。因此,容错能力赋予了我们快速部署的能力。
听起来很棒。但有一个问题 - 你可能之前注意到我提到了shuffle sharding,即我们将随机将对象分布在所有驱动器上。虽然在整个系统中只有很少的这些新故障域,可能只有数千万驱动器中的100个驱动器,或者一个运行新软件的服务器机架,但你的对象暴露于该容量的概率非常低。但这里还有另一个你可能听说过的小小数学问题,叫做生日悖论。
生日悖论的概念是,在这个房间里,有人和我同一个生日的可能性很小。我不会试图去找出来,因为我不想告诉你我的生日,但有人和我同一个生日的可能性很小。但如果你看整个房间,在这个房间里存在两个人共享一个生日的可能性就非常非常高。事实上,对于这个房间的规模来说,这是100%的鸽巢原理。
所以生日悖论告诉我们,即使一个对象暴露于故障域的可能性很小,在这种情况下,不太可能被选中存储在这些驱动器上,但在大规模情况下,至少会有一个对象过度暴露。大多数对象可能只有一个分片在新驱动器或新软件上,但在我们系统中肯定会有一个对象非常不幸,恰好被分配到了比如说5个新驱动器上,只是一个数学问题。
因此,我们现在必须更加小心。我们不能再完全随机地在整个系统中分散分片了。我们的shuffle sharding系统有意识地纳入了这些新故障域的知识。它知道新的软件和硬件,并在进行shuffle sharding时有意确保只将一个分片放在新硬件上。
这种带有防护的shuffle sharding思想的一个例子就是Seth告诉你的shard store系统。这是我们最新的存储节点软件,运行在我们所有驱动器上。我们在过去几年中一直在S3上推广这个软件,并利用纠删码使其在每一步都变得安全,因为我们从shard store开始,只允许一个对象的一个分片存储在这些shard store(新软件)上,并保持一段时间。团队从这种经验中学到了很多。他们调整了shard store,改进了shard store,然后我们将其提高到2个分片,再提高到3个分片,然后提高到一个可用区的数据量,最终我们可以移除防护,shard store无处不在。我们能够在你甚至没有注意到的情况下改变S3的引擎。我们重建了这个最基本的系统部分,并利用纠删码实现了这一点。
关于纠删码还有一点,它不仅用于容错。纠删码也是提高性能的一个很好的手段,其思路与我之前展示的关于CRT(公共运行时)取消请求和重试的做法非常相似。记住,如果我有一个这样的纠删码对象,我需要5个分片来恢复对象,但这5个分片无关紧要,对吗?所以我可以选择这5个分片中的任意5个,如果发现其中一个分片由于某种原因运行缓慢,可能是因为与其他客户竞争导致驱动器过载,没关系,我们可以选择另一个,对吗?我们可以取消那个运行太慢的请求,尝试一个不同的分片,对吗?使用shuffle sharding带来额外的分片可以帮助我们规避来自最慢分片的尾部延迟。
所以,容错不仅仅是容错。它也是为了快速部署,也是为了性能。这很酷。底线是S3团队花费了大量时间来规划容错。这不仅仅是在系统中拥有冗余,我们非常努力地确保能够检测到故障,快速响应故障,并设计像纠删码这样的冗余方案,以实现这种检测故障和从故障中恢复的能力。
作为工程师,有时候容错感觉有点像负担。构建不会失败的系统会容易得多,但在大规模下它们确实会失败。容错是一个巨大的助力。我们的团队可以快速行动,可以充满信心地行动,原因正是我们的系统具有容错能力。
这就是我今天想告诉你的全部内容。我希望这能让你略窥我们如何思考构建一个持久弹性存储系统,以及S3的规模如何为我们和你带来优势。一如既往,我们存储团队所遵循的原则是,如果你从未想过我们,我们的存储系统就应该运行良好,你不应该考虑S3或任何类似的事情。希望我已经让你了解了我们的团队是如何将这一理念付诸实践的。
也许我给了你一些可以带回家的想法。当你开始寻找时,你会惊讶于shuffle sharding或两个随机选择的力量在系统中出现的频率有多高。或者即使你没有获得任何东西,我也希望你真的享受了与我们一起探讨S3中一些很酷的东西的乐趣。
总之,这次深入探讨了亚马逊S3如何利用其巨大规模为客户提供高度持久、高性能的对象存储服务。通过统计多路复用独立的客户工作负载,S3可以将每个客户的数据分散在超过100万个驱动器上,使他们能够远远超出其存储占用空间。智能技术如shuffle sharding和纠删码可实现高利用率的同时保持容错能力。S3拥抱“拥抱失败”的理念,利用容错能力实现敏捷开发和快速恢复。总的来说,S3的架构通过负载均衡、统计多路复用和容错来利用规模优势,超越任何单一部署所能实现的水平。它的团队不断创新,以无缝方式将这些规模优势传递给客户。
下面是一些演讲现场的精彩瞬间:
Seth Markle是亚马逊S3的高级主管工程师,他分享了自己在过去15年中参与S3技术栈各个组件的背景和经验。
亚马逊云科技的大规模优势让客户能够利用数百万个硬盘驱动器的总体吞吐量,即使他们的存储需求远低于此。
他解释了纠删码如何通过允许从任何组合的分片中恢复数据来提供容错能力,即使某些驱动器发生故障。
纠删码是一种强大的机制,可以提高开发速度,使系统能够容忍硬件或软件问题等任何原因导致的故障。
亚马逊使用纠删码和分片存储重建了S3的基本存储引擎,实现了无缝升级,而不会中断服务。
纠删码不仅提供了容错能力,而且通过允许选择更快的分片来恢复对象,从而提高了性能,缓解了慢速或过载驱动器的影响。
演讲者强调亚马逊云科技旨在提供无缝可靠的存储解决方案,让客户专注于自己的核心业务,而不必担心底层基础设施。
总结
在这个富有洞见的演讲中,Seth Markle和James Thornholt深入探讨了Amazon S3设计的复杂性,揭示了其庞大规模如何为客户带来卓越的性能和弹性。他们强调了诸如shuffle sharding这样的关键原则,有意将数据分布在整个系统中,从而实现去相关性,缓解热点问题并确保容错能力。两个随机选择的力量,这种简单而有效的技术,优化了资源分配,同时保持了效率。
纠删码是一种数学奇迹,以低开销提供容错能力,使S3能够容忍跨驱动器、机架,甚至整个可用区域的故障。值得注意的是,这种容错能力还加速了开发速度,使新硬件和软件的快速部署风险降到最低。团队利用S3的规模,将数以艾字节的数据和数百万的请求暴露给新组件,从而获得宝贵的生产经验。
最终,S3的规模是一个强大的优势,将挑战转化为机遇。通过拥抱去相关性、容错性和智能资源分配,S3为客户提供了无与伦比的持久性、弹性和性能,无缝处理他们不同的工作负载和突发流量。演讲者的见解为我们提供了一个迷人的窗口,窥视支撑S3卓越能力的创新工程原则。
亚马逊云科技(Amazon Web Services)是全球云计算的开创者和引领者。提供200多类广泛而深入的云服务,服务全球245个国家和地区的数百万客户。做为全球生成式AI前行者,亚马逊云科技正在携手广泛的客户和合作伙伴,缔造可见的商业价值 – 汇集全球40余款大模型,亚马逊云科技为10万家全球企业提供AI及机器学习服务,守护3/4中国企业出海。