物理时钟逻辑时钟和向量时钟
一、背景
在分布式系统中,多个节点都有可能修改数据,由于网络等的原因,第一个修改的时间节点发起的写入请求并不一定能最先到达实际写入节点,为了数据的一致性,各个节点对同一数据产生的update/create的值要达到一致性,一般情况下就需要对各个节点的请求数据更新时间进行比对,将最后更新的数据更新到数据系统中(这里的数据系统可以是kv也可以是关系数据库)。
这类问题通常会出在常用的db或kv中间件中。

如上图中,纵轴分别表示A\B\C三个节点,横轴表示时间轴。A\B节点可以看做是请求写入数据的节点,C节点可以看做是最终写入数据的节点。
在3:00,A写入发起将X写入为0的请求,在3:01A告诉B把x写入为1,在3:02A告诉B的写入请求开始发起,B的写入请求在3:03时间到达写入节点C,写入成功,但是A在3:00的时候发起的写入请求到3:04才到C节点,这样A先发起的请求最后更新到db或者kv中,这样就出错了。
二、物理时钟
解决上面的问题最简单的方法就是物理时钟,假设每次请求中都包含时间戳,那么在写入节点查看到请求节点发起的时间戳发现后面在3:00的时间戳比较小,那么直接丢掉就好了。
这种方式可以简单称为物理时钟,即分布式系统中所有的节点都遵循同样的时间,即 “绝对” 时间,但是无论采用怎样的时间同步机制,不但要保证时间同步机制始终生效,还要知道,这样的绝对时间精度总有上限的。
上面的解释是抄别的参考的专业描述,实际上简单点描述就是,不同机器之间的时间是不同步的,即使再怎么同步也会有那么一个时间差值,比如说1ms,1us等等。虽然有很多种ntp时钟同步的方案,但是物理世界做到完整的时钟一丝丝(这里的一丝丝你可以认为完全逼近没有差值)不差是不可能的,总归是有差值在的,高并发情况下这样可不行。
Lamport 逻辑时钟
既然上面的有问题,然后Lamport提出了逻辑时钟,Lamport 也是搞paxos made simple文章的作者,大佬就是大佬。
然后就写了一篇论文,Time, Clocks, and the Ordering of Events in a Distributed System
阐述了这样一个观点:逻辑时钟和物理时钟最大的区别是,它不再关心绝对的 “时间” 是多少,转而关心事件之间的发生顺序,即它们的发生先后这一依赖关系。 所谓的逻辑时钟就是,每个节点都维护一个永远递增的版本号version,在发送的时候把时间改为version就行了。每当有事件产生和发送,版本号就自增 1;如果有写入请求到达,发现版本号比现在版本号小的直接丢掉就行了,反之并更新当前的版本号为当前版本号和请求所携带的版本号二者的最大值。
这个过程简单描述下:
最开始 A、B、C 三个节点的版本号都是 0; 在节点 A 产生了给 x 赋值 0 的事件,版本号更新为 1,这个事件被发送给 C; 接着版本号为 2 的事件传递给了 B,B 的版本号就更新为自己的当前版本号(为 0)和接收事件的版本号(为 2)二者的最大值 2,由此触发产生给 x 赋值 1 的事件并发送给 C,这时的版本号为 3; C 首先收到了版本号为 3 的事件,比当前版本号 0 要大,因此接纳事件,赋值 x 为 1,并更新当前版本号为 3; C 接着又收到了版本号为 1 的事件,比当前版本号 3 要小,因此丢弃该事件。
冲突问题
冲突过程如下:
节点 B 先发生某请求,版本号更新为 1,接着产生数据变更事件,赋值 x 为 0,版本号为 2,此事件需要同步到 C; 接着 A 上产生赋值 x 为 1 的请求,版本号为 1,同步到 C; B 发送过来的同步事件被 C 接纳,C 上版本号为 2,x 被赋值为 0; A 发送过来的同步请求被 C 丢弃,因为此时 C 的版本号已经是 2 了,大于 B 同步过来的版本号 1。
三、向量时钟
采用向量(Vector)时钟的方式时,前面提到的单纯版本号,就会变成一个版本号数组,上面记录了每一个节点当前的版本号:
上面的图示,每次版本号变更,都会对于这个版本号向量中相应的那一维自增。当 C 收到事件的时候,根据这个向量版本号的大小就可以做出应该接纳还是拒绝的决定了:
C 的初始版本号是 [A:0, B:0, C:0],它先收到了 x 赋值为 1 的事件,其版本号是 [A:2, B:1, C:0],这个版本号的每一维,都等于或大于初始版本号,因此,接纳该事件; 之后收到了 x 赋值为 0 的事件,其版本号是 [A:1, B:0, C:0],这个版本号的每一维,都小于或等于当时的版本号,因此,丢弃该事件。
相应地,我们再来看看前面那个 “冲突识别” 的例子:
如图所示,在 C 上比较这两个事件的时候,一个事件的向量是 [A:0, B:2, C:0],另一个则是 [A:1, B:0, C:0]。前者在向量 B 上更大,而后者则在向量 A 上更大,这样的矛盾就意味着冲突的发生。既然冲突能够被识别出来,我们就可以根据系统的预定义来选择冲突处理策略了。
最后,就如同任何软件上的解决方案都有两面性一样,向量时钟也不是完美的,比方说,在节点数量巨大的情况下,你可以想象这个版本号的向量维度会非常高,那么传递一个简单的信息,得携带大得多的版本信息这样的 overhead。
总结
后面的我就没再重写了,直接复制的。有兴趣的可以看看原文,底下第二个参考资料。
参考资料
Time, Clocks, and the Ordering of Events in a Distributed System https://www.raychase.net/5768




