http://catkang.github.io/2016/05/27/dynamo.html

Dynamo是Amazon开发的分布式存储系统,本文是阅读Dynamo论文后的总结:Dynamo: Amazon’s Highly Available Key-value Store。将从背景、定位、简介、问题及解决方案几个方面介绍Dynamo的整体设计思路。

背景

Dynamo是在Amazon所处的应用环境中因运而生的,其需要面对的问题和场景在互联网的业务中也是类似的:

  • 大多数场景并不需要复杂的查询功能;
  • 由于使用商用机作为服务器,导致机器或网络失败成为相对常见并需要妥善处理的场景;
  • 为了提供良好的用户体验,服务需要极高的可用性和较好的性能;
  • 随着业务量逐步增大,服务的处理能力也需要平滑提升。

定位

为了理解Dynamo的整体设计,了解其定位是很有必要的,因为从其定位中我们可以清楚的了解到系统关注的问题和可以放弃或妥协的点。针对上所述的背景,Dynamo提出了自己的基本定位:

  • 仅提供简单的kv查询
  • 高可用性
  • 易扩展性

正是由于上述对自己的定位,Dynamo在设计中可以舍弃掉许多负担,如关系型数据库复杂的查询模型、对ACID的支持,对强一致性的追求,以及复杂的存储结构。同时,Dynamo认为其面对的是相对安全的内部网络环境,所以并没有处理安全或权限问题。

简介

  • Dynamo客户端使用put,get接口读写指定key所对应的数据;
  • 将整个数据空间划分为不同分片后存储在不同节点上;
  • 其通过分片复制和一系列故障发生时的应对方案来保证整个服务的高可用;
  • 为了可用性Dynamo损失了一些一致性,可能发生的数据冲突有可能需要应用程序处理;
  • Dynamo去中心化的维护整个集群的成员及故障信息,并采用gossip同步。

问题及解决方案

1,数据分片

为了更好更灵活的操作管理存储的数据,对数据空间进行分片并将分片分配到不同存储机器是一个显而易见的不错的做法。Dynamo采用类似一致性哈希的方式进行分片的划分和分配。关于数据分片的算法,Dynamo经历了几个阶段的选择和进化。

  • 传统的一致性hash:将机器节点随机对应在hash ring上,数据key所对应的hash值所在位置顺时针遇到的第一个节点负责自己所在的range。
  • 优点:新节点加入,或旧节点退出时,只影响紧相邻的下一个节点
  • 缺点:负载不均匀且不同的机器性能的不同没有考虑到
  • 增加virtrual nodes的一致性hash:这时每个机器节点对应hash ring上的多个虚拟节点,可以根据机器性能方便的调节所负责的虚拟节点数
  • 优点:充分考虑机器性能的不同且可以做到负载均衡
  • 缺点:分片转移时,实现上需要对整个range进行遍历; 加入或删除节点时Merkle tree需要重新计算
  • 数据空间等分Q份,T个机器节点时,每个机器分得S个分片,其中Q=T*S
  • 优点:分片固定大小,可对应单个文件,因此容易加入或删除节点,且容易备份。

2,分片备份

为了系统高可用,Dynamo的每个分片都有N个副本,存储在hash ring上顺时针方向的N个节点上。这N个节点称为该数据的preference list。其中的每一个节点都可以对接受针对该数据的操作请求。 

3,数据版本及冲突处理

由于preference list中的每个节点都可以对同一个数据进行处理,且为了高可用,用户写请求返回前并没有将数据同步到所有分片。当有大量并行访问或故障发生时,集群中的不同机器看到的同一个数据状态可能不同,这时就发生了冲突。Dynamo引入vector clock来在一定程度上缓解冲突的发生,并最终由应用端对冲突进行合并。



vector clock := list{ (node, counter), ...}
node := 机器id
counter := 该数据在node上的处理序序号



  • vector clock通过列出在数据在每个节点上的处理序列来发现不同vector clock之间的因果关系,其中每个(node, counter)可以看做是一个分量。
  • 当某个vector clock的所有分量都小于另一个时,该vector clock便是另一个的因,可以被覆盖。Dynamo节点通过这种因果关系尽可能的处理冲突。
  • 没有因果关系的所有vector clock需要全部返回客户端,在应用端处理
  • 。 

4,读写过程

  • 客户端通过负载均衡代理或者自己维护数据到机器的映射关系,将请求最终交给preference list中的一个Dynamo节点处理,该节点称为coodinator
  • Dynamo采用类似Quorum的方式保证数据正确,即W+R>N。
  • Put流程:
  • coodinator生成新的数据版本,及vector clock分量
  • 本地保存新数据
  • 向preference list中的所有节点发送写入请求
  • 收到W-1个确认后向用户返回成功
  • Get流程
  • coodinator向preference list中所有节点请求数据版本
  • 等到R-1个答复
  • coodinator通过vector clock处理有因果关系的数据版本
  • 将不相关的所有数据版本返回用户

5,发生暂时错误时保证可用

Dynamo中用hinted handoff的方式保证在出现暂时的节点或网络故障时,集群依然可以正常提供服务。

  • 流程:
  • 节点失败时,会将其负责分片发送给一致性哈希环上下一个本来没有该分片的节点
  • 收到该分片的节点会将分片放到单独的空间,并成为该分片的处理节点,同时不断的通过hint检测原节点
  • 发现原节点可用时,将数据传回原节点并删除本地分片
  • 优点:
  • 避免了短暂的机器或网络故障造成的不可用

6,降低分片同步数据传输量

通过上面所述,可以看出当故障发生或者有新节点加入、离开集群时,都涉及分片的拷贝和传输。希望能够快速检查分片中内容是否相同,并通过仅发送不同的部分来减少数据传输量。Dynamo采用Merkle Tree来解决这个问题。

  • Merkle tree:
  • 每个叶子节点对应一个数据项,并记录其hash值
  • 每个非叶子节点记录其所有子节点的hash值
  • 使用:
  • Dynamo为自己维护的每一个分片维护一个Merkle Tree
  • 需要比较分片是否相同时,自根向下的比较两个Merkle Tree的对应节点,可以快速发现并定位差异所在

7,成员信息及故障检测

  • 考虑到节点失败无法恢复的情况并不常见,Dynamo加入或离开集群都需要手动通过命令完成;
  • 当有用户请求时,coordinator会发现不可达的节点,并用其他节点代替之,之后开始周期性探测其恢复;
  • Dynamo集群中的每台机器都会维护当前集群的成员及节点不可达等信息,这些信息通过gossip协议广播到整个集群;
  • 客户端可以通过任意一个节点获得并维护这种成员信息,从而精确的找到自己要访问的数据所在。

总结

论文中介绍了Dynamo面对的问题及其解决方案,而对于其中众多的细节还需要在工程实现中权衡和优化。包括Dynamo中冲突的处理方式,对大多数业务场景来说可能都是无法接受的。