物理时钟
基本概念
计算机的系统时钟是一个频率精确和稳定的脉冲信号发生器,也称为"物理时钟"。
对于传统的单机数据库,物理时钟的大小可以用来准确描述事件在当前物理机发生的先后关系。
存在的问题
但在分布式系统中,各个节点分布在不同的物理位置,每个节点都有自己的物理时钟,即使采用 NTP 对各个设备的物理时钟进行同步,也有可能发生毫秒级的偏移。所以物理时钟不能作为分布式系统内在不同节点上并发执行的事务的排序依据。
逻辑时钟
基本概念
逻辑时钟的概念是Lamport于1978年在论文《Time, Clocks, and the Ordering of Events in a Distributed System》中提出,可以解决物理时钟无法解决的分布式系统中两个事件先后顺序的问题。
事件先后(因果)关系
逻辑时钟关键点在于定义事件的先后关系,我们把事件a发生在b之前定义为a→b。以下三种情况都满足a→b:
- a和b是同一个进程内的事件,a发生在b之前,则a→b
- a和b在不同的进程中,a是发送进程内的发送事件,b是同一消息接收进程内的接收事件,则a→b
- 如果a→b且b→c,则a→c
如果a和b没有先后关系,则两个事件是并发的,记作a||b
上图中A和B为两个不同的进程,每个点表示一个事件,黑点表示进程内的事件,蓝点表示进程的发送事件,红点表示进程的接收事件,从上图可以得出:
4. a → b → c → d
5. a → b → e
6. f → c → d
7. a || f
8. e || d
9. b || f
10. e || c
a → b可以表示两个事件的先后关系,也可以理解为两个事件的因果关系。
逻辑时钟算法
每个进程Pi保存一个本地逻辑时钟Ci,Ci(a)表示进程Pi发生事件a时的逻辑时钟值,Ci的更新算法为:
- 进程Pi每发生一次事件,Ci加1。
- 进程Pi给进程Pj发送消息,需要带上自己的本地逻辑时钟Ci。
- 进程Pj接收消息,更新Cj为 max (Ci, Cj) + 1。
按照此算法,上图逻辑时钟值可以表示为下面这样:
从上面可以得出结论,对于任意两个事件a和b,如果a → b,那么C(a) < C(b)。
但是如果C(a) < C(b),则不能反推得到a → b,好比以上的事件e和事件d,C(e)<C(d),但是e和d属于并发关系,即e||d。
全序事件集
上面所介绍的逻辑时钟算法构造的事件集合是一种偏序关系,即只有部分事件是可以比较的。可以通过增加另外一个条件(判断两个进程号的大小)来使整个事件变成一种全序关系,即所有事件都可以进行比较。
Pi进程的事件a和Pj进程的事件b如果满足下面两个关系中的任何一个,则称a⇒b。
- Ci (a) < Cj (b)
- Ci (a) = Cj (b) 并且 Pi < Pj。
上图假设A进程号为1,B进程号为2,事件的全序关系为:
a ⇒ f ⇒ b ⇒ e ⇒ c ⇒ d
以上即使物理时间上e发生于d之后,但由于两个事件并没有因果关系,所以排序时没有按照物理时间顺序也不会有问题。
存在的问题
以上述事件的全序关系为例,事件e和d的因果关系为e ⇒ d,但是实际上e可能是发生在d之后的,这说明逻辑时钟算法还不能完全解决这两个事件真正的先后关系。
混合逻辑时钟
基本概念
混合逻辑时钟(HLC)的概念来自于2014年的论文《Logical Physical Clocks and Consistent Snapshots in Globally Distributed Databases》,目的是为了解决逻辑时钟和物理时钟之间的差距,支持因果关系,同时又有物理时钟直观的特点。HLC是将物理时钟和逻辑时钟结合起来的一种算法。
HLC由两部分值组成,即物理时钟值和逻辑时钟值,可以表示为一个二元组 [l,c],其中l代表物理时钟值,c代表逻辑时钟值,例如[10,1]。
HLC的算法如下:
初始化:l.j = 0; c.j = 0
发送消息事件或者本地事件:
if pt.j <= l.j then c.j = c.j + 1
else c.j = 0; l.j = pt.j
接收m消息事件:
if l.j == m.j == pt.j then c.j = max(c.j, c.m) + 1
else if pt.j <= l.j and l.m <= l.j then c.j = c.j + 1
else if pt.j <= l.m and l.j <= l.m then c.j = c.m + 1; l.j = l.m
else c.j = 0; l.j = pt.j
具体又可以描述如下,
进程内HLC的更新方式
进程内发生事件时,检查当前获取的物理时间pt(now)和上一个HLC里的物理时钟部分l(prev)
- 如果pt(now)<=l(prev),将逻辑时钟+1
- 如果pt(now)>l(prev),将逻辑时钟清零
进程间HLC的更新方式
接收消息时,检查当前获取的物理时间pt(now)、上一次事件HLC里面的物理时间l(prev)、以及消息里面的HLC物理时间l(msg)
- 首先将HLC的物理时钟部分设置为pt(now)、l(prev)、l(msg)中的最大值
- 如果pt(now)<=l(prev)=l(msg),更新逻辑时钟为max(c(prev),c(msg))+1
- 如果pt(now)>max(l(prev),l(msg)),将逻辑时钟清零
- 如果pt(now)<=l(prev)<l(msg),将逻辑时钟设为c(msg)+1
- 如果pt(now)<=l(msg)<l(prev),将逻辑时钟设为c(prev)+1
上图是一个使用HLC算法的示例。