分布式基础- 谁来当老大
前言
上次讨论到谁当老大的问题,介绍了 Bully 选主算法,比较简单,直接选 ID 最大或最小的作为 leader。这样粗暴的选举算法,没有考虑到数据的新旧,所以可能更容易造成数据丢失的问题。
单纯的论资排辈不一定是最合适的,老人可能没新人能力强,所以又发明了其他的分布式选举算法。本次介绍的是 ZAB 算法,就是更民主的,选举更新的数据的节点作为 leader。ZAB 算法全称:Zookeeper Atomic Broadcast ,用一句话解释 ZAB 协议是保障操作顺序性的,基于主备模式的原子广播协议。
Zookeeper 作为分布式应用服务器,由于高性能和稳定性,和可靠性得到了广泛的应用。 它由 Leader,Follower 和 Observer 三种身份成员组成。
Leader : 作为主节点,负责处理写请求,客户端发送的写请求只能由 Leader 来处理,如果发送给了 Follower,则需要转发给 Leader,Leader 广播方式再把数据同步给 Follower。 Follower: 作为备份节点,负责数据的读请求,所以读的能力不够话,可以通过增加 Follower 个数来提升整体的读能力。 Observer: 观察者节点,不参与投票,具备读写能力,大规模集群情况下,比如 solr 集群,极度依赖 Zookeeper,如果通过增加 Follower 来提升读的能力的话,当时由于节点增加了,所以会造成选举比较忙,影响写操作性能,所以这时候增加观察者节点比较好。
# 可以查看zkserver的服务器的身份
./zkServer.sh status

一 如何选举
ZAB 支持三种成员身份,有四种状态,三种状态分别代表三种身份。
FOLLOWING: 跟随者状态,表示此节点是 Follower。 LEADING: 领导者状态,表示此节点为 Leader。 OBSERVING: 观察者状态,表示此节点是观察者。 除了三种状态,集群刚开始启动的时候或者领导者挂了之后,需要重新选举,标记选举的的状态是 LOOKING 状态。
选举采用投票形式,被选为 Leader 的不同节点进行相互 PK,获取大多数节点投票的 Leader 会当选。 投票的信息为<Leader, Epoch, LastZxid,node>
Leader :表示为投选择哪个节点作为 leader;Epoch:第多少届选举(有的叫任期,我觉得多少届更好),递增的,有这个是为了防止消息延迟造成的不同伦消息相互干扰;LastZxid: 表示被选举为 Leader 的节点的最大事务 ID;Node:即投票的节点。
举例说明: < 3, 1,101,B> 这里面表示节点 B 提议节点 ID:3 在第一届选举中作为 Leader,节点 ID:3 的最大事务 ID 为 101。 节点开始都会投给自己,然后将此投票信息发送给其他节点,节点收到投票信息后,会进行以下 PK:

PK 过程很简单,届大的获胜,比如人家已经在进行第二轮选举了,你进行第一轮选举,老的失效; 如果时同届选举,lastzxid 大的获胜,大的说明数据更新;如果数据一样新,那么大的 serverid 获胜。 当一个节点获得大部分节点的票之后,就可以当选为 leader 了。
节点 ID 可以通过在 zoo.cfg 里面看到或配置的 data 目录下有个 myid 文件,里面也是此节点的 id。
我们以最常见的三个节点组成的 Zookeeper 集群,来阐述 zk 如何进行选举。

说明: 初始时候节点 A 是 Leader,节点 B 和节点 C 是 Follower,只所以选用三个节点,是因为 ZK 在处理选举或同步数据时候,需要大多数确认,所以一般都是奇数个节点,只所以节点用三个,因为一个不可靠;五个这种选举慢,写性能会弱些,一般三个就够了。
1.1 发生故障 进入选举状态
这时候,节点 A 发生故障,或网络出现问题,节点 B 和节点 C 读取消息的时候超时,进入到 LOOKING 状态,发起领导者选举,如下:

1.2 投票
进入选举阶段后,每个节点都会生成投票信息,初始的时候每个节点都投自己一票,假设节点 B 的最大事务 id 为 100,serverid 为 2;节点 C 的最大事务 id 为 101,serverid 为 3,则投票的信息如下:

投票消息不光发给其他节点,也发给自己节点。如上图,节点 B 收到节点 C 发来的投票信息[3,1,101,C]之后,进行 pk,发现届都是 1,但是 C 的最大事务 ID:101 大于 B 的最大的事务 ID:100,所以 C 获胜。B 节点调整自己的投票信息:[3,1,101,B] 将这个消息发送给自己和 C 节点。节点 C 收到消息后进行 PK,C 获胜所以不用调整投票信息。
1.3 得到投票结果
按照刚才的逻辑,每个节点都有累加的投票信息,节点 ID 为 3 即节点 C 获得了大多数投票。

这样节点 C 就变更状态为 LEADING 状态,节点 B 就变更为 FOLLOWING 状态。zookeeper 就是通过这种方式完成了尽量选择数据最新的节点作为 Leader 节点。

二 ZAB 算法特点
ZAB 算法的性能不错,对系统没有什么特殊的要求。
消息风暴: 但是从通过上面的描述可以看出每次选票都要广播给每个节点,如果集群中有 n 个节点,则需要发送的消息量为 n*(n-1)个消息,容易出现消息风暴。 算法稳定性好: 当新节点加入集群或发生故障之后,会触发重新选举,但是不一定会切主,除非新选举的节点的最新的事务 id 大于现在 leader 的事务 id 和节点 ID 最大,且获得半数以上投票才会进行切主。
三 ZAB 选举之后
ZAB 选举完成之后,并不能立刻处理请求,还要经历集群发现阶段和数据同步阶段后才可以提供读写服务。 简单的聊下。
DISCOVERY 集群发现阶段 领导者在选举完成后,需要递增自己的选举届号(任期编号),然后通过和跟随者协商来完成领导者的确认。
比如刚才的 B 节点和 C 节点需要进入到 DISCOVERY 状态,具体如下图:

Follwer 主动向 Leader 发送 FollwINFO 消息,标注上一届的编号为 1. Leader 回复 Follwer LEADINFO 消息,包含本次新生成的选举届号 2,和本届的事务 id 为 0. Follwer 判断下如果收到 Leader 的选举届号更大,则是合法的 Leader,回复确认消息 ACKEPOH。 Leader 收到绝大多数的节点返回的 ACKEPOH 的确认后就可以正式当选 Leader,开展领导者的工作了。 这时 ZAB 的状态变更为数据同步状态 SYNCHRONIZATION。
SYNCHRONIZATION 数据同步阶段 刚才的集群发现阶段完成了领导者的确认,数据同步阶段完成的是数据的恢复和同步。 同步数据的过程如下: 
Leader 通过比较和 Follower 数据差异大小选择同步方式,将差异数据放入到队列中。 Leader 生成 New Leader 消息也发送到队列中。 Leader 将队列中的数据发送给 Follower。 Follower 完成数据的同步。 Follower 回复 ACK 消息给 Leader。 当 Leader 收到大部分节点的 ACK 消息后,发送 UPDATE 消息给 Follower,表示节点同步完成,进入到广播阶段 BROADCAST,正式开始处理消息了。
刚才说数据同步的方式有几种,不同情况选择不同的方式。Leader 在内存队列中保存了最近 500 个(默认)事务 ID 的最大值和最小值。
TRUNC:当 Follower 的最新的事务 ID 比 Leader 事务最大 ID,还大发送消息给 Follower 根据 Leader 的最大事务 ID 丢弃已经提交的事务; DIFF:当 Follower 的最新事务在 Leader 的两个事务 ID 之间,采用 DIFF 方式来完成数据同步,领导者将 Follower 没有的事务同步给 Follower。 SNAP:当 Follower 的最新事务小于 Leader 的最小事务的时候,采用 SNAP 的方式,同步 Leader 的本地快照给 Follower,Follower 直接覆盖本地内容。
其他说明下:
zookeeper 是实现的最终一致性,写入到 zookeeper 的消息,如果通过客户端向任意的节点发起读请求,不一定会读到最新的数据,如果需要读到最新的数据,可以先执行
sync命令后再读取节点数据。zookeeper 对于复制到大多数节点的事务不会丢失,但是对于只复制到少数节点的事务,有可能最终被保留,也有可能被截断,要看新选举的 Leader 是否包含这个事务。
四诗词欣赏
古诗十九首
[汉] [汉无名氏]
回车驾言迈,
悠悠涉长道。
四顾何茫茫,
东风摇百草。
所遇无故物,
焉得不速老。
盛衰各有时,
立身苦不早。
人生非金石,
岂能长寿考?
奄忽随物化,
荣名以为宝。
作者:明翼(XGogo)
-------------
公众号:TSparks
微信:shinelife
扫描关注我的微信公众号感谢
-------------


浙公网安备 33010602011771号