与Redis的区别

  1. 使用场景:Dynamo是永久可写的持久文件系统,Redis是内存数据库,主要做缓存用。
  2. 存储方式:Dynamo是磁盘存储,Redis是内存存储+磁盘持久化
  3. 系统规模:Dynamo是分布式系统,Redis可以是单机或者集群

Dynamo特点

  1. 键值存储
  2. 可拓展性和高可用性
  3. 去中心化

背景

为什么要有Dynamo

  1. 检索效率:一些场景只需要主键检索,而不需要RDBMS提供的复杂功能。
  2. 可拓展性:RDBMS复制功能受限,通过牺牲可用性来保证一致性。

适用场景

  1. 通过唯一的Key进行读写,值以二进制对象存储。
  2. 单一数据单元的操作,即没有关系型Schema的需求。
  3. 存储较小的文件(1MB以内)。
  4. 不保证ACID。
  5. CAP:可用性(A)优先级大于一致性©。

SLA(Service Level Agreement)

允许服务自己控制自己的系统特性。让服务自行决定如何在功能、性能、成本之间折中。

Dynamo的设计考虑

最终一致数据仓库(Eventually consistent data store),最终所有的更新会应用到所有的副本。

何时解决冲突

Dynamo保证永远可写,即写入请求永远不会被拒绝。因此冲突问题的解决主要体现在读操作上。

谁来解决冲突

数据仓库的冲突解决方案往往是最后写入有效(Last write wins)。
应用能够理解数据的意义,可以自主选择合适的冲突解决算法(合并、最后一次写入有效等等)。

其他特性

  1. 增量拓展:在很少影响系统和运维的情况下支持节点的可拓展
  2. 对称性:每个节点职责相同。这样可以简化系统的交付和运维
  3. 去中心化:去中心化使得系统高可用、可拓展、更简单
  4. 异构性:系统要考虑到基础设施的差异化。如机器的负载要和机器的性能成比例。

系统架构

分布式核心技术

Partition

技术:一致性哈希
优点:增量可拓展性

写高可用

技术:读时协调的向量时钟
优点:版本大小与更新频率解耦

临时故障的处理

技术:宽松选举与附带信息的移交
优点:部分副本不可用时仍然能保证可用性和持久性

持久的故障恢复

技术:基于Merkel tree的逆熵
优点:后台同步版本不一致的副本

成员管理与故障检测

技术:基于Gossip的成员管理协议和故障检测
优点:保持架构的去中心化,无需中心节点存储成员和节点状态

系统接口

Dynamo提供两个操作接口:PUT() 和 GET()。

get(key)

get(key)会定位到存储系统中key对应的所有对象副本,返回对象(有可能是包含多个版本的对象列表)、一个上下文(Context)。

put(key)

put操作确定对象存放的位置,然后写入相应的磁盘。
Dynamo将调用者提供的key进行MD5运算得到一个128bit的ID值,根据这个ID值计算存储的节点位置。

context

context包含对象的元数据信息,如对象的版本。该信息对调用者是不透明。系统可以通过上下文验证put的请求的context是否合法。

数据分散算法

Dynamo为了支持增量拓展,需要将数据分散到系统的不同节点。Dynamo的分散方案采用的是一致性哈希算法。

算法原理

一致性哈希算法是将值对2的32次方进行模运算,从而将哈希空间映射到一个虚拟的环上[0-(2的32次方-1)]。

  1. 当节点加入到哈希环时,按照一定的规则(如按照节点名称进行哈希运算)计算节点的位置。
  2. 在存取数据时,使用调用方的key值进行哈希运算得到对应的哈希环的位置
  3. 以顺时针的方向向后寻找到对应的节点机器,进行相应的存取操作。

优点

  1. 减少节点(节点故障)
    传统的哈希映射中,当节点数量减少时,由于模值变小整个节点群在进行运算时就会发生偏移。原来节点存储的数据在新的模运算下将无法访问到。
    一致性哈希运算,只会影响该节点和前一个(逆时针前序)节点之间的数据。其他节点能正常工作。
  2. 新增节点
    传统的哈希映射,当新加入节点时,模值增大,整个节点在运算时也会发生偏移。这就会导致整个系统需要进行大规模数据迁移来保证原有数据的正常访问,效率非常低。
    一致性哈希算法,当加入新的节点时,只有该节点到逆时针前序节点之间的数据需要进行迁移。其他节点的数据不受影响。该算法使得需要迁移的数据量大大减小。

一致性哈希算法具有较好的容错性可拓展性

局限性

初级的一致性哈希算法可能会出现由于节点分布不均导致部分节点负载过大的问题。
为了解决这一问题,可以引入虚拟节点。即在哈希环上,一个真实节点可以映射到环上的多个位置,虚拟节点通过指向真实节点完成工作。
虚拟节点的好处:

  1. 负载更加均衡
  2. 可以根据不同节点的性能差异设置不同的虚拟节点数量,充分利用物理机器的异构性。

数据复制(Replication)

Dynamo会将数据复制到N台机器上,N可在Dynamo中配置。
每一个Key会对应一个协调者节点,该节点除了存储这个键值对之外,还需要在哈希环上顺时针方向寻找N-1个节点,将该键值对复制到这些节点上去。

以图片上的K值(A,B]为例,N=3的情况下,该键值对的协调者节点为B,顺时针方向的CD节点也需要存储K键值对。
存储某个键值对的所有节点会形成一个优先列表(Preference List),由于虚拟节点的存在,优先列表在选择存储节点的时候会跳过一些位置,使得该列表里面的节点都是不重复的物理节点。

数据版本化

Dynamo保证最终一致性,所有的更新操作会异步传递给所有副本。
put()操作(新增或者删除操作都是put)返回时,数据的更新可能还没有应用到所有的副本。因此,get()操作可能拿不到最新的数据。尽快系统在正常情况下有最终一致性的耗时上限,但是在服务器宕机或者网络发生分裂的情况下,副本之间可能还是无法达成一致。
Amazon有一些应用可以容忍这种不一致的情况。比如,购物车应用要求“添加到购物车”的请求永远不会被拒绝或丢失,如果用户在操作购物车时出现了A(删除一件鞋子)、B(加入了一件T恤)两种不一致的状态,不应该是A状态覆盖B或者B状态覆盖A,而是需要在后续的步骤处理更新冲突。

如何解决更新冲突

Dynamo将每次的修改结果都作为一个新的、不可变的版本。
冲突解决的方式:

  1. 基于句法的调和(Syntactic reconciliation)
  2. 基于语义的调和(Semantic reconciliation)
    一般情况下,新版本的数据会包含老版本的数据。系统可以自行决定权威版本(基于语法的调和)。但是,在出现分叉的情况之下,则需要客户端介入,进行分支合并(基于语义的调和)
    还有一些情况下,系统可能存在三个及以上的分叉版本,每个版本的子历史都不一样,这时候就需要系统将其一致化。这需要将应用设计为显式承认多版本存在的可能性以防止任何更新的丢失。

向量时钟(Vector clock)

Dynamo使用向量时钟来追踪同一对象不同版本之间的因果性。向量时钟就是一个(Node,Counter)的列表。向量时钟关联了所有版本,可以通过它判断两个版本之间的关系(并行、因果)。如果对象的A版本中所有对应Node的counter均小于B版本中对应Node的counter,则认为A是B的祖先,则A可以安全的删除;否则的话,这两个版本就是冲突的,需要进行调和。

Dynamo在执行更新操作时,需要指定基于哪个版本进行的更新。具体流程是先执行Get操作,拿到对象的context(其中包含向量时钟信息),然后put的时候带上这个context。

示例:

  1. A客户端发起写操作,由Sx节点执行写入。向量时钟为[(Sx,1)],数据为D1
  2. A客户端在D1基础上执行更新,由Sx节点写入,向量时钟为[(Sx,2)],数据为D2
  3. P,Q客户端在D2的基础上分别发起写入操作,分别由Sy,Sz节点执行。对应的向量时钟为[(Sx,2),(Sy,1)]和[(Sx,2),(Sz,1)],数据为D3,D4。
  4. A客户端Get操作得到D3和D4两个版本(由于Dynamo无法通过句法调和解决这个分叉冲突,因此返回所有版本给客户端,由客户端处理)。客户端拿到数据以后执行语义调和,由Sx节点进行协调写入(Coordinate the write)。最后向量时钟为[(Sx,3),(Sy,1),(Sz,1)]
    向量时钟潜在的问题:
    假设在上述步骤的第四步时,有多个客户端都进行读操作。而每个客户端都会触发协调写入。但这个时候,每个客户端执行协调写入以后会出现多个并行版本。如 [(Sx,3),(Sy,1),(Sz,1)]、[(Sx,2),(Sy,1),(Sz,1),(Sm,1)],[(Sx,2),(Sy,1),(Sz,1),(Sn,1)]、[(Sx,2),(Sy,1),(Sz,1),(Sw,1)]。在这种情况下,新的协调写入(假设是So节点写入)的结果可能是[(Sx,3),(Sy,1),(Sz,1),(Sm,1),(Sn,1),(Sw,1),(So,1)]。这样一来,该对象的向量时钟列表就比较长。
    为了解决这个问题,Dynamo采用clock截断方案。
    Dynamo另外保存一个与(Node,counter)对应的时间戳,记录对应节点最后一次更新的时间戳。当向量时钟列表长度超过10时,就删除最老的一项。但是,这个方案可能会导致在执行调和的时候无法判断部分后代的因果关系。

Put和Get的执行过程

在Dynamo中,任何存储节点都可以执行任何Key的get和put请求

请求方式

get和put请求由Amazon的基础设施相关的请求处理框架发起,使用HTTP协议。客户端有两种请求方式:

  1. 将请求路由到负载均衡器,由负载均衡器根据负载信息选择一个后端节点。该方法下,客户端与Dynamo解耦,客户端应用不需要了解Dynamo。
  2. 使用能感知partition的客户端,将请求直接路由到某个coordinator节点。该方法下,客户端直接与节点通信,延迟更低。

请求原则

负责处理读写请求的节点成为Coordinator节点。通常情况下,该节点是优先列表里面前N个节点中的第一个节点。如果请求是由负载均衡器转发的,那么这个请求可能会被转发到哈希环上的任意一个节点。在这种情况下,如果收到请求的节点不是优先列表前N的节点,那么该节点就不会处理这个请求,而是将其转发到前N节点的第一个节点。

**读写操作需要优先列表中前N个节点处于健康状态。**如果有故障节点,则跳过该节点。所有节点的健康的情况下,选取前N个节点。如果节点有故障或者网络分裂,则选取优先列表中编号较小的节点。

读写操作仲裁算法

为了保证副本一致性,Dynamo使用类似仲裁系统的一致性协议。这个协议有两个配置参数:R和W。

  • R:允许一次读操作所需要的最少投票者
  • W: 允许一次写操作所需要的最少投票者

设置R+W>N就可以得到一个类仲裁系统。在这种模型下,一次Get(Put)的延迟由R(W)个副本中最慢的一个决定。因此,为了降低延迟,通常R和W都设置的比N小。

读写过程

coordinator节点收到put请求后,会为新版本的数据生成vector clock,并将其保存到本地。然后,再将新版本数据发送给优先列表中前N个可到达的节点。coordinator节点收到W-1个节点的成功返回,则认为该写入操作成功。

对于一个Get请求,coordinator节点会请求前N个可到达节点关于这个Key值对应数据的版本,等得到R个响应以后,就将该节点返回给客户端。如果coordinator节点收到多个版本的数据,它会进行句法调和(Syntactic Reconciliation),然后将没有因果关系的数据版本返回给客户端。客户端如果收到多个版本的数据,则需要进行语义调和(Semantic Reconciliation)合并数据版本并执行协调写入。

短时故障处理:hinted Handoff

如果使用传统的仲裁算法,Dynamo无法在服务器宕机或者分裂的时候仍然保持可用,而且在遇到简单故障的情况下,持久性也会降低。

Dynamo采用宽松的仲裁机制(Sloppy Quorum):所有读写操作在前N个健康的节点上执行。注意,这里的N节点并不一定指的是前N个节点,在遇到故障节点时,会沿着哈希环顺时针后延。

以图片为例,N=3时,如果A节点发生故障,那么到达A的请求就会发送给D。这样顺延是为了保证可用性和持久性。发送到D节点的数据副本的元数据中会提示该副本应该发送给谁,这个数据副本会被存储在D节点独立的数据库中,并由一个定时任务不断扫描,一旦A节点可用,就将该数据发回A,然后删除本地的数据副本。

使用这种hinted handoff方式,Dynamo保证了在节点或网络发生短时故障时读写操作不会失败。希望可用性最高的应用可以将W设置为1,这样可以保证只要有一个节点完成写入,操作就能被系统接受。实际上,大部分Amazon的应用都是设置一个比1大的值,以满足期望的持久性。

高度可用的存储系统必须能够处理整个数据中心挂掉的情况。Dynamo可以配置向多个数据中心同步副本,只要将优先列表里的节点分散到不同的数据中心即可。

持久故障处理:副本跨数据中心同步

短时故障情况下,Hinted handoff能很好的应对。但是,可能存在一些情况,如Hinted handoff节点在还未移交该数据副本到原本对应的节点之前,Hinted handoff节点就发生故障。为了解决这一类影响到持久性的场景,Dynamo实现了一种逆熵(副本同步)协议来保证副本是同步的。

为了快速检测副本之间的不一致性、以及最小化的转移数据量,Dynamo使用Merkle trees(默克尔树)。

一棵默克尔树就是一个哈希树,其叶子节点值是Key对应的Value值的哈希值,父节点是子节点的哈希值。
默克尔树优点:

  1. 每个分支都可以独立查看、节点无需下载整棵树或者整个数据集。
  2. 减少检查副本一致性时所需传输的数据量。

例如,如果两个树的根节点哈希值相同,则两个树一定相同,对应的数据副本也是一致的,就不需要再进行同步。否则,两棵树就是不同的,则需要从上往下找到不一致的叶子节点,找到对应的不一致的Key后就可以针对性的进行数据同步。这样能极大减少同步过程中需要转移的数据量和磁盘IO次数。

Dynamo使用默克尔树实现逆熵过程:每个节点为每段Key range(一台虚拟节点所覆盖的Key范围)维护了一棵单独的默克尔树。这样,节点之间可以比较key range,确定其维护的range内的key是否是最新的。两个节点可以比较共同的key range所对应的默克尔树根节点,如果有不一致的,可以进行树的遍历找到不一致叶子节点,然后进行同步。

该方案缺点:每当有新节点加入或者退出时,key range就会发生改变。对应的默克尔树就需要重新计算。

节点成员管理与故障检测

哈希环成员

在Amazon环境中,节点服务不可用的情况一般持续时间较短,但也有可能存在中断时间较长情况。一个节点服务的中断并不代表该节点永久地离开了系统,因此不应该导致系统对partition进行再平衡、或者修复无法访问地副本。与此类似,无意地手动操作也可能导致新的节点加入Dynamo。

因此,为了避免以上问题,我们决定使用显式机制来向Dynamo增删节点。
管理员使用命令行或者web终端连接到Dynamo Node,然后下发一个成员变更命令,来将这个Node添加到ring或者从ring删除。负责处理这个请求的Node将成员变动信息和对应的时间写入持久存储。成员变动会形成历史记录,因为一个节点会多次从系统中添加或删除。Dynamo使用Gossip-based的算法通告成员变动信息,维护成员的一份最终一致视图。

每个节点每秒随机选择另外一个节点作为对端,两个节点会高效地协调成员变动历史。

一个节点初次运行时,会选择它的token集合(一致性哈希环上的虚拟节点),然后将节点映射到各自的token集合。映射关系持久化存储到磁盘上,初始时只包含本节点和token集合。存储在不同节点上的映射关系会在节点交换成员变动历史时被调和。因此,分区(partitioning)和位置(placement)信息也会通过gossip协议进行扩散,最终每个节点都能知道其他节点负责的token范围。
这使得每一个节点可以将一个key的读写操作直接发送给正确的节点进行处理。

系统外部发现和种子节点

以上机制可能会导致Dynamo ring在逻辑上的临时分裂。

例如,管理员先联系node A,将其加入ring;然后又联系node B,将B加入ring。在这种情况下,node A和node B都认为自己是环成员,但是并没有感知到对方的存在。

为了避免逻辑分裂,我们会将一些Dynamo节点作为种子节点。种子节点是通过外部机制发现的,所有节点都知道种子节点的存在。所有节点都会和种子节点协调成员信息,因此逻辑分裂的可能性几乎不存在。

种子节点可以从静态配置文件获取或从配置中心获取。种子节点具有普通节点的全部功能。

故障检测

故障检测在Dynamo中用于如下场景跳过不可达节点:

  • get和put操作时
  • 转移partition和hinted replica时

要避免尝试与不可达节点通信,一个纯本地概念的故障检测就够了:节点B只要没收到节点A的答复,B就认为A不可达。

在客户端有持续频率的请求下,Dynamo ring的节点之间就会有持续的交互;因此只要B无法应答消息,A就可以很快发现。在这种情况下,A选择与B同属于一个Partition的其他节点来处理该请求,并定期检查B是否活过来。

在没有持续客户端请求的情况下,两个节点都不需要知道另一方是否可达。

去中心化故障检测协议使用简单的gossip风格协议,使得系统内每个节点都可以感知到其他节点的加入或者离开。

添加、移除存储节点

当一个新的节点X加入系统后,它会获得一些随机分散在ring上的token。对分配给X的key range,当前可能已经有一些节点在负责处理。因此,将Key range分配给X后,这些节点就不需要再处理这些key对应的请求了,而是要将keys转移给X。


基于图二,N=3的情况下,节点X加入到A-B之间。此时,X节点需要负责(F,G]、(G,A]、(A,X]范围的键值对请求。其中(F,A]范围是因为N=3的备份因子的要求。在这种情况下,B、C、D节点就不需要再负责(A,X]范围的key。因此,在收到X转移key的请求后,BCD节点就会向X转移相应的key。当移除一个节点时,操作则与之相反。

Dynamo的实际运行经验表明,这种方式可以使得存储节点之间保持key的均匀分布,这对于保证延迟需求和快速的bootstrapping非常重要。此外,在源节点和目的节点之间增加了转移确认机制,可以避免转移重复的key range。

实现

Dynamo中每个存储节点上主要有三个组件,都是使用Java实现:

  1. 请求协调组件
  2. 成员验证和故障检测组件
  3. 本地持久存储引擎

本地持久存储引擎

Dynamo的本地存储组件支持以插件的方式使用不同的存储引擎:

  • Berkeley Database(BDB)Transactional Data Store
  • BDB Java Edition
  • MySQL
  • an in-memory buffer with persistent backing store
    通过可插拔的设计,不同类型的应用可以根据存储需求选择合适的存储引擎。

BDB通常用于处理大小几十KB的对象,而MySQL可以处理更大的对象。

请求协调组件

该组件构建在一个事件驱动的消息系统上,其中消息处理的pipeline分为多个阶段,和SEDA架构类似。所有通信基于Java NIO实现。

Coordinator代替客户端执行读和写的请求:读操作时会从一个或多个节点收集数据、写操作时会向一个或多个节点存储数据。每个客户端请求都会在收到这个请求的节点上创建一个状态机。这个状态机包含了识别key对应的节点、发送请求、等待响应、重试、处理响应结果和组合响应返回给客户端的所有逻辑。

Read Coordination

每个状态机处理且只处理一个读请求,一个读操作的状态机如下:

  1. 发送读请求给节点
  2. 等待所需的最少数量响应
  3. 如果在规定的时间内没有收到足够多的响应,则认为请求失败
  4. 否则,收集对象的所有版本,确定哪些应该返回
  5. 如果打开的版本化配置,执行syntactic reconciliation,生成一个不透明的写上下文,其中包含了合并之后的版本对应的vector clock。

读操作的响应发送给调用方之后,状态机会继续等待一小段时间,接收可能的响应。如果返回中有过期版本,coordinator就需要合并版本,并将最新版本更新回这些节点。这个过程成为读时修复(Read Repair),因为它在一个乐观的时间点修复了哪些错过了最新更新的副本,减少了逆熵协议的工作(该工作本该由逆熵协议做)。

Write Coordination

写请求是由优先列表里面前N个节点的任意一个Coordinate,总是让N个节点中的第一个来coordinate有一些好处:在同一个地方完成写操作的序列化。但是,这种方式也有缺点:它会导致不均分的负载分布,损害SLA。这是因为对象请求并不是分布均匀的。为了解决这个问题,优先列表前N节点都可以执行coordinate操作。而且,因为一个写操作之前通常伴随一个读操作,因此写操作的coordinate都可以选择为:前一次读操作返回最快的哪个节点,这个信息存储在读操作返回的上下文中。

这项优化还使得在下一次读取时,前一次的读操作选中的存储这个数据的节点更容易被选中,提高了**“读取刚刚写入的数据”**的概率。同时还降低了请求处理性能的抖动,提高了P99.9性能。

测试结果与总结

Dynamo适用场景

  1. **业务逻辑相关的reconciliation:**每个数据对象都会复制到不同的存储节点上,发生版本冲突时由应用负责执行reconciliation。如购物车场景。
  2. **基于时间戳的reconciliation:发生冲突时采用“最后一次写入胜出策略”(last write wins)。**如用户session信息的维护。
  3. 高性能读引擎:Dynamo被设计为永远可写,但应用可以通过对Dynamo的仲裁特性调优,使得它作为一个高性能读引擎使用。当应用具有高频读低频写的特性时,可以将R设置为1,W设置为N。

Dynamo最大的优势是,客户端应用可以通过对N、R、W三个参数进行调优来达到期望的性能、可用性和持久性等级。
N的大小决定了每个对象的持久性。Dynamo用户的常用配置是3。
W和R的值会影响对象的可用性、持久性和一致性。例如,将W设置为1,那么只要有一个节点是正常工作的,写入操作就不会被拒绝。但是,太小的W和R可能会增加不一致的风险。
传统观点认为持久性和可用性是相伴而生的,但是在Dynamo中,增加W会降低持久性的风险窗口,但是这会增加请求被拒绝的概率,即降低了可用性。

目前常用的Dynamo配置是(3,2,2),有几百个基于异构硬件配置的节点。

性能和持久性的平衡

为了提供一致性的用户体验,Amazon的服务设置了一个很高的性能指标(如P99.9、P99.99)。典型的SLA指标是:读写操作的P99.9要在300ms内完成。
由于Dynamo是在通用硬件上运行,IO吞吐性能要比高端企业级服务器差很多。因此,提供一致的高性能读写并不简单。并且,读写操作涉及多台节点,性能受限于最慢的那个副本所在节点。

通用配置下的性能数据


图片中X轴每刻度为12小时,y轴表示时间延迟(毫秒)。从图片可以看出,写请求的平均延迟和P99.9均比读请求高,因为写请求总是涉及磁盘IO。
P99.9比平均值高一个数量级,因为P99.9有很多影响因素:如请求负载变化、对象大小、网络位置模式等。

低延迟配置下的性能数据

通用配置下的性能可以满足大部分场景的要求,但是一小部分情况对延迟要求可能会更高。在这种情况下,Dynamo可以牺牲一定的持久性换性能。Dynamo的存储节点在主存中维护一个对象缓存,写操作将数据存储到缓存后直接返回,由另外的独立线程定期将主存数据写入磁盘。读操作时,先检查缓存是否存在,有则返回缓存数据,从而避免访问存储引擎。

该优化可以将峰值流量期间的P99.9降低到原来的1/5。即使只用到一个存放1000对象的缓存,如下图所示。

从图中可以看出缓存写性能更加平滑。显然,该方案对持久性和性能做了折中,节点有可能在缓存数据还未落盘的时候挂掉导致数据丢失。为了减少这种风险,写操作进行了优化:Coordinator除了对优先列表的N个节点发送缓存写指令外,还会选择一个节点发送持久写的指令。Coordinator只需要等待W个成功写入就可以向客户端返回,而不需要等待那一个持久化写入的返回。

均匀负载分布

Dynamo通过一致性哈希将它的key空间进行partition,保证负载分布的均匀性。只要key的访问不是极度不均衡的,均匀的key分布可以帮助实现负载均衡。下文将介绍Dynamo的负载均衡策略。

每个节点T个随机Token

该策略下,会给每个节点随机分配T个Token。所有节点的token在哈希空间是有序的,相邻的两个token定义一个范围(key range)。最后一个token和第一个token首尾相连。
因为token的选择是随机的,因此每个节点分配到的范围有大有小。同时,当有节点加入或者离开系统时,节点token集合会发生变化,导致范围跟随改变。每个节点用于维护成员信息的空间也会随着节点的增加和增加。
该策略下的几个问题:

  1. 一个节点加入系统以后,需要从其他节点“偷”出它所需要的key range。这会导致那些需要将一部分key range移交给新节点的节点,扫描他们全部的本地存储,以过滤出所需的数据,该操作会占用大量的磁盘IO,影响服务性能。为了保证服务的性能,只能将这些任务降低优先级,结果就是导致新节点上线速度很慢。
  2. 一个节点加入或者离开系统时,很多节点负责的key range会发生变化,对应的merkle tree需要重新计算。对生产环境来说,这工作也不小。
  3. 由于key range的随机性,无法快速对整个key空间进行快照。这使得存档工作变得复杂。在该方案下,进行一次快照需要分别从所有节点获取key,效率低下。

该策略的本质问题在于,数据的partition和placement方案混合在了一起。一些场景下,添加新的节点是为了应对请求量的增长,而该方案下,添加新节点也会导致原有节点负责的数据发生迁移,影响整个数据的partition。

哈希空间均匀分散、每个节点T个随机Token

该策略将哈希空间分为Q个大小相同的Partition,每个节点分配T个随机token。Q的选择满足:Q>>N && Q>>S*T(>>表示远大于),其中S是节点数量。
在该策略下,token仅仅用于将哈希空间的值映射到有序节点列表,token不会影响数据的partition。一个partition的数据会放在该partition末尾开始沿顺时针方向得到的前N个独立节点。

上图展示了key值对应的partition(分区)和placement(归属节点)。N=3,A、B、C是key k1的preference list的三个独立节点。阴影区域表示的是preference list是[A、B、C]的key range,箭头表示不同节点对应的token位置。

每个节点Q/S个token,平均分散

策略三也将哈希空间均分分为Q个partition,将partition和placement解耦。与策略二不同的是,该策略下每个节点获得的token数量是Q/S。当一个节点离开时,它的token会随机分配给其他节点;当一个节点加入系统时,它会从其他节点“偷”一些token过来,同时保证Q/S的特性成立。

三种策略的对比总结


对比环境:30个节点、N=3、每个节点维护相同大小的元数据
从图中可以看出:策略三负载均衡性能最优、策略一次之、策略二最差。
策略三的优点如下:

  1. 减少了每个节点所需维护的成员信息的大小。虽然维护成员信息不会占用太多存储,但是考虑到节点需要通过gossip协议定期将成员信息发送给其他节点,因此保持越紧凑越好。(我的理解:该策略下,token的数量等于Q。新节点的加入或退出不会影响这个Q的数量,因此成员信息的存储上相对比较稳定)
  2. bootstrap和恢复更快:因为partition范围是固定的,因此可以将整个partition存放到单独的文件,在进行relocation的时候可以将整个单独文件发送到其他节点。
  3. 易于存档:由于partition可以打包为单独的文件,存档将会更加方便。而策略一中,token是随机选取的,则需要访问所有节点分别获取对应的key值对应的数据对象。
    策略三的缺点:
    变更节点成员时需要进行coordination,以保证每个节点Q/S的特性。

版本分叉

Dynamo牺牲了一些一致性(consistency)来保证可用性(availability)。
常见的两种情况导致的分叉:

  1. 遇到节点失败、数据中心故障或者网络分裂等场景
  2. 同一数据对象的大量并发写操作,不同的节点都在coordinating写操作

冲突情况如果无法通过向量时钟(vector clock)进行句法调和(syntactic reconciliation),则需要交给业务逻辑执行语法调和(semantic reconciliation)。语法调和会给服务增加新的负担,因此应该越少越好。

客户端驱动或者服务端驱动的Coordination

服务端驱动

客户请求通过负载均衡器均匀地发送给哈希环上的节点。每个节点都可以作为读请求的coordinator,而写操作必须由key的preference list里面的节点执行。有该限制是因为,preference list中的节点被赋予额外的职责:创建一个新的版本戳,在因果关系上包含被它写操作更新的版本。如果Dynamo的版本化方案选择的是物理时间戳,则任何节点都可以执行coordinate写操作。

客户端驱动

该模式下,状态机将会前移到客户端。客户端使用本地库在本地执行请求的coordination。每个客户端定期选择一个Dynamo节点,下载它的系统成员状态的当前视图。有了这个信息,客户端就知道任何key所对应的preference list里面包含的节点。
读请求可以在客户端进行coordinate,相比于由负载均衡器转发的方式,这种直接请求可以减少网络跳数。写操作可以转发给key对应的preference里面对应的节点(如果是基于物理时间戳的版本化方式,则可以由客户端本地执行)。
客户端驱动的优势在于,不再需要一个负载均衡器转发请求。因为存储节点对应key值的分布几乎是均匀的。
客户端每隔10秒就会随机地轮询Dynamo节点一次(pull),获取最新的成员信息。采用pull模型相比push模型,服务器在有大量客户端的情况下不用维护大量的信息且可拓展性更好。
最差的情况下,客户端10秒一次更新可能会出现脏数据,客户端在遇到不可达的情况时会立即更新一次成员信息。

性能对比


从图片可以看出,客户端模式P99.9比服务端减少了30ms,平均值减少了3ms左右。
性能提升是因为客户端方式没有负载均衡器的开销、减少了网络路由的条数、以及由于负载均衡器的引入导致的网络抖动问题。

平衡前后台任务

Dynamo节点除了执行前台的put/get操作外,还要执行如副本同步和数据移交等后台任务。
为了保证后台任务的执行不影响前台任务,Dynamo引入了一种许可控制机制(admission control mechanism)。每个后台任务通过这个控制器申请资源(数据库)的运行时间片,这些资源是所有后台任务共享的。通过对前台任务性能的监控,系统可以使用反馈机制更改后台任务的时间片数量。
许可控制器(admission controller)在执行前台put/get操作时,会持续监控资源使用情况。**监控的指标包括:磁盘操作延迟、锁竞争、事务超时导致的访问失败次数、请求队列等待时间等。**这些信息用于判定在给定的时间窗口内的延迟和失败是否在可接受的范围,并适当的调整后台程序的时间片数量。例如,许可控制器检查数据库P99的延迟是否离预设的阈值足够近。控制器正是通过这些信息为前台评估资源的可用性,然后决定后台任务的时间片数量,利用反馈机制避免后台任务的侵入性

总结

Dynamo提供了应用可配置的能力,应用可以通过设置(N、R、W)来满足自己的需求。同时,Dynamo将数据一致性和冲突调和的逻辑开放给开发者,由应用来处理冲突。
Dynamo采用了完整成员模型,在该模型中,每个节点都知道其他节点存储了哪些范围的数据。在实现中,每个节点主动将完整路由表通过gossip传输给系统内其他节点,该模型对几百台、上千台规模的节点适用。
对于上万台的集群规模,Dynamo还可以引入层级拓展来解决路由表开销过大的问题,如动态哈希树系统技术。

Dynamo-Amazon高可用键值存储 笔记相关推荐

  1. Dynamo:亚马逊的高可用键值存储

    目录 1. 简介 2. 背景 3. 相关工作 4. 系统结构 5. 实现 6. 经验和教训 7. 结论 Dynamo:亚马逊的高可用键值存储 摘要:大规模的可靠性是我们在 Amazon.com 面临的 ...

  2. 解析高可用分布式键值存储 etcd 的原理

    2019独角兽企业重金招聘Python工程师标准>>> 这篇文章将会介绍 etcd 的实现原理,其中包括 Raft 协议.存储两大模块,在最后我们也会简单介绍 etcd 一些具体应用 ...

  3. SLIK: 高扩展、低延时的键值存储索引实现(RAMCloud)

    http://www.wzxue.com/slik-ramcloud/ SLIK: 高扩展.低延时的键值存储索引实现(RAMCloud) 2014 年 06 月 01 日 写评论 作者 麦子迈 RAM ...

  4. cockroachdb mysql_CockroachDB学习笔记——[译]CockroachDB中的SQL:映射表中数据到键值存储...

    CockroachDB学习笔记--[译]CockroachDB中的SQL:映射表中数据到键值存储 原文标题:SQL in CockroachDB: Mapping Table Data to Key- ...

  5. fastjson查找指定键值_分布式键值存储 etcd

    引言 前面的文章中,介绍了基于Paxos的ZooKeeper,本文将介绍另一种分布式一致性算法Raft的工业级实现----etcd,它们虽然实现方案不同,但是最终的实现效果都很像,而且etcd相较于Z ...

  6. 分布式键值存储ETCD

    分布式键值存储 前言 实现分布式系统的目标以及挑战 分布式ETCD存储 etcd介绍 基于etcd的使用场景 1.键值对存储 2.服务注册与发现 3.消息发布与订阅 4.分布式锁 etcd相关术语(关 ...

  7. Cloudflare Workers支持WebAssembly和键值存储

    Cloudflare最近宣布给他们的"无服务器"服务Workers新增两个附加功能:将WebAssembly作为JavaScript的替代方案,提供了对WebAssembly的支持 ...

  8. 五大存储模型关系模型、键值存储、文档存储、列式存储、图形数据

    五大存储模型关系模型.键值存储.文档存储.列式存储.图形数据 时间:2014-06-12 16:15来源:知行网www.zhixing123.cn 编辑:麦田守望者 昨天跟一同事讨论Sybase是不是 ...

  9. plsql 存储过程 批量提交_新一代的键值存储 KVell SOSP 2019

    『看看论文』是一系列分析计算机和软件工程领域论文的文章,我们在这个系列的每一篇文章中都会阅读一篇来自 OSDI.SOSP 等顶会中的论文,这里不会事无巨细地介绍所有的细节,而是会筛选论文中的关键内容, ...

最新文章

  1. 《HTML5+CSS3网页设计入门必读》——1.7 使用FTP传输文件
  2. C++检查数字是否为2的幂的实现算法(附完整源码)
  3. 简单的聊聊,顺便招前端
  4. java中方法的参数传递机制
  5. 静态和动态include
  6. 上传图片在页面上显示
  7. 【Pytorch 】nn.functional.unfold()==>卷积操作中的提取kernel filter对应的滑动窗口
  8. 图的最小生成树(Prim算法)
  9. 网页上无缝滚动的实现
  10. Linux进阶之补充知识篇
  11. 阿里 Lindorm 数据库联手 Hightopo ,开启工业物联超融合存储模式
  12. PSD文件太大?高效压缩插件!
  13. 计算机内存不足提示栻框,【计算机】CIMS概论6.ppt
  14. 汽车照明全国产化电子元件推荐方案
  15. 企业实战之部署Solarwinds Network八部众
  16. 安装黑苹果提示未能安装_安装黑苹果最后一分钟提示失败求助
  17. DHU Matlab Experiment【2】作业记录_第三章、第四章
  18. 车内看车头正不正技巧_为什么在驾校成绩挺好,上路就傻眼了——停车入位技巧...
  19. java跑动d小人_Java课堂练习5
  20. 吊打java面试官之 Hashtable详细介绍(源码解析)和使用示例

热门文章

  1. 学习笔记(03):大数据工程师新手必学实战:手把手教你做一份大数据行业分析报告-课程介绍...
  2. 公式编写及各种K形态的描述
  3. Mac如何删除在磁盘空间中的可清除空间以及Mac开机时进度条卡100%的解决办法
  4. OneNote 无法打开该位置,它可能不存在或者您可能没有打开权限
  5. YFI创始人又双叒叕发项目了!社区都在问“Deriswap有币吗?sir”
  6. EasyExcel 下载demo(zip压缩包)
  7. 《炬丰科技-半导体工艺》 双异质结双极晶体管的加速振荡
  8. 微软Google暗战:霸主的商战智慧
  9. 虚拟机winxp纯净_虚拟机 win7系统ghostxp纯净版
  10. 利用区块链进行数据安全共享的思考