设计场景
在分布式系统中,最常见的场景就是主备架构,一个主节点和多个普通节点。主节点负责对其他节点的协调和管理。当主节点宕机时,需要在普通节点中重新选举一个成为新的主节点。本文主要基于paxos算法来一步步讲解重新选举方案的实现。
自荐与无条件服从
发生主节点宕机时,存活着的普通节点都会“自荐”参与主节点的选举,并且将这一"提议"广播通知所有存活节点。节点每收到一个候选的"提议"后,都会无条件的同意该提议。
很明显,这样最终结果会导致各个节点中的master节点会各不相同。原因在于后到的“提议”会覆盖掉前面的“提议”,而对于每个节点最后接收的“提议”可能各不相同。
带有版本号的提议
为了解决各个节点最后接收的“选举提议”不同导致对主节点的选举无法达成一致,我们引进一个选举版本号,节点“自荐”前,会先在版本生成器中获取一个单调递增的版本号,在广播”提议“时带上版本号。节点收到"提议"后,会比较提议中的版本号a与节点此前收到的最大版本号b相比,如果a>b,则接受该”提议“,否则保持原本的master选择。
由于v5比其他版本号都大,所以最终结果是全部节点都统一server5成为master。
选举结果虽然统一了,但选举过程中经历了几次选举结果的覆盖,比如server1首先收到v2,将master设置为server2,后面再收到v5,将master覆盖为server5。
有些主从架构中,切换了master后,新master需要对自身进行初始化操作,同时,普通节点确定master后,需要向master请求同步一些元信息。当server收到一个更大版本的master时,都会重新进行信息同步,当server发现自己版本是当前最大时,也会进行一次master初始化。所以,这个方案还需要进一步完善。
提议前询问
上个方案的问题主要是由于高版本的master提议与已被采纳的低版本master提议不一样导致的。所以,我们可以在获取版本号后,先向其他各个存活节点发送请求。请求的目的有三个:
- 获取各个节点当前接受的master提议。
- 检查这次发送的版本号Vsend各个节点能不能接受(如果Vsend比节点此前收到的最大版本号Vrecvd小,则Vsend不会被节点接受)。
- 如果发送的版本号比节点接收过的版本号都大,会更新Vrecvd = Vsend。
各节点的响应汇总后按以下处理:
- 如果没有超过集群节点总数一半(全部节点,不止当前存活节点)的节点接受发送的版本号,直接中断此次master提议。
- 如果超过半数同意了Vsend:
- 节点中有已经接受的master提议(这种情况下,如果有多个节点都有接受的master提议,接受的提议都是一样的,可以思考下为什么),将该提议当成此次版本的master提议。
- 各节点此前都没有任何master提议,将采取“自荐”的方式,把当前server当成此次版本的master提议。
确定了这次版本的master提议后,再向各个节点广播提议内容。节点收到提议后,会再次比较Vrecvd与提议中的版本号,如果提议中的版本号比较大,接受此次提议,否则保持原本的master选择。
我们通过下面时序图来看看当两个节点同时进行提议时,会如何处理。假设server的版本号为v1,server2的版本号为v2,v1 < v2。
v1的提议,虽然在"询问"是否接受v1版本时,4个节点都给予了肯定,但最终只有被server1接受了,因为其他节点在收到v1的提议时,当前的版本Vrecvd均为v2,所以都拒绝了v1的提议。
v2的提议,当”询问"server1的时候,此时server1上已经有master提议了,所以v2的提议内容与v1的提议相同,都是server1。保证了高版本的提议与已被采纳的低版本master提议一致。