在分布式核心体系结构:一致性、事务、共识 这篇文章中,我们已经梳理过了分布式系统概念的思维导图,如下图:
图中的分布式事务相关内容,上篇文章 已经结束了学习。在进入分布式一致性和共识算法的学习之前,本篇文章我们通过逻辑时钟这样的引子,来看看一致性、共识和逻辑时钟之间的关系。通过对这些联系脉络的梳理,也为后续学习埋下思考主线。
为什么要关心事件时序?只要你用分布式系统中节点的视角来看,这点就很好理解。如下图:节点 先后 收到 set x = 1,和 set x = -1。这里的 先后 是真实世界中发生的先后关系吗?由于网络拥堵,故障等,还真不一定。如果节点贸然先设置 x = 1,再设置 x = -1,可能就会造成整个系统的不一致。
那么,我们可以选择让各节点发送消息时,附带上这个消息产生的时间戳,也就是携带本机的物理时间。这种做法当然也是不行的,物理时间不准,可能节点1消息比节点2产生的早,但是节点1时间戳却比节点2的大。
既然物理上不行,我们就寻求逻辑上的实现。这也就引出了逻辑时钟
。
逻辑时钟的本质,我认为就是:
定义一个基础共识:即n个事件,在到达不同节点时,这些节点根据某种规则来定义这些事件发生的先后顺序。
比如定义规则如下:
- 逻辑时钟是一个序号
- 节点发生任何事件,逻辑时钟+1
- 针对所有事件的排序,已逻辑时钟的大小排列
可见,通过这样的规则定义,就在各节点间达成了对事件顺序的基本共识。但是根据第一种规则,当事件并发时,我们无法准确定义并发事件的顺序。
注意图中的红线,正是因为有了消息通信,才产生了因果关系(站在整个系统角度来看),也就是 t1事件发生在t2事件前,t2事件是由t1事件引起的。
让我们在上述规则基础上,重新修补一下:
- 给每个节点定义一个不同的节点号,比如1,2
- 逻辑时钟表示为:[节点号,序号]
- 当序号相同时,以节点号大的为准
可见,第二种规则就实现了针对并发事件顺序的定义。
在第一种规则中,我们无法定义清楚并发事件顺序,术语是只能实现事件间的偏序关系。而针对第二种规则,则可以定义清楚并发关系,也就是可以定义清楚所有事件的顺序,称为事件间的全序关系。
但是,注意。这里虽然定义清楚了并发事件的顺序,但只是相当于在多个节点中达成共识,即对并发事件发生后,如何统一对并发事件排序的认知。比如a,b并发,统一认知为:a 在 b 之前发生,但是实际上可能是 b 在 a 之前发生,也即虽然实现了全序关系,但是无法区分a是否真的在b事件之前发生,无法区分是因果关系还是并发关系。但是,可以在各节点间达成统一共识。
说到这里,有必要提前谈下共识算法。共识算法可以看做两个大阶段:选举、选举之后的数据同步。
那么针对选举阶段,因为此时没有主节点,各节点编号也就无法确定。实际上就类似第一种规则,所以只能在偏序环境下,实现选举过程。针对选举后的数据同步,就类似第二种规则。因为此时各节点编号已经确定(虽然事实上不是节点编号,而是任期等其他形式,但是思想一样),所以是在全序环境下,进行数据同步。
而且又是主节点发起的数据同步,所以整个事件的通信过程可以看做是将事件的全序关系去广播到各个节点。
现在再来看导图中数据一致性(复制)的三种情况:主从一致、主主一致、无主一致。
其中,主从一致其实就和共识非常相似,等同于选举之后的数据同步。只不过最大的区别在于,主从一致情境下,主节点故障后,选主的过程是人为的,或者是人为制定的。而不像共识是重新由各个节点选举出来的。所以,主从一致可以看做是共识算法的特殊情况。
主主一致面临的问题就是并发写入后的数据同步。现在我们清楚了,这就是在偏序环境下,能不能实现全序?针对 能或者不能,该怎么处理并发写入?后面讲主主一致时再进行讨论。
其实可以把主主一致看做是无主一致的特殊情况,所以面对的问题是一样的,也是如何处理偏序环境下的并发冲突。但是无主复制和主主复制还是有根本的区别,后面再进行讨论。
在我们听说过的各种一致性模型中,最强的一致性就是线性一致性模型。也就是只要写入成功,就可保证不论什么时候读取,都能读到写入的数据,就和操作单机数据库一样。
对于共识算法来说,是可以实现线性化模型的。现在我们知道,共识算法的基础就是全序关系广播,但是全序关系广播的实现可以是异步广播,也可以是同步广播。但是不论怎样,都是在全序的情境下执行。简单来看,如果是异步广播,那么就无法实现线性化模型;如果是同步广播,就可以实现线性化模型。
tips:通过这样的分析,我们可以得出一个关键点,即如何学习各类共识算法,比如zookeeper等采用的算法,可从两点来思考:
- 在偏序环境下,选举是如何实现的
- 在全序环境下,提供哪种一致性模型?结合全序关系广播思想,如何实现该模型?
对于主从一致来说,假如只向主节点读取,或者向同步(不是异步)更新的从节点读取数据,那么也可以实现线性化模型。但是如果全部的从节点都同步更新,则非常影响性能。所以对于多个从节点的主从一致来说,属于部分可线性化。
再考虑主主一致,主主无法实现线性化模型。因为写操作是向其中一个主节点写入,但是多个客户端可以同时写入不同的主节点。这样子主节点间就会发生写入冲突,无法达成线性一致。
反观无主一致,和主主一致最大的区别是:写操作可能是同时向多个节点写入,读操作也可能从多个节点读取。对于线性一致性模型来说,可能无法实现,需要具体情境具体分析。
分析完线性化模型,我们再来看下因果一致性模型。上面提到的偏序概念,其实就是因果关系。因果一致性无法定义并发的先后顺序,也就无法达到全序效果,是比线型一致性弱的模型。
最后,最终一致性模型则是权衡了一致性和性能的模型。该模型会保证数据在一段时间后肯定会达到一致的效果。但是,考虑到主节点挂掉后的重新选举,再结合性能的考虑,该模型在某些场景下,连因果关系也无法保证,只能保证数据最终是一致的。
好了,本篇就学习到这里,通过逻辑时钟的联系,我们分析了一致性模型和三种复制情景以及共识之间的关联。下篇我们针对三种复制(主从、主主、无主)进行研究。
附录:推荐几篇开阔思维的文章: