Dynamo: Amazon’s Highly Available Key-value Store 读书笔记

第一节 介绍
   介绍Amazon的面临的情况:在高峰期面临百万级客户的并发访问,所以可靠性尤为重要,任何的小的"断档期",都会带来财务上的损失和影响客户的信任。随着业务量的增长,平台也要有更高的可伸缩性(scalable)。

第二节 背景
   Amazon有数以百计的服务需要暴露,后面支持的服务器数以万计 部署在世界各地的数据中心。这些服务分有状态的(依赖其他服务)和有状态的(状态要持久化)。传统的系统设计把状态(state)保存到关系数据库。但关系数据库的复制需要昂贵的硬件费用和高技能的人操作,效率不高。另外可靠的复制技术的限制、重视一致性超过可靠性。
   Dynamo:高可靠的数据存储技术。标记服务分类、简单的key-value接口、清晰的一致性窗口定义、用简单的方案记录数据增长量和服务请求率。每个服务有自己的Dynamo实例。
Dynamo: Amazon’s Highly Available Key-value Store 读书笔记_第1张图片

  系统预期和需求
    • QueryModel:简单。Amazon的服务查询使用key,不需要用关联Schema来完成。存储的对象较小<1M.
    • ACID:ACID (Atomicity, Consistency,Isolation,Durability),包保证ACID,势必降低可靠性。Dynamo的目标是轻一致性而重可靠性。不提供任何Isolation保证而允许简单的根据关键字key更新操作。
    • Efficiency:性能、效率成本、可靠性、持久性的折中。
    • Other Assumptions:Dynamo是内部系统,没有安全的要求,例如认证和授权。

  服务协议SLA(Service Level Agreements)
  • 参照取平均值,但不局限于此,致力于最高的可用率。
  设计的考虑点
  • 什么时候执行解决更新冲突的过程?这个需求要求将复杂的冲突在read解决,保证write不会被拒绝(即使出现一些网络异常等),“总是可写”。
  • 谁来执行冲突解决方案?一种是在数据存储层,只能用简单的策略,如:采用“以最后更新为准”;一种是在应用层来维护冲突解决方案。
  • 其他一些关键的设计原则:
    • Incremental scalability:增加一个新的node,应该对已有的系统和操作影响最小。
    • Symmetry:每个节点都是同等的。
    • Decentralization:分散化,简单而可伸缩。
    • Heterogeneity:支持异构化,不因增加一个node而需要一次性更新所有已有的节点。

第三节 相关工作
   点对点系统
      介绍了一些知名的系统。

   分布式的文件系统和数据库
  • Dynamo的Distributed blockstorage系统采用将大对象分割成小block来存储,原因(a)为了存储小对象< 1M);(b)key-value 存储方式易于配置。
  • Dynamo目标:使仅要求key/value访问的应用主要关注可用性:不拒绝写,即使在网络分区出现问题或系统故障时。
      
   讨论
  • Dynamo 目标在于系统“总是可写”
  • Dynamo 是在一个简单的可管理的domain里,所有的节点都是可信任的。
  • 使用Dynamo的应用不要求分等级的命名空间。
  • Dynamo 是为latency敏感的应用而建立的,它要求至少99.9%的几百毫秒的read和write操作响应。
  • 所以避免路由请求要通过多个节点。因为多个节点multihop路由给响应时间增加可变性,而且增加很高百分比的latency。
  •  Dynamo 形容为一个zero-hop DHT,每个节点本地维护足够的路由信息,直接路由请求到精确的节点。

第四节 系统架构
     Dynamo使用的技术和优势
Dynamo: Amazon’s Highly Available Key-value Store 读书笔记_第2张图片

        系统接口
              get(),put()简单接口。

        分区算法
Dynamo: Amazon’s Highly Available Key-value Store 读书笔记_第3张图片


      Dynamo 采用一段连续变化的hash值(简单的使用[10, 20]的一个),而不是采用一个节点映射到环里的一个点,任何一个节点都会复制给其他多个节点。Dynamo采用“虚拟节点”的概念。一个虚拟节点像系统的一个节点,但是每个节点都可以由一个以上的虚拟节点的负责(获得响应)。当一个新的节点被加入系统,它就被赋予了环中的多个位置(“token”)。使用虚拟节点的优点:
  • 如果一个节点不可用(由于故障或例行维护),原来由这个节点完成的负载就分发到剩余可用的节点上。
  • 当一个节点又恢复可用,或者一个新的节点加入系统,新可用节点可以从其他节点接收相当数量的负载。
  • 虚拟节点的数量是由它自己的容量决定的,根据物理基础架构的不同而不同。

       复制
      为了达到高可用和高持久性的目的,Dynamo复制它的数据到多个hosts,每个数据项被复制到N个hosts,N是一个“每个实例”的参数配置。每个key k被赋予一个协调者节点,协调者掌管数据项的复制,保证属于它(NODE)的范围。此外,本地保存每个key在它(协调者NODE)的范围内,协调者还复制这些key到N-1个环中顺时针方向的节点。具体见上图,节点B复制key k到节点C和D,并保存在本地。节点D将保存key将落在(A, B], (B, C], and (C,D]。
      优先列表:负责保存特殊key的节点列表,至少包括N个节点,小于等于N个物理节点,所以优先列表忽略环中的一些位置来保证列表中包含不同的物理节点。

       数据版本
      为了保证写的可用性,Dynamo 把每次修改看成一个新的不便的数据版本(dataversion),它允许对象的多个数据版本同时出现在系统内。大多数情况下,新版本包含之前的版本,系统自己可以决定有效的版本(根据语意协调syntacticreconciliation)。但是可能出现版本分支,系统无法协对象调多版本冲突,那客户必须执行调解保证多分枝数据版本合并成一个。
       Dynamo采用向量时钟vector clocks 来捕同一个对象不多版本的因果关系。一个 vectorclock是有效的(节点,计数器)的列表. 一个向量时钟vectorclock关联了所有对象的所有版本。通过检查向量时钟可以决定哪两个版本是并行分支或有一个因果顺序。 One candetermine
whether two versions of an object are on parallel branches orhave
a causal ordering, by examine their vector clocks.如果计数器在第一个对象的时钟小于等于第二个时钟所有节点的计数器,它就是第二个时钟的父亲,可以被忘记,否则,这2个变化被认为是冲突需要协调。
Dynamo: Amazon’s Highly Available Key-value Store 读书笔记_第4张图片

       一个可能的问题:向量时钟长度由于多server同等的对同一个对象写而不断增加?在实践中,这个不可能,因为写通常被优先列表中的TOPN个节点处理,只有在网络分区或多服务器不可用的情况下,才会有优先列表中非TOPN的节点处理,这样才会使向量时钟增长。Dynamo最后采用下面截断方式处理clock truncationscheme:在每个(节点,计数器) 中, Dynamo保存一个时间戳表示更新数据项的最后时间。当(节点,计数器)的对的数量超过一定的阀值例如10,最老的对就会被从时钟移除。显而易见,这种截断的方式可能会导致在协调冲突是变得无效,例如后代关系不够精确。但是这个问题没有在生产环境出现,而且这个问题还没有调查研究。
      
      执行get()和put()操作
     这节为了简化,仅描述读写操作在无异常的环境的情况。根据Amazon的基础架构规范,通过http访问get和put操作。客户端选择节点有2种策略:(1)路由客户端请求通过总的负载均衡器基于负载信息来选择一个节点。好处:客户端不用将Dynamo硬编码在应用里。(2)使用分区敏感的客户端库/包请求直接到达适当的协调者节点。好处:能达到较低的延迟(latency),因为它跳过潜在的forward步骤。
为了维护复制的一致性,Dynamo采用一个一致性协议类似配额制quorum systems:这个协议有2个关键的配置项: R 和 W。R是必须参与成功读操作的最少节点数;W是必须参与成功写操作的最少节点数。根据配额制quorum-like system设置R和W满足R+ W >N(N为优先列表中的前N个节点)。在这个模型中,读写操作最差的延迟由R或W中最慢的复制决定。由于这个原因,所以读和写通常配置小于N来达到较好的延迟betterlatency。
     一旦协调者收到put()请求,它将产生一个新版本的向量时钟,在本地写入新的版本,并将新版本(连同新向量时钟)发送给N个最好级别的可达节点。如果有至少W-1个节点响应写操作就认为是成功了。
     类似,对应get()请求,协调者请求所有已存在的数据版本,针对key从优先列表中的N个最高级别的可达节点获取,在返回给客户端之前,要等待R个响应。如果协调者整理收集多个数据版本,它返回所有它认为这些没有关联的版本。有分歧的版本被协调,协调后的版本(取代当前版本的)被回写。
       
        失败处理:提示移交HintedHandoff
       如果Dynamo采用传统的配额制,在服务器宕机或网络分区异常,它将降低持久性(一个已提交事务的任何结果都必须是永久性的,即“在任何崩溃的情况的能保存下来”。),即使是最简单失败情况也是如此。
为了完善这个问题,采用“宽松的配额制”,所有的读写在优先列表不一定总是前N个健康的节点。
      参考分区算法的图,N=3,这个例子,如果在写操作的过程中节点A临时宕机或不可达,复制将发送到节点D,这个是为了保证期望的可用性和持久性。发送达节点D的复制包括一个提示在它的元数据中:建议哪个节点是期望的接收节点(这个例子是A)。接收到暗示的节点将保存写的数据在独立的本地数据库,并定期扫描。
一旦侦测到节点A恢复,节点D将尝试发送的复制信息到节点A,一旦传输成功,节点D将删除本地的这些临时对象而不损失复制信息。
      为了提升应用的高可用,可以将W设置为1,它表示写操作只要一个节点写到本地存储就被认为成功,这样只有所有的节点在不可用是写操作才被拒绝。但是,实际上,大多数Amazon应用在生产环境设置W为一个比较大的值,主要是为了满足期望的持久性程度。

        处理永久性失败情况:同步复制
       如果系统成员变化较低,提示移交方案较好。有一些场景暗示:在这些场景下,在返回源复制节点前提示移交方案不可用。Dynamo实现反熵(同步复制)协议保持复制同步。
     为了侦测快速复制和最小化传输数据之间的矛盾,Dynamo采用Merkle trees方案。Merkletree是一个Hash树,叶节点是每个key的hash值,父节点的值是它子节点的值的hash(key的hash的hash)。好处:Merkletree的每个分支可以独立检查,而不需要整棵树所有数据。而且在复制检查不一致性时,Merkletree可以帮组减少所需传递的数据。例如两棵树的root节点的hash值相同,表示树的叶节点都相同,所以不需要同步。如果不同,两个node之间要叫号树上子节点的hash只,直到树的叶节点。主机host可以识别出不需要同步的keys。Merkletree最小化同步复制传输的数据,减少磁盘读操作的执行。
       每个节点维护各自独立的Merkletree,在一定的key范围内。这使得节点可比较key是否在范围内。这个方案中,2个节点交换对应的Merkletree,那么它们对应的key范围是共有的。随后,采用树反转方案(treetraversal)描述上述节点,决定它们是否有一些不同而需要执行同步动作。这个方案缺点:但一个节点加入或退出系统,大量key范围变化要求tree重新计算。
     
        成员资格和失败侦测
       .环成员资格
      由于Amazon环境内的节点的不可用通常是透明的,但可能持续较长时间。一个节点很少明确表示永久的离开,而且不应该导致重新均衡分区的分配,或重新修复不可达的复制。所以采用显式的机制初始化加入或去除环中的节点。自适应成员关系管理协议(gossip-basedprotocol)传递成员资格变化和维护最终的成员的一致性视图。每个节点每秒随机选择一个对等节点,这两个节点有效地协调它们之间持久的成员资格变更历史信息。
       当一个节点首次开始,它选择自己token集合(在一段连续hash空间的多个虚拟节点),映射节点和token值。这些映射信息将持久化到本地磁盘,最初只包含本地节点和token集合信息。保存在Dynamo不同节点的映射信息在同一个通讯交换时候协调成员资格变更历史信息。而且分区信息和部署信息经一起gossip-basedprotocol传递,每个存储节点都知道它的对等节点能处理的token范围。这使得每个节点直接forward一个key的读/写操作到正确的节点集合。

        .外部发现
       为了避免逻辑隔离,一些Dynamo节点要作为种子节点的角色。种子节点经由外部机制发现,所有节点都知道。因为所有几点最后都和一个种子节点协调成员资格,逻辑隔离不太可能做到。种子节点可用从配置文件或配置服务中获取。

       . 失败侦测
      失败侦测为了避免在get和put操作,或传输分区信息和提示复制时,尝试与不可达的节点通讯。节点A人为节点B失败,如果节点B不能响应A的消息(即使节点B响应节点C的消息)。如果客户端请求在一定频率,产生Dynamo环中的内部节点通讯,节点A很快发现节点B没有响应。节点A使用备选的节点服务请求,即与B分区映射的节点。节点A定期尝试检查节点B是否恢复。如果没有客户请求的驱动两个节点之间的通讯,没有任何一个节点知道对方是否可达或有响应。
       去中心化失败侦测协议使用简单的gossip-style协议,使得系统中的每个节点了解其他节点加入或推出。前期的设计Dynamo使用了一个去中心化的失败侦测机制维护一个全局一致的失败状态视图。后期决定采用
节点明确的加入或退出方法,避免采用全局失败状态视图。因为所有节点可以通过显式地调用加入和推出方法来得到通知持久节点的加入和退出通知;而临时节点的失败,通过各自节点与其他节点的通讯异常来侦测。

       . 增加和删除存储节点
      当一个新的节点X加入到系统,它将获得多个Token并随机散布到环里。对于每个赋予节点X的key范围,
有一些节点(<=N)目前是这些节点负责的,节点X的key范围中的key原则是落在这些节点的key范围内的。
为了分配key范围给节点X,一些已经存在的节点不再拥有一些key,而是交给节点X。
       这种方案可以平均的分配存储节点的负载,对应满足延迟需求和保证快速加载比较重要。最后,需要在源和目的节点之间增加一个确认的回合,确认目的节点没有收到2次重复的key范围。

       
第五节 实现细节

  
在Dynamo,每个存储节点有3个软件组件:请求协调组件,成员资格和失败侦测组件,本地持久化引擎组件。所有组件均采用Java。
   Dynamo的本地持久化组件支持不同存储引擎插件。引擎使用Berkely DB传统数据存储,BDBjava版本,MySQL和拥有持久化支持的内存缓冲。
可以插入式的持久化组件设计,主要原因是为了针对应用访问模式采用适当的存储引擎。例如,BDB能处理的对象主要是kb级别的,MySQL能处理更大的对象。应用选择Dynamo的本地持久化引擎取决于它们对象大小的分布。绝大多数Dynamo的生产实例主要使用BDB传统数据存储。
   请求协调组件建立在事件驱动消息方式,底层消息处理管道被划分成多个步骤,类似SEDA架构。所有通讯均采用JavaNIO通道。每个客户端请求导致节点上创建一个状态机服务请求。状态机包含所有业务逻辑:识别可以响应某个key的节点,发送请求,等待响应,尝试重发,处理恢复和打包响应给客户端。每个状态机实例处理对应的一个客户端请求。
   在读操作的响应返回到调用者后,状态机等待一个短暂的时间去接收任何重要的响应。如果在一些返回的响应中有旧的版本信息,协调这会用最新版本去更新这些节点。这个过程称为“读修复”,因为它修复这些节点的复制信息,这些节点在某个时间刚好缺失最近的一个更新,也能减轻对反熵协议(同步)的依赖。
   在先前的章节,写请求被优先列表中的前N个节点之一协调处理。尽管它总是期望TopN的第一个节点协调写操作并在一个地点序列化所有的操作,但是这种方案会导致不均衡的分发负载,从而破坏SLA协议。这是因为请求没有根据对象做负载均衡。为了解决这个问题,优先列表中的任意一个节点都可以协调写操作。特别地,既然每个写操作通常紧跟在读操作之后,协调者会选择先前响应读操作最快的节点来处理写操作,这个信息是保存在请求的上下文信息中的。这个优化可以选择拥有这个数据的节点,而且可以提升获得"readyourwrites"的一致性。另外它也可以减少请求处理性能的变化,99.9%可以提升性能。
  
 
第六节经验分享
    .业务逻辑详细描述协调逻辑:这是Dynamo的典型场景。每个数据对象被复制到多个节点。在出现歧义版本的情况下,客户应用执行它自己的协调逻辑。
    .基于时间戳的协调逻辑:和上面的模式不同在于协调机制。Dynamo执行简单的基于时间戳的协调逻辑-“最近修改为准”。
    .高性能的读引擎:当Dynamo要建成一个“总是可写”的数据存储,一些服务优化自己的配额特征(quorum),使用它作为一个高性能的读引擎。典型地,这些服务器拥有一个较高的读请求率和仅仅一些少量的更新。在这种配置下,通常R被设置为1,W为N。对于这些服务,Dynamo提供分区和复制数据到多个节点,而且提供可增加的伸缩性(Incrementalscalability)。这些实例功能中一些针对数据的权威持久化缓存,将数据存储在一些重量级的存储上。维护产品分类和推销产品的服务适合这种方案。

   
Dynamo的主要优点是客户端应用可以优化参数N,R,W来获取它们期望的性能,可用性和持久性的级别。
例如N的值决定每个对象的持久性。Dynamo的用户通常使用N=3.
   W和R的值影响对象的可用性,持久性和一致性。例如,如果W设为1,那么只要还有至少一个节点在系统中,
系统将不会拒绝写请求。但是,W和R的值过低会增加不一致性的风险,由于写请求被人为成功而返回给客户端,
甚至没有处理大多数的复制。这也表明有一个易被攻击的窗口:当写请求成功地返回给客户端时,正是它只在一
小部分节点做了持久化。通过增加W的值可以增加降低被攻击的风险,但是可能增加拒绝请求的可能性(而且降低可用性),因为更多的存储节点需要处理一个写请求。
   通常在Dynamo的几个实例配置(N,R,W)为(3,2,2)。这些值满足性能,持久性和可用性的SLA协议。

    平衡性能和持久性
    一个典型的DynamoSLA需要要求:99.9%的读/写请求在300ms内执行完。
  
    图4
   写操作的延迟总是高于读操作,显然是因为写操作总是导致磁盘访问。99.9%延迟在200ms附近,密度要高于平均值。这是因为99.9%延迟受以下几个因素影响:请求负载,对象大小,地点位置策略。
   当一些面向客户的服务要求更高的可用级别,Dynamo提供用持久性换取性能保证的方案。优化每个存储节点:在主内存中维护一个对象缓冲(objectbuffer)。每个写操作保存在缓冲中,使用一个写线程定期获取写操作,将对象保存到存储中。在这种方案中,读操作首先检查请求的key是否在缓冲中,如果有,存储引擎直接从缓冲读取对象返回。
  
    图5
   这种优化后延迟是峰值的1/5,甚至对应一个保存1000个对象的小缓冲也是这样。显然这个方案是用持久性换取性能,在这种情况下,一台Server宕机导致缓冲队列中的写操作丢失。为了减少持久性风险,写操作改进:让协调者选择N个节点中的一个进行“持久化写操作”。既然协调者只等待W个响应,所以在一个简单的复制中,写操作的性能不受持久化写操作的性能影响。
  
    保证负责分布的均衡
   
Dynamo使用一致性hash算法区分key空间,覆盖对应对象的复制,保证负载分布均衡。一个均衡的key分布有助于获取均衡的复制分布。实际上,Dynamo的设计预测即使在有严重倾斜的访问部分,也是有大量的热门的key分布于此,以至于能通过分区均衡地将热门key负载传播到其他节点。
   一个节点人为均衡,它的负载偏移在15%内,否则人为不均衡。不均衡节点的百分比称为不均衡率。
   
    图6
   针对以上实例,低负载的不均衡率高达20%,高负载接近10%。直观感觉,在高负载下,大量热门key被访问,由于key的均衡分布,所以负载也均衡。但是在低负载下(1/8峰值),很少热门key被访问,导致很高的不均衡率。
   
   Dynamo的分区方案:
   策略1:每个节点T个随机Token和基于Token值进行分区:这是最早部署在生产环境的策略(在4.2节中描述)。在这个方案中,每个节点被分配T个Tokens(从哈希空间随机均匀地选择)。所有节点的token,是按照其在哈希空间中的值进行排序的。每两个连续的Token定义一个范围。最后的Token与最开始的Token构成一区域(range):从哈希空间中最大值绕(wrap)到最低值。由于Token是随机选择,范围大小是可变的。节点加入和离开系统导致Token集合的改变,最终导致key范围的变化,请注意,每个节点所需的用来维护系统的成员的空间与系统中节点的数目成线性关系。
   在使用这一策略时,遇到了以下问题。首先,当一个新的节点加入系统时,它需要“窃取”(steal)其他节点的键范围。然而,这些需要移交keyranges给新节点的节点必须扫描他们的本地持久化存储来得到适当的数据项。请注意,在生产节点上执行这样的扫描操作是非常复杂,因为扫描是资源高度密集的操作,他们需要在后台执行,而不至于影响客户的性能。这就要求我们必须将引导工作设置为最低的优先级。然而,这将大大减缓了引导过程,在繁忙的购物季节,当节点每天处理数百万的请求时,引导过程可能需要几乎一天才能完成。第二,当一个节点加入/离开系统,由许多节点处理的keyrange的变化以及新的范围的MertkleTree需要重新计算,在生产系统上,这不是一个简单的操作。最后,由于keyrange的随机性,没有一个简单的办法为整个key space做一个快照,这使得归档过程复杂化。在这个方案中,归档整个keyspace 需要分别检索每个节点的key,这是非常低效的。
   这个策略的根本问题是,数据划分和数据安置的计划交织在一起。例如,在某些情况下,最好是添加更多的节点到系统,以应对处理请求负载的增加。但是,在这种情况下,添加节点(导致数据安置)不可能不影响数据划分。理想的情况下,最好使用独立划分和安置计划。为此,对以下策略进行了评估:

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

  
   策略3:每个节点Q/S个Token,大小相等的分区:类似策略2,这一策略空间划分成同样大小为Q的散列分区,以及分区布局(placementof partition)与划分方法(partitioningscheme)脱钩。此外,每个节点被分配Q/S个Token其中S是系统的节点数。当一个节点离开系统,为使这些属性被保留,它的Token随机分发到其他节点。同样,当一个节点加入系统,新节点将通过一种可以保留这种属性的方式从系统的其他节点“偷”Token。

   对这三个策略的效率评估使用S=30和N=3配置的系统。然而,以一个比较公平的方式这些不同的策略是很难的,因为不同的策略有不同的配置来调整他们的效率。例如,策略1取决于负荷的适当分配(即T),而策略3信赖于分区的个数(即Q)。一个公平的比较方式是在所有策略中使用相同数量的空间来维持他们的成员信息时,通过评估负荷分布的偏斜.例如,策略1每个节点需要维护所有环内的Token位置,策略3每个节点需要维护分配到每个节点的分区信息。

   在我们的下一个实验,通过改变相关的参数(T 和Q),对这些策略进行了评价。每个策略的负载均衡的效率是根据每个节点需要维持的成员信息的大小的不同来测量,负载平衡效率是指每个节点服务的平均请求数与最忙(hottest)的节点服务的最大请求数之比。
 
   
    图8
   
正如图中看到,策略3达到最佳的负载平衡效率,而策略2最差负载均衡的效率。一个短暂的时期,在将Dynamo实例从策略1到策略3的迁移过程中,策略2曾作为一个临时配置。相对于策略1,策略3达到更好的效率并且在每个节点需要维持的信息的大小规模降低了三个数量级。虽然存储不是一个主要问题,但节点间周期地Gossip成员信息,因此最好是尽可能保持这些信息紧凑。除了这个,策略3有利于且易于部署,理由如下:(i)更快的bootstrapping/恢复:由于分区范围是固定的,它们可以被保存在单独的文件,这意味着一个分区可以通过简单地转移文件并作为一个单位重新安置(避免随机访问需要定位具体项目)。这简化了引导和恢复过程。(ii)易于档案:对数据集定期归档是Amazon存储服务提出的强制性要求。Dynamo在策略3下归档整个数据集很简单,因为分区的文件可以被分别归档。相反,在策略1,Token是随机选取的,归档存储的数据需要分别检索各个节点的key,这通常是低效和缓慢的。策略3的缺点是,为维护分配所需的属性改变节点成员时需要协调。

    有分歧的版本:何时有?有多少?
   如前所述,Dynamo被设计成为用一致性换取可用性。为了解不同的一致性失败导致的确切影响,多方面的详细的数据是必需的:中断时长,失效类型,组件可靠性,负载量等。详细地呈现所有这些数字超出本文的范围。不过,本节讨论了一个很好的简要的度量尺度:在现场生产环境中的应用所出现的不同版本的数量。
   有分歧的版本的数据项出现在两种情况下。首先是当系统正面临着如节点失效故障的情况下,数据中心的故障和网络分裂。二是当系统的并发处理大量写单个数据项,并且最终多个节点同时协调更新操作。无论从易用性和效率的角度来看,都应首先确保在任何特定时间内不同版本的数量尽可能少。如果版本不能单独通过矢量时钟在语法上加以协调,他们必须被传递到业务逻辑层进行语义协调。语义协调给服务应用引入了额外的负担,因此应尽量减少它的需要。
   在我们的下一个实验中,返回到购物车服务的版本数量是基于24小时为周期来剖析的。在此期间,99.94%的请求恰好看到了1个版本。0.00057%的请求看到2个版本,0.00047%的请求看到3个版本和0.00009%的请求看到4个版本。这表明,不同版本创建的很少。
   经验表明,有分歧的版本的数量的增加不是由于失败而是由于并发写操作的数量增加造成的。数量递增的并发写操作通常是由忙碌的机器人(busyrobot-自动化的客户端程序)导致而很少是人为触发。由于敏感性,这个问题还没有详细讨论。
    
   客户端驱动协调 或 服务器端驱动协调
   
Dynamo有一个请求协调组件,它使用一个状态机来处理请求。客户端的请求均匀分配到环上的节点是由负载平衡器完成的。Dynamo的任何节点都可以充当一个读请求协调者。另一方面,写请求将由key的首选列表中的节点来协调。此限制是由于这一事实--这些首选节点具有附加的责任:即创建一个新的版本标识,使之与写请求更新的版本建立因果关系。请注意,如果Dynamo的版本方案是建基于物理时间戳的话,任何节点都可以协调一个写请求。

   另一种请求协调的方法是将状态机移到客户端节点。在这个方案中,客户端应用程序使用一个库在本地执行请求协调。客户端定期随机选取一个节点,并下载其当前的
Dynamo 成员状态视图。利用这些信息 , 客户端可以从首选列表中为给定的 key 选定相应的节点集。读请求可以在客户端节点进行协调,从而避免了额外一跳的网络开销 ,比如,如果请求是由负载平衡器分配到一个随机的 Dynamo 节点,这种情况会招致这样的额外一跳。如果 Dynamo 使用基于时间戳的版本机制,写要么被转发到在 key 的首选列表中的节点,也可以在本地协调。
   一个客户端驱动的协调方式的重要优势是不再需要一个负载平衡器来均匀分布客户的负载。公平的负载分布隐含地由近乎平均的分配
key 到存储节点的方式来保证的。显然,这个方案的有效性是信赖于客户端的成员信息的新鲜度的。目前客户每 10 秒随机地轮循一 Dynamo 节点来更新成员信息。一个基于抽取 (pull) 而不是推送 (push) 的方被采用,因为前一种方法在客户端数量比较大的情况下扩展性好些,并且服务端只需要维护一小部分关于客户端的状态信息。然而,在最坏的情况下,客户端可能持有长达 10 秒的陈旧的成员信息。如果客户端检测其成员列表是陈旧的 ( 例如,当一些成员是无法访问 ) 情况下,它会立即刷新其成员信息。

表二:客户驱动和服务器驱动的协调方法的性能。

 

99.9th百分读延时(毫秒)

99.9th百分写入延时(毫秒)

平均读取延时时间(毫秒)

平均写入延时(毫秒)

服务器驱动

68.9

68.5

3.9

4.02

客户驱动

30.4

30.4

1.55

1.9


   表 显示了 24 小时内观察到的,对比于使用服务端协调方法,使用客户端驱动的协调方法,在 99.9 百分位延时和平均延时的改善。如表所示,客户端驱动的协调方法, 99.9 百分位减少至少 30 毫秒的延时,以及降低了 3 4 毫秒的平均延时。延时的改善是因为客户端驱动的方法消除了负载平衡器额外的开销以及网络一跳,这在请求被分配到一个随机节点时将导致的开销。如表所示,平均延时往往要明显比 99.9 百分位延时低。这是因为 Dynamo 的存储引擎缓存和写缓冲器具有良好的命中率。此外,由于负载平衡器和网络引入额外的对响应时间的可变性,在响应时间方面, 99.9th 百分位这这种情况下 ( 即使用负载平衡器 ) 响应时间比平均情况下要高。

   
平衡后台任务 vs 前台任务   
   
每个节点除了正常的前台
put/get 操作,还将执行不同的后台任务,如数据的副本的同步和数据移交 (由于提示 或添加 / 删除节点导致 ) 。在早期的生产设置中,这些后台任务触发了资源争用问题,影响了正常的 put get 操作的性能。因此,有必要确保后台任务只有在不会显著影响正常的关键操作时运行。为了达到这个目的,所有后台任务都整合了管理控制机制。每个后台任务都使用此控制器,以预留所有后台任务共享的时间片资源 ( 如数据库 ) 。采用一个基于对前台任务进行监控的反馈机制来控制用于后台任务的时间片数。
   管理控制器在进行前台
put/get 操作时不断监测资源访问的行为,监测数据包括对磁盘操作延时,由于锁争用导致的失败的数据库访问和交易超时,以及请求队列等待时间。此信息是用于检查在特定的后沿时间窗口延时 ( 或失败 ) 的百分位是否接近所期望的阀值。例如,背景控制器检查,看看数据库的 99 百分位的读延时 ( 在最后 60 秒内 ) 与预设的阈值 ( 比如 50 毫秒 ) 的接近程度。该控制器采用这种比较来评估前台业务的资源可用性。随后,它决定多少时间片可以提供给后台任务,从而利用反馈环来限制背景活动的侵扰。

   讨论

   
本节总结了在实现和维护
Dynamo 过程中获得的一些经验。很多 Amazon 的内部服务在过去二年中已经使用了 Dynamo ,它给应用提供了很高级别的可用性。特别是,应用程序的 99.9995 %的请求都收到成功的响应 ( 无超时 ) ,到目前为止,无数据丢失事件发生。
    此外,
Dynamo 的主要优点是,它提供了使用三个参数的 (N R W) ,根据自己的需要来调整它们的实例。不同于流行的商业数据存储, Dynamo 将数据一致性与协调的逻辑问题暴露给开发者。开始,人们可能会认为应用程序逻辑会变得更加复杂。然而,从历史上看, Amazon 平台都为高可用性而构建,且许多应用内置了处理不同的失效模式和可能出现的不一致性。因此,移植这些应用程序到使用 Dynamo 是一个相对简单的任务。对于那些希望使用 Dynamo 的应用,需要开发的初始阶段做一些分析,以选择正确的冲突的协调机制以适当地满足业务情况。最后, Dynamo 采用全成员(fullmembership) 模式,其中每个节点都知道其对等节点承载的数据。要做到这一点,每个节点都需要积极地与系统中的其他节点 Gossip 完整的路由表。这种模式在一个包含数百个节点的系统中运作良好,然而,扩展这样的设计以运行成千上万节点并不容易,因为维持路由表的开销将随着系统的大小的增加而增加。克服这种限制可能需要通过对 Dynamo 引入分层扩展。此外,请注意这个问题正在积极由 O(1)DHT 的系统解决

第七节结论
   本文介绍了Dynamo,一个高度可用和可扩展的数据存储系统,被Amazon.com电子商务平台用来存储许多核心服务的状态。Dynamo已经提供了所需的可用性和性能水平,并已成功处理服务器故障,数据中心故障和网络分裂。Dynamo是增量扩展,并允许服务的拥有者根据请求负载按比例增加或减少。Dynamo让服务的所有者通过调整参数N,R和W来达到他们渴求的性能,耐用性和一致性的SLA。
   在过去的一年生产系统使用Dynamo表明,分散技术可以结合起来提供一个单一的高可用性系统。其成功应用在最具挑战性的应用环境之一中表明,最终一致性的存储系统可以是一个高度可用的应用程序的构建块。

 

你可能感兴趣的:(architecture)