通过一个大型项目来学习分布式算法(5)

时间:2021-07-24 06:16:10

这也引入了一个耐用性漏洞(vulnerability)窗口:即使它只是在少数几个节点上持久化了但写入请求成功返回到客户端。

传统的观点认为,耐用性和可用性关系总是非常紧密(hand-in-hand手牵手^-^)。但是,这并不一定总是真的。例如,耐用性漏洞窗口可以通过增加W来减少,但这将增加请求被拒绝的机率(从而减少可用性),因为为处理一个写请求需要更多的存储主机需要活着。

被好几个Dynamo实例采用的(n,R,W)配置通常为(3,2,2)。选择这些值是为满足性能,耐用性,一致性和可用性SLAs的需求。

所有在本节中测量的是一个在线系统,其工作在(3,2,2)配置并运行在几百个同质硬件配置上。如前所述,每一个实例包含位于多个数据中心的Dynamo节点。这些数据中心通常是通过高速网络连接。回想一下,产生一个成功的get(或put)响应,R(或W)个节点需要响应协调员。显然,数据中心之间的网络延时会影响响应时间,因此节点(及其数据中心位置)的选择要使得应用的目标SLAs得到满足。

6.1平衡性能和耐久性 

虽然Dynamo的主要的设计目标是建立一个高度可用的数据存储,性能是在Amazon平台中是一个同样重要的衡量标准。如前所述,为客户提供一致的客户体验,Amazon的服务定在较高的百分位(如99.9或99.99),一个典型的使用Dynamo的服务的SLA要求99.9%的读取和写入请求在300毫秒内完成。

由于Dynamo是运行在标准的日用级硬件组件上,这些组件的I/O吞吐量远比不上高端企业级服务器,因此提供一致性的高性能的读取和写入操作并不是一个简单的任务。再加上涉及到多个存​​储节点的读取和写入操作,让我们更加具有挑战性,因为这些操作的性能是由最慢的R或W副本限制的。图4显示了Dynamo为期30天的读/写的平均和99.9百分位的延时。正如图中可以看出,延时表现出明显的昼夜模式这是因为进来的请求速率存在昼夜模式的结果造成的(即请求速率在白天和黑夜有着显着差异)。此外,写延时明显高于读取延时,因为写操作总是导致磁盘访问。此外,99.9百分位的延时大约是200毫秒,比平均水平高出一个数量级。这是因为99.9百分位的延时受几个因素,如请求负载,对象大小和位置格局的变化影响。

 
图4:读,写操作的平均和99.9百分点延时,2006年12月高峰时的请求。

在X轴的刻度之间的间隔相当于连续12小时。延时遵循昼夜模式类似请求速率99.9百分点比平均水平高出一个数量级。

虽然这种性能水平是可以被大多数服务所接受,一些面向客户的服务需要更高的性能。针对这些服务,Dynamo能够牺牲持久性来保证性能。在这个优化中,每个存储节点维护一个内存中的对象缓冲区(BigTable 中的memtable)。每次写操作都存储在缓冲区,“写”线程定期将缓冲写到存储中。在这个方案中,读操作首先检查请求的 key 是否存在于缓冲区。如果是这样,对象是从缓冲区读取,而不是存储引擎。

这种优化的结果是99.9百位在流量高峰期间的延时降低达5倍之多,即使是一千个对象(参见图5)的非常小的缓冲区。此外,如图中所示,写缓冲在较高百分位具有平滑延时。显然,这个方案是平衡耐久性来提高性能的。在这个方案中,服务器崩溃可能会导致写操作丢失,即那些在缓冲区队列中的写(还未持久化到存储中的写)。为了减少耐用性风险,更细化的写操作要求协调员选择N副本中的一个执行“持久写”。由于协调员只需等待W个响应(译,这里讨论的这种情况包含W-1个缓冲区写,1个持久化写),写操作的性能不会因为单一一个副本的持久化写而受到影响。

 
图5:24小时内的99.9百分位延时缓冲和非缓冲写的性能比较。在x轴的刻度之间的间隔连续为一小时。

6.2确保均匀的负载分布 

Dynamo采用一致性的散列将key space(键空间)分布在其所有的副本上,并确保负载均匀分布。假设对key的访问分布不会高度偏移,一个统一的key分配可以帮助我们达到均匀的负载分布。特别地, Dynamo设计假定,即使访问的分布存在显着偏移,只要在流行的那端(popular end)有足够多的keys,那么对那些流行的key的处理的负载就可以通过partitioning均匀地分散到各个节点。本节讨论Dynamo中所出现负载不均衡和不同的划分策略对负载分布的影响。

为了研究负载不平衡与请求负载的相关性,通过测量各个节点在24小时内收到的请求总数-细分为30分钟一段。在一个给定的时间窗口,如果该节点的请求负载偏离平均负载没有超过某个阈值(这里15%),认为一个节点被认为是“平衡的”。否则,节点被认为是“失去平衡”。图6给出了一部分在这段时间内“失去平衡”的节点(以下简称“失衡比例”)。作为参考,整个系统在这段时间内收到的相应的请求负载也被绘制。正如图所示,不平衡率随着负载的增加而下降。例如,在低负荷时,不平衡率高达20%,在高负荷高接近10%。直观地说,这可以解释为,在高负荷时大量流行键(popular key)访问且由于key的均匀分布,负载最终均匀分布。然而,在(其中负载为高峰负载的八分之一)低负载下,当更少的流行键被访问,将导致一个比较高的负载不平衡。

 

图6:部分失去平衡的节点(即节点的请求负载高于系统平均负载的某一阈值)和其相应的请求负载。

X轴刻度间隔相当于一个30分钟的时间。

本节讨论Dynamo的划分方案(partitioning scheme)是如何随着时间和负载分布的影响进行演化的。

策略1:每个节点T个随机Token和基于Token值进行分割:这是最早部署在生产环境的策略(在4.2节中描述)。在这个方案中,每个节点被分配T 个Tokens(从哈希空间随机均匀地选择)。所有节点的token,是按照其在哈希空间中的值进行排序的。每两个连续的Token定义一个范围。最后的Token与最开始的Token构成一区域(range):从哈希空间中最大值绕(wrap)到最低值。由于Token是随机选择,范围大小是可变的。节点加入和离开系统导致Token集的改变,最终导致ranges的变化,请注意,每个节点所需的用来维护系统的成员的空间与系统中节点的数目成线性关系。

在使用这一策略时,遇到了以下问题。首先,当一个新的节点加入系统时,它需要“窃取”(steal)其他节点的键范围。然而,这些需要移交key ranges给新节点的节点必须扫描他们的本地持久化存储来得到适当的数据项。请注意,在生产节点上执行这样的扫描操作是非常复杂,因为扫描是资源高度密集的操作,他们需要在后台执行,而不至于影响客户的性能。这就要求我们必须将引导工作设置为最低的优先级。然而,这将大大减缓了引导过程,在繁忙的购物季节,当节点每天处理数百万的请求时,引导过程可能需要几乎一天才能完成。第二,当一个节点加入/离开系统,由许多节点处理的key range的变化以及新的范围的MertkleTree需要重新计算,在生产系统上,这不是一个简单的操作。最后,由于key range的随机性,没有一个简单的办法为整个key space做一个快照,这使得归档过程复杂化。在这个方案中,归档整个key space 需要分别检索每个节点的key,这是非常低效的。

这个策略的根本问题是,数据划分和数据安置的计划交织在一起。例如,在某些情况下,最好是添加更多的节点到系统,以应对处理请求负载的增加。但是,在这种情况下,添加节点(导致数据安置)不可能不影响数据划分。理想的情况下,最好使用独立划分和安置计划。为此,对以下策略进行了评估:

策略2:每个节点T个随机token和同等大小的分区:在此策略中,节点的哈希空间分为Q个同样大小的分区/范围,每个节点被分配T个随机Token。Q是通常设置使得Q>>N和Q>>S*T,其中S为系统的节点个数。在这一策略中,Token只是用来构造一个映射函数该函数将哈希空间的值映射到一个有序列的节点列表,而不决定分区。分区是放置在从分区的末尾开始沿着一致性hash环顺时针移动遇到的前N个独立的节点上。图7说明了这一策略当N=3时的情况。在这个例子中,节点A,B,C是从分区的末尾开始沿着一致性hash环顺时针移动遇到的包含key K1的节点。这一策略的主要优点是:(i)划分和分区布局脱耦 (ii)使得在运行时改变安置方案成为可能。