Vector Clock/Version Clock

physical clock

机器上的物理时钟,不一样的机器在同一个时间点取到的physical clock不同,之间会存在必定的偏差,NTP能够用来控制这个偏差,机器之间的时钟偏差能够控制在几十ms之内。两个事件a和b,a在机器M1上physical clock为12点5分0秒6ms发生,b在机器M2上physical clock为12点5分0秒7ms发生,这并不表明a发生在b以前,由于两个机器上取到的physical clock和真实时间(这个时间就是国际标准时间UTC,能够经过原子钟,internet,卫星得到)之间都有偏差。好比机器M1的physical clock比真实时间慢10ms,那么事件a其实是在真实时间12点5分0秒16ms发生的,机器M2的physical clock比真实事件慢5ms,那么事件b的其实是在真实时间12点5分0秒12ms发生的,显然,事件a发生在事件b以后。算法

Lamport's Logical Clock

单机系统容易给发生的全部事件定义一个全局顺序(total order),可是分布式系统没有全局时钟,很难给全部事件定义一个全局顺序。因此,Lamport定义了一种偏序关系,happens-before.记做 ->bash

a->b意味着全部的进程都agree事件a发生在事件b以前。服务器

在三种状况下,能够很容易的获得这个关系:网络

  1. 若是事件a和事件b是同一个进程中的而且事件a发生在事件b前面,那么a->bapp

  2. 若是进程A发送一条消息m给进程B,a表明进程A发送消息m的事件,b表明进程B接收消息m的事件,那么a->b(因为消息的传递须要时间)
  3. ->知足传递性,若是a->b AND b->c => a->c异步

Lamport's Logical Clock算法以下:分布式

  1. 每一个机器本地维护一个logical clock LCi
  2. 每一个机器本地每发生一个事件设置LCi = LCi + 1,而且把结果做为这个事件的logical clock。
  3. 当机器i给机器j发送消息m时,把LCi存在消息里。
  4. 当机器j收到消息m时候,LCj = max(LCj, m timestamp)+1,结果值做为收到消息m这个事件的时间戳。

这个算法可以保证a->b,那么a事件的logical clock比b事件的logical clock小。反过来,经过只比较两个事件的logical clock不能获得a和b的前后。wordpress

Vector Clock

每一个机器维护一个向量VC,也就是Vector Clock,这个向量VC有以下属性:post

  1. VCi[i] 是到目前为止机器i上发生的事件的个数设计

  2. VCi[k] 是机器i知道的机器k发生的事件的个数(即机器i对机器j的知识)

每一个机器都有一个向量(Vector),每一个向量中的元素都是一个logical clock,因此取名为Vector Clock。

经过以下算法更新Vector Clock

  1. 机器i本地发生一个事件时将VCi[i]加1
  2. 机器i给机器j发送消息m时,将整个VCi存在消息内
  3. 机器j收到消息m时,VCj[k]=max(VCj[k],VCi[k]),同时,VCj[j]+1

能够看出,Vector Clock是一种maintain因果关系(causality)的一种手段,Vector Clock在机器之间传递达到给对方传递本身已有的关于其余机器知识的目的。

Dynamo为何须要Vector Clock(其实是Version Clock)

Dynamo是一个分布式Key/Value存储系统,这个Value能够是一行,包含多个列, 为了容错,每一个Key/Value保存多副本,一般在不一样的机器上,通常是3,后面以3为例。对外是一个最终一致性系统,即客户端A写入一个值返回成功后,在必定的时间内另一个客户端可能读不到最新的值。一般,成功写入两个副本成功即返回给客户端成功,同时请求会异步的同步到第三个副本。然而,高可用是Dynamo的主要设计目标之一,即便在出现网络分区或者机器宕机时依然可读可写。

假设Key K有三个副本k1,k2,k3分别在M1,M2,M3上。

正常状况

M1处理写请求,M1将请求发往M2,M3,只要有一个返回,即返回客户端成功。

网络分区

若是M1和M2/M3之间网络都不通,k1被更新(持续高可用,依然给客户端返回成功),随后,其余节点(集群中任意一个节点均可以接客户端请求,而且将请求路由到正确的节点上)路由了写请求给M2(假设其余节点和M1/M3之间网络不通),k2被更新。这时,k1和k2数据不同,最后网络恢复,三个副本进行同步时,应该保留哪一个版本?若是只保留k2,即采用last write win机制,那么同步后,第一个客户端会发现它写的数据丢了。

这个时候就须要Vector Clock,更确切的说是Version Clock。

为了处理这种场景,Dynamo使用Version Clock来捕获同一个Object的不一样版本之间的causality。每一个Object的每一个版本会有一个相关联的Version Clock, 形如[(serverA,counter),(serverB,counter),...], 经过检查同一个Object不一样版本的Version Clock,能够决定是否能够彻底丢弃一个版本,仅保留另一个版本,仍是须要将两个版本进行merge。若是Object的版本A的VCA包含的每项(server, counter)在版本B的VCB中都有对应项,而且counter小于等于版本B中对应项的counter(记做VCB descends VCA),那么这个Object的版本A能够被丢弃,不然须要对两个版本进行merge。

回到刚才的例子,k1被更新,Version Clock(注:此处假设k1/k2/k3三个副本以前如出一辙,那么就能够省略以前的Version Clock)为[(M1,1)],k2被更新,Version Clock为[(M2,1)],随后k1/k2网络通了,他们经过比较两个Version Clock发现两个Version Clock存在冲突,不是descends的关系,那么就两个版本都保留,当客户端来读Key K的时候,两个版本的数据和对应的VC都返回给客户端,由客户端进行冲突合并,客户端进行冲突合并后写入Key K的时候,带着合并后的VC[(M1, 1), [M2, 1]]发到M1/M2,覆盖服务器版本,冲突解决。

能够看出,Vector Clock最初是为了给分布式系统的事件定序发明的,本质上是一种捕获causality的手段,只是他们捕获的是事件的关系。而Version Clock是捕获同一个数据的不一样版本之间的causality.

Riak这个系统也使用了Vector Clock来作冲突合并,对Vector Clock的用法可谓比较深刻,具体能够看最后两篇参考资料。

参考资料

Dynamo

Scalable and Accurate Causality Tracking
for Eventually Consistent Stores

version-vectors-are-not-vector-clocks

Causality Is Expensive

Vector Clocks Revisited

vector-clocks-revisited-part-2-dotted-version-vectors

相关文章
相关标签/搜索