本文是《如何学习分布式系统》中,关于时钟的相关介绍。
Lamport逻辑时钟的问题
Lamport逻辑时钟的第一个问题在于,它的全序关系不是唯一确定的。因为当逻辑时钟的值一致的时候,需要选择进程的全序关系R,而选择的R具有任意性,比如可以比较进程编号,也可以比较进程运行服务器的ip地址。这样意味着R的不同,会导致事件顺序的不同。
Lamport逻辑时钟的第二个问题在于,它的大小无法准确描述事件先后顺序。假设已知C(a) < C(b),也无法得到任何结论。例如下图中57时刻的事件,尽管其逻辑时间小于61,但是实际上它是在61事件之后发生的。
Lamport逻辑时钟的第三个问题在于,它丢失了一些依赖细节。例如下图中,当进程p2接收到消息,把时钟改成3的时候,其实这个3是由p1的2产生的,这一依赖细节就完全消失了。
Vector clock的实现
在Lamport逻辑时钟的基础上,人们又研究出了vector clock,vector clock本质上可以认为是Lamport逻辑时钟的数组,即Lamport逻辑时钟的向量,因此得名。
按照维基百科的说法,vector clock是在以下两篇文章中各自独立出现的:
- 《Timestamps in Message-Passing Systems That Preserve the Partial Ordering》
- 《Virtual Time and Global States of Distributed Systems》
Vector clock定义了一种happen before
关系,当且仅当两个两个事件在所有的可能性中都具有因果关系时,它们才具有这种关系。假设VC(a)和VC(b)分别是a和b事件的vector clock值,如果VC(a) < VC(b),那么a happen before
b。
假设一共n个进程,则每个进程i维护n维向量(长度为n的数组)VCi,VCi初始值都为0。其中VCi[j]代表进程i知道的进程j的最新时间。
关于vector clock的实现,很多资料众说纷纭,这里我们只参考上述两篇文章。
《Virtual Time and Global States of Distributed Systems》
VC的更新规则如下:
- 进程i执行任何事件之前,将VCi[i]的值+1
- 进程i发送消息m时,将m的时间戳设置为VCi
- 进程i接收消息m时,取得m的时间戳t,对VCi中的每个分量k,执行VCi[k]=max(VCi[k],t[k])
文中示意图如下
这个和维基百科上的图片效果是一样的
这种情况下,对于VC(a)和VC(b),如果同时满足:
- 对于每个分量x,都有VC(a)[x] <= VC(b)[x]
- 存在分量y,使得VC(a)[y] < VC(b)[y]
那么VC(a) < VC(b)。
《Timestamps in Message-Passing Systems That Preserve the Partial Ordering》中的异步模型
VC的更新规则有所不同,主要体现在发送消息的进程所在的分量也要递增。例如下面的文章示意图中,发送事件a发生在[1,0,0],接收事件m发生在[2,2,0],注意向量的第一个分量也加1了。
这种情况下,假设事件a和b分别发生在进程i和j中,如果VC(a)[i] < VC(b)[i],则说明a happen before
b
《Timestamps in Message-Passing Systems That Preserve the Partial Ordering》中的同步模型
这时网络通信变成同步模型了,发送事件的时间和接收事件的时间变成一样了,文中图示如下:
这种情况下,假设事件a和b分别发生在进程i和j中,如果VC(a)[i] <= VC(b)[i] 并且VC(a)[j] <= VC(b)[j],则说明a happen before
b
同步模型我还没有理解,欢迎知道的同学请补充一下
Vector clock的应用
Dynamo中使用vector clock来进行冲突解决,由于Dynamo是多master结构,可以在多个节点进行写入操作,因此需要解决写入的冲突问题。通过vector clock,系统可以找到冲突的数据,进而让用户进行修正。
在后续文章中有可能进一步介绍Dynamo的相关知识
Vector clock还有一种应用是获得系统的global state,合法的系统状态是系统的consistent cut,这个cut中的事件应该有并发上的要求。
更多相关内容,请参考系列文章《如何学习分布式系统》。