分布式系统的弱一致性模型协议和Dynamo DB的设计思想

对于容许数据副本不一致的协议, 我们很难用一个单一的维度来定义或者归类这些协议,对于这些协议来说,更关键的问题在于他们提供的一致性保证,抽象和API是不是对用户有用,尽管这些协议允许副本有某种程度的不一致,但是往往某些业务是可以容忍的。

那么,为什么弱一致性系统还没有一天下呢?

实际上是因为分布式系统主要是处理‘分布’带来的两个后果:

1.信息是以光速传播的,(意味着一定会有一些延迟)

2.互相独立的组件或节点会独立的发生失败

信息传播速度有限说明分布式系统中每个节点的对外界的感知都是独一无二的,在单个节点上计算很简单,因为所有事情都会按照一个可预测的全局全序关系发生。在分布式系统上的计算很困难,因为没有一个全局的全序序列。

显然在分布式系统中实现顺序代价是很高的,特别是对于大型的互联网应用,这种应用往往必须保持时刻可用。强一致性往往不是分布式系统的特点, 强一致性系统的行为就像一个单独的系统,这种系统在网络分割的情况下往往会不可用。

更进一步说,强一致性对于每个操作而言,往往需要和系统中的多数节点进行通信,而且往往不只一次(一个来回),而是至少两次(例如2PC),这对于地理上分布的,但是又要提供足够好性能的系统来说是很困难的。

也许我们想要的是一个并不需要代价很高的协同,但是依然可以返回有用的响应给客户的系统。我们放弃数据唯一真实的条件,转而允许不同的数据副本之间有冲突,并通过某些手段来解决这些数据冲突,同时保证系统的效率和网络容错性。最终一致性表达了这样的理念:不同的节点之间的状态和数据可能会在某一时段不一致,但是最终他们会就某个值达成共识。

提供最终一致性的系统往往有两种不同的设计理念:

1. 概率性最终一致:这种系统能检测到互相冲突的写操作,但是不能保证最终的结果和顺序执行写操作序列的结果保持一致。也就是说,老的写操作的值可能会覆盖较新的写操作的值。在网络分割发生的时候可能会有些异常结果产生。

最近这些年最有影响的提供单一数据正确性的系统是Amazon的Dynamo, 该系统就采用了上述理念。

2. 绝对最终一致,这种系统保证系统的值和顺序执行的结果一致。也就是说,这种系统不会产生任何异常结果;你可以自由的增加某个服务的副本,这些副本之间可以以任何形式通信,他们获得数据更新的顺序可以是任意的,只要他们能够最终获得相同的信息他们就能对最终结果达成一致。

CRDT(复制收敛的数据类型) 是在有网络延迟,分割,或者消息乱序的情况下,仍然能保证数据收敛的数据类型。这些数据类型可以从理论上证明是收敛的,但是能够支持理论的实现很有限。

另一种相似的理论叫做CALM,基于逻辑单一性的一致性:如果我们能够证明某个系统从逻辑上说是单调一致的,那么在没有协同(通信)的情况下,系统任然是正确的。

协调系统执行顺序

如果一个系统不支持绝对数据一致性,那么它的行为会是什么样的呢,我们来看几个例子:

一个显然的特点是数据副本之间的不一致,这意味着系统没有严格定义的通信范式,各个数据副本可以互相隔离,而且同时也能够单独的接受写操作。

假设有三个数据副本,每个副本之间现在互相是隔离的(网路上分割),比如三个副本在三个不同的数据中心,他们之间网络不通。三个数据都是可用的,可以接受读写操作。

[Clients]  - > [A]

--- Partition ---

[Clients]  - > [B]

--- Partition ---

[Clients]  - > [C]

一段时间以后,网络恢复了,每个副本之间开始交换数据,他们都收到不同client端的update操作,所以需要进行冲突合并,我们希望所有的数据副本最终能一致,

[A] \

--> [merge]

[B] /    |

|

[C] ----[merge]---> result

另一种看待弱一致性的方式是,假设有一组client向两个数据副本发送消息,因为副本之间没有同步数据的协议,那么消息可能会议完全不同的顺序到达两个副本:

[Clients]  --> [A]  1, 2, 3

[Clients]  --> [B]  2, 3, 1

这就是为什么我们需要数据同步的协议,例如我们需要拼接一个字符串,分别有如下三个消息;

1: { operation: concat('Hello ') }

2: { operation: concat('World') }

3: { operation: concat('!') }

没有数据同步的情况下,A可能产生"Hello World!",但是B会产生"World!Hello"

A: concat(concat(concat('', 'Hello '), 'World'), '!') = 'Hello World!'

B: concat(concat(concat('', 'World'), '!'), 'Hello ') = 'World!Hello '

这当然是不正确的,我们希望所有副本之间的最终数据保持一致。 有了这两个例子之后,我们来看一下Dynamo,我们可以有一个基线,然后讨论一下构建弱一致

系统的几种方式,例如CRDT和CALM.

Amazon的Dynamo

Dynamo是最有名的弱一致的高可用系统。他的原理是很多其他系统基础,包括Linkedin的Voldermort,Cassandra和Riak.

Dynamo是一个最终一致,高可用的kv store. kv store像一个大的哈希表,client可以通过set(key,value)保存数据,并通过get方法获取数据。

一个Dynamo 集群包含N个对等节点,每个节点负责存储一组key和对应的value.

Dynamo优先保证可用性,(牺牲一致性),他并不保证单一数据一致。 数据副本之间可能会不一致,当读某个数据的时候,在返回给客户端之前有一个步骤会尝试消除多个副本之间的数据不一致。

对于Amazon来说,很多场景下,系统可用性比绝对一致性更重要,因为系统不工作可能会导致业务流失或者信誉受损。 如果某些数据不是特别重要,那么相对传统的RDMBS,弱一致系统可以提供更好的性能和可用性。

下面的图演示了一个写操作是如何route到一个节点,并同时又是怎么写到多个数据副本中的:

[ Client ]

|

( Mapping keys to nodes )

|

V

[ Node A ]

|    \

( Synchronous replication task: minimum durability )

|        \

[ Node B]  [ Node C ]

A

|

( Conflict detection; asynchronous replication task:

ensuring that partitioned / recovered nodes recover )

|

V

[ Node D]

现在我们再看一下如何进行冲突检测和异步数据复制。高可用性的目标要求必须有数据复制,有些复制目标节点可能会由于网络分割而暂时不可用,副本同步任务确保节点在恢复以后能快速更新到最新状态。

一致Hash

无论是读还是写,我们首先要做数据路由,那么一定会有key到某个Node的映射算法。Dynamo里使用一致哈希来做key到节点的映射,这个计算由client端完成,这种方式是的client不需要进行查询来获得目标节点, 这样做的好处是hash运算比rpc的成本要低。

部分仲裁

一旦我们知道key在哪里存储,我们就要持久化这个值,这是一个同步过过程,需要立即将值写入多个节点的原因是保持更高的持久化能力,例如防止刚好在写操作时某个节点失败了。

Dynamo基于一个quroum进行数据复制,但是Dynamo实现的是一个非严格的quroum复制。

一个严格的quroum系统有如下属性:任何两个quroum之间是有交集的。在实际接收一个写操作之前要求得到多数节点的确认,这样能保证对一个数据写操作的历史是全局一致的,

因为在两个多数quroum之间至少有个一个相交的节点(这个节点会记录正确的数据写操作序列)。Paxos协议基于这个规则。

但是部分Quroum就没有这个特性,这意味着写操作不需要多数node参与,那么对同一项数据,不同的节点子集可能有不同的数据版本。对于读写操作,用户可以自己选择需要多少节点参与:

1.用户可以选择W个节点参与写操作

2.用户可以选择R个节点参与读操作

一般建议R+W>N, (N为一个数据的所有副本个数),这意味着read和write至少会hit一个公共的节点,这样范围过期的值得可能会变小。一般的配置都是N=3,那么用户

可以选择:

R = 1, W = 3;

R = 2, W = 2 or

R = 3, W = 1

假如我们有如下配置:

1. R=1,W=N, :读的速度快,写的速度慢

2. R=N,W=1, :写的速度快,读的速度慢

3. R=N/2 W=N/2+1 二者兼顾

N很少会大于3,因为将数据冗余太多副本会带来很高的成本。下面是其他一些产品的R/W配置:

Riak N=3,R=2,W=2

Voldemort N=2 or 3  R=w=1

Cassandra N=3, R=1,W=1

另一个细节是当发出读写请求是,是不是所有N个节点都需要响应,或者说只有一个部分节点需要响应,

如果给所有数据副本节点发请求,那么响应更快,因为他只需要等待最快的R/W个节点返回即可。如果是

仅仅发送给最小个数的R/W个节点那么需要等待所有节点的返回,但是网络发送的消息数量会小一些。

如果读写的Quroum发生重叠,也就是R+W>N的时候,这种情况下的一致性是否就是强一致性?

答案是否定的。 如果一个系统的R+W>N,那么会检测到副本之间的数据冲突,因为任何读写都至少会在一个节点上都发生:

1    2  N/2+1    N/2+2    N

[...] [R]  [R + W]  [W]    [...]

这样就保证了一个写操作的结果会被后面的读操作获取。但是,这个结论仅仅在N个节点不发生变化的情况下是正确的。所以Dynamo并不能算是强一致因为他的集群节点可能会动态变化。(在某些节点失效时)

Dynamo的设计目标是总是保持可写。如果负责某些key结合的服务器宕机了,那么它加入一个新的server到集群中来处理请求,这就意味着quorum数量的server可能不会有交集,甚至R=W=N 这样的配置都不行,因为当quroum数量为N时,所有N台server可能都会因为failure发生改变。当网络分割发生时,如果有足够数量的server不能联网,Dynamo就会自动增加一些server节点,这些节点没有数据但是可以访问。

另外Dynamo处理网络分割的方式和强一致系统的方式也不一样:在两个分割区都可以进行写操作,这以为这至少在一个时间段内系统内的同一数据可能有不同的值,所以R+W>N不等于强一致,这里的强一致仅仅是一个概率的概念。

冲突检测和写修复

容许数据副本有冲突的系统必须有一套最终能消除冲突的方法,一种方式在读数据的时候消除冲突。一般来说这种方法是记录数据改动的因果序列,客户端需要有数据的元数据,然后根据元数据来消除冲突,在写数据的时候系统要返回数据的元数据(因果序列)

之前已经指出了一个实现方法,就是使用向量时钟,一开始Dynamo就是使用向量时钟来消除冲突的。但是也还有其他的方法,有些系统基于元数据实现了其他的方法:

1.没有元数据的方法。系统并不跟踪记录元数据,就直接返回得到的数值,这种系统对并发写无能为力,仅仅就是简单的最后写胜出,(Last-WRITE-WIN),如果有两个并发的写,那么最慢的值会是最终的值。

2.使用时间戳。系统保留有较高时间戳值得写操作的结果。但是,如果时间不同步可能会发生意想不到的情况,可能叫老的写操作发生在时钟有问题的节点上,那么它可能最终会被保留。Cassandra使用了这种方式而不是向量时钟。

3.版本号。版本号避免了时间不同步的问题,但是表达因果顺序的最简单的方式任然是向量时钟。

4.向量时钟。向量时钟可以检测到冲突的写操作,然后进行读修复,但有时候必须让客户端进行选择,如果有数据冲突,但是又没有元数据为依据来解决冲突,那么只能将数据丢弃。

客户端会和R个节点通信进行读取,它拿到所有的响应,然后根据向量时钟丢弃老的数据,如果最后只有一个数据和向量时钟,那么就返回这个数据,如果有多个无法决定先后的向量时钟和数值,那么久返回所有的数值。这种情况下客户端需要根据具体的case选择一个值。

另外实际应用的向量时钟实现不能让时钟无限制的增加,需要有一个垃圾回收过期时钟的方式以避免消耗过多的存储。

数据副本同步 Gossip和Merkle Tree

Dynamo系统既然允许节点失效或者网络分割,那么它会有一个机制来处理重新加入集群的失效节点。当失效节点重新恢复后,需要进行副本复制,并定期执行副本同步。

Gossip是一个副本同步的方法,(概率性的),节点之间的通信方式是随机的,每隔t秒,每隔节点随机选择另外一个节点进行同步,实际上在别的问题上比如quroum写上面也可以用这种方式。

Gossip是扩展的,没有单点失效,但是不能够绝对保证数据同步的正确性。为了保证数据同步的效率,Dynamo使用了merkle tree的技术,他的思想是一个数据存储单元可以整体进行hash,或者每一半个key进行hash,等等。

总结,目前为止讨论了Dynamo的主要设计要点:

1.一致哈希来决定key的存储路由

2.读写是部分quorum

3.通过向量时钟做冲突检测和读修复

4.数据副本同步用Gossip协议

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容

  • 分布式系统面临的第一个问题就是数据分布,即将数据均匀地分布到多个存储节点。另外,为了保证可靠性和可用性,需要将数据...
    olostin阅读 4,425评论 2 26
  • 本博客在http://doc001.com/同步更新。 本文主要内容翻译自MySQL开发者Ulf Wendel在P...
    doc001阅读 1,612评论 0 14
  • 分布式键值模型可以看成是分布式表格模型的一种特例。然而,由于它只支持针对单个key-value的增、删、查、改操作...
    olostin阅读 2,444评论 0 6
  • 当我们在生产线上用一台服务器来提供数据服务的时候,我会遇到如下的两个问题: 1)一台服务器的性能不足以提供足够的能...
    isgiker阅读 600评论 0 5
  • 、 记录下来以便未来看看现在的自己! 刚刚把日剧(深夜食堂)第二季看完,从第一集我就被深深地吸引,看完只让我感慨人...
    御姐优阅读 424评论 4 2