概述
所谓共识机制,从字面意思上就是多个节点对于某一事件的结果达成一致,比如第一个人提出“Computer”的中文译名叫“电脑”,其他人觉得这个翻译很棒,电子的大脑嘛,然后大家都这样想,也没有因为物理上的距离导致这个名称丢失,那么我们所有人就对此达成了共识,认为它就叫做“电脑”。
我们把最终达成对某个东西的命名,称之为“一致”,而在达成一致的过程称之为“共识机制”。而在这个过程中,如果我们能够保证整体状态转变的顺序是一致的,那么一定能够达成最终对“命名”的一致。而分布式场景最大的问题在于网络的不稳定性,那么还要考虑当对“命名”产生分歧的时候以“少数服从多数”为规则去保证一致。
以上是大概对共识机制这一概念的简述,而在共识机制这一领域最主要的算法就是Paxos算法,其中在后续中Basic Paxos指的是Paxos算法最初的基础版本,而Multi Paxos是在Basic Paxos基础上改进的变种。除此之外可以言明的是,几乎所有的共识算法都是Paxos算法的退化版本。
Basic Paxos
首先,Basic Paxos算法主要将分布式系统中的节点分为三类:
- 提案节点 Proposer,提出要进行某个操作的节点
- 决策节点 Acceptor,对提案节点发出的提案判定是否同意,如果决策节点同意某个提案的数量占到多数,即对这个提案达成共识,最终达成一致,不可更改。
- 记录节点 Learner,不参与提案和决策,主要从提案节点和决策节点中学习已经达成共识的提案,比如新上线的节点或者从网络故障恢复的节点。
Basic Paxos算法的阶段主要分为两个:“准备”和“批准”。而必须说明的是Basic Paxos只是对单个值进行决议!!!一般只是用来作为学术理论研究,而不会将其纳入实战之中。
准备
其中“准备”主要是如果某个提案节点准备发起提案,那么首先要对所有决策节点进行广播一个许可申请,在其中附带全局唯一且递增的数字n作为提案ID,决策节点收到后将会给与提案节点两个承诺和一个应答。
两个承诺分别是:
- 承诺不会接受提案ID小于或等于n的Prepare请求
- 承诺不会再接受提案ID小于n的Accept请求
第一个承诺可以杜绝多个提案节点同时进行提案所产生的冲突问题,而第二个承诺可以保证新的提案不会被旧的提案所替代。
一个应答是指:
回复提案节点自己批准过的提案中ID最大的值和提案ID,如果没有批准过任何提案则返回空。如果该提案违反了之前设下的两个承诺,则不理会该提案,即不返回。
批准
当提案节点收到了过半数量决策节点的响应后即进入批准阶段。
而提案节点返回的内容有两种可能性:
- 全部节点都返回空值,即自己是首个发起提案的节点,那么自己可以自定义这个值,将自己选定的值和提案ID作为二元组(id,value)再次广播给全部决策节点(Accept请求)。
- 某一个节点返回了之前已经批准过的节点的值,那么无条件的从所有应答中找到其中最大的提案节点将对应的节点ID和值进行广播(Accept请求)。
当将Accept请求发送给各个决策节点后,各个决策节点会在不违背之前承诺的情况下进行接收对应的广播请求并发送应答(Accepted),否则就对此请求无视。而如果应答过半数,则代表达成共识,协商结束,并将形成的决议进行广播提供给记录节点进行学习。反之,协商失败,即本次提案失败。
Multi Paxos
从字面意思上看,Multi Paxos即为多个Paxos算法,它本身是作为对Basic Paxos的优化,那么它与Basic有什么区别呢?又解决了什么问题?
所以首先要理清Basic Paxos有什么缺点:
- 仅能对单个值的变化进行决议
- 每次提案都需要至少两次请求和应答,产生大量网络开销
- 极端情况下可能出现活锁问题
对于前两个缺点几乎是不言自明的,所以需要详细举例来证明第三点。
首先,假设现有:S1、S2...S5,
- S1发起1号提案的Prepare请求,记为1.1,此时S2、S3响应Promise,加上S1已过半数,Prepare请求通过
- 随后S5请求2号提案,记为2.5,此时S3、S4响应Promise,加上S5已过半数,Prepare请求通过
- S1根据1号提案,发送Accept请求,与此同时由于S3在2号提案中回应了Promise,所以S3由于承诺限制,拒绝了S1的Accept请求
- S1重新发起了3号提案,记为3.1,与此同时S3回应Promise,随后由于承诺限制,拒绝了S5的Accept请求
极端情况...balabala,因此在这样的极端场景下可能导致长时间无法达成共识,因此无限循环形成活锁。
Multi Paxos试图去建立一个场景:既不破坏Paxos算法中“众节点平等”的原则,又能在提案节点中分清主次,以减少多个节点由于争抢提案资格造成的不必要消耗。那么当成功选举出主节点后,所有的提案都将有主节点进行发起,即其他的决策节点都需要将自己要发起的提案请求转发给主节点。因此,当只有主节点拥有提案权利的时候,意味着在当前的环境中属于一个无竞争的环境,此时并不需要准备阶段,可以直接进入批准节点将对应的值广播给其他节点。
而与此同时,有了唯一的主节点后也就不需要:提案、决策、记录,三种身份了,统统以“节点”来代替,节点只有“主”和“从”的区别。那么模型就把整个共识问题抽象成了具体的子问题,即:
- 如何进行选主
- 如何进行复制数据(主节点发送Accept请求给各个从节点,直接衍生为主节点将数据复制给从节点)
除此之外实际上还有比较抽象的“关于安全性的子问题”,而以这种类似的共识算法思路后面一般认为是“Raft算法”,还有一些相似的如ZooKeeper的ZAB算法,这些都可以认为是Multi Paxos算法的等价派生实现。
选主
Multi Paxos主要的改进在于,增加了一个“主节点”的概念。在这个算法中,所有的节点与其他节点通过心跳机制确认当前网络中是否存在一个主节点,一旦发现不存在主节点,则基于Basic Paxos算法发起“该节点是主节点”这样一个提案,如果后面通过了这个决议的话,那么这个节点就成为了新的主节点,并广播自己的任期(即前任任期的基础上的递增值)。
那么如果对应的主节点是由于陷入网络分区所以与其他节点失去了联系,当后面重新上线后会得知自己的任期小于目前主节点的任期,那么它就应该回退之前记录的数据(随后详细去说)。
复制
正常情况下客户端向主节点发送请求:“将某个值设置为X”,主节点首先会将X写入自己的变更日志,但不提交,接着在下一次心跳包中把这条变更广播给各个从节点,并要求它们将对应的变更写入自己的变更日志中,并响应以表明“确认收到” ,当从节点响应过半的时候提交自己的变更,并应答客户端,以及向从节点广播,提示可以提交对应的信息。自此整个复制流程结束。
网络分区
当出现网络分区的时候,部分节点失联,但只要仍然能够工作的节点的数量能够维持过半的数量,那么分布式系统依然能够正常工作。
假设现有,S1、S2..S5,S1是主节点且任期为1,由于网络故障导致S1、S2和S3、S4、S5失去联系。那么当S3对S1的心跳超时之后会发现自己与主节点已失去联系,那么S3就向自己能够联系的所有节点发送一条“竞选提案”,如果响应能够过半(加上自己能够>=3),那么它将成为新的主节点,并且任期为2。显然,S4和S5都可以进行投票,因此S3能够当选。
那么随后客户端如果连接到了S1和S2并发起请求,由于它们所属的分区对提案的响应并不能过半,因此不能达成任何变更。倘若客户端连接到了S3、S4、S5则可以正常的构成多数派提交变更。不过一般情况下,如果各个节点造成分区,出现问题的节点大概率是并不能与外界通信的,包括与客户端之间的连接也会断开。
随后,如果故障恢复,分区接触,五个节点重新可以恢复通信。S1和S3都向全体发送心跳包,但S1发现S3的任期比自己的大,S1和S2亦达成共识,即S3为现在的主节点,随后S1和S2回退所有在分区期间产生的变更,并通过S3的心跳包进行记录在分区过程中产生的变更。
最后,五个节点对现有数据达成一致性。
Gossip
一般情况下我们会把Paxos、Raft、ZAB等分布式算法认为其是“强一致性”的分布式共识机制协议,但实际上他们的语义其实是:“即使系统内部节点可以存在不一致的状态,但从系统外部来看,不一致的情况并不会被观察到,所以整体上看系统是强一致性的”。而与此同时,还有一类被称作“最终一致性”的分布式共识机制,这表明使用这类共识机制的系统在外部是能够观察到系统内部节点的不一致性,不过根据其语义,会保证最终会达到一致性。
而Gossip算法便是这样一个保证最终一致性的共识机制,它的主要特点就如其名,要同步的信息如同流言一样传播,一传十,十传百,像病毒一样扩散,以最终达成数据信息的统一。
而这个算法的流程也很简单,如下:
- 将信息以一个传播周期随机发送给与它相连接的k个节点
- 每个节点收到信息后,如果这个信息是它没有收到过的,则在下个周期内将此信息发送给除了之前发送这条信息的节点之外的随机k个节点
自此流程结束,尽管整个一致性的时间不可控,但最终理论上一定能够所有节点达成一致hhh。因此也可以通过流程发现,这样的信息是不具备有序性的,如果消息有前后依赖就不行了,最适合于那种对数据的正确性不敏感的系统,比如缓存。
最后
凤凰架构这本书本身就写的很言简意赅了,倒把博文内容显得像鹦鹉学舌