(图片拍摄于澳大利亚-悉尼歌剧院旁,yishuihan摄)
在分布式系统中采用的是主从架构,也就是有一个主节点,若干个从节点。主节点可以执行读写操作,从节点只执行读操作。采用这种一主多从的方案,可以有效保证了数据的一致性(哪怕不是强一致性,也能有效保证最终一致性)。
在开源的Redis,MongoDB,Zookeeper等软件中也都采用了的选举算法,常用的选举算法有以下三种:Bully算法,Raft算法,ZAB算法。
1、Bully算法
Bully算法是一种简单直接的算法,因为它的选举原则是取ID的最大值作为主节点。在Bully算法中,有两种节点角色:普通节点和主节点。初始化的时候,所有节点都是普通节点。当主节点挂掉了,才会发起下一轮选举。
在选举中,有三种消息,分别是Election消息,用于发起选举。Alive消息,对Elelction消息的回复。Victory消息是选主成功后向其他节点发送的通知消息。选举过程如下:
集群中每个节点判断一下自己的节点ID是否为存活节点中最大,如果是则直接发送Victory消息,宣布自己为主节点。
如果自己不是最大的ID节点,则向比自己大的节点发送Election消息,等待其他节点回复。
在指定时间内没有收到其他节点Alive信息,则认为自己是主节点,然后向其他节点发送Victory消息,宣布自己是主节点。如果收到比自己ID大的节点Alive消息,则等待其他节点发送Victory消息。
如果收到比自己节点ID小的消息,则回复Alive消息,告诉对方重新选举。
该算法简单易于实现,但是当频繁有节点加入或退出,会造成频繁主节点切换。当应用在节点数量稳定的系统中,该算法的通用性可能还不错。此外,在MongoDB中故障迁移也采用了这个算法,它是采用最后操作的时间戳作为节点ID,在实际生产中,我们还可以使用启动的时间作为ID。 2、Raft算法 提起分布式系统,Raft算法是一个基础的算法。这种算法开创了投票选举的先例。核心思想就是“少数服从多数”,获得投票最多的节点就是主节点。在Raft算法中,节点有三种角色:
Leader:主节点,同一时刻只有一个主节点。
Candidate:候选节点,节点处于该状态才能选举为Leader节点。
Follower:Leader节点的跟随者,不能发起选举。
Raft选举流程如下:
初始化阶段,每个节点都是Follower状态。
进入选举阶段时,每个节点进入Candidate状态,并向其他节点发送选举Vote请求。
其他节点根据收到选举请求的顺序回复是否同意成为主节点,在每一轮选举中每个候选人只有一张选票。
若发起选举的节点超过一半选票,则成为主节点,状态转换为Leader,其他节点状态有Candidate转换为Follower,leader和follower之间有周期性的心跳包,用于检测主节点存活。
Leader节点的周期到了,会自动降为Follower,进入下一轮选主。当然了,当前Leader节点挂掉了也会由Follower发起新一轮选举。
目前集群管理容器Kubernetes采用的etcd组件就是采用Raft选举算法进行的。总之,Raft算法选举速度快,缺点是要求系统内每个节点都可以通信,因此通信容量大。 3、ZAB算法 ZAB算法英文叫Zookeeper Atomic Broadcast,是改进了Raft算法而用于zookeeper分布式协调服务的一种算法。这种算法引入了节点ID和数据ID作为参考进行选择主节点。节点ID或数据ID越大,表示数据越新。所以相比Raft算法,ZAB算法尽可能保证了数据的最新。 ZAB算法有三种角色:
- Laeder节点。即主节点
- Follower节点,也就是从节点。
- Obserer节点,观察节点,无投票权利。
选举过程中,节点拥有四种状态。分别是:
- Looking状态:即选举状态,当进入这种状态,节点会认为集群中没有主节点,自己会进入选举状态。
- Leading状态:表示已经选出了主节点,当前节点为Leader。
- Following状态:表示集群中已经选出了主节点,其他非主节点更新自己的状态为Following。
- Observer状态:当前节点处于该状态没有投票和选举权。
投票过程中,每个节点有一个唯一的三元组(server_id,server_zxid,epoch),其中Server_id代表当前节点的ID,server_zxid代表数据(事务id),该值越大表示数据越新。epoch为当前选举轮数。选举原则为server_zxid最大为主节点,若相同则server_id最大为主节点。 该算法性能很好,对系统无特殊要求,一般采用广播的方式发送消息。但是当集群内集群数据庞大,也容易出现网络风暴。此外,选举过程需要节点ID和数据事务ID,因此选举周期可能稍长。不过有新节点的加入也不一定触发新的选举。