Valentine 转载请标明出处。
Zookeeper的由来
1、各个节点的数据一致性;
2、怎么保证任务只在一个节点上执行;
3、如果service1挂了,其他节点如何发现并接替任务;
4、存在共享资源,互斥性、安全性。
Zookeeper的前世今生
从上面的案例可以看出,分布式系统的很多难题,都是由于缺少协调机制造成的,在分布式协调这块做得比较好的有Google的Chubby以及Apache的Zookeeper。
Google Chubby是一个分布式锁服务,通过Google Chubby来解决分布式协作、Master选举与分布式锁服务相关的问题。
Zookeeper也是类似,因为当时在雅虎内部的很多系统都需要一个系统来进行分布式协调,但是谷歌的Chubby是不开源的,所以后来雅虎基于Chubby的思想开发了Zookeeper,并捐赠给了Apache。
在上面这个架构下Zookeeper可以用来解决task执行问题,各个服务先去zookeeper上注册节点,然后获得权限以后再来访问task。
Zookeeper的设计猜想
Zookeeper主要是解决分布式环境下的服务协调问题而产生的,如果我们要去实现一个Zookeeper这样的中间件,我们需要做什么?
1、防止单点故障
如果要防止Zookeeper这个中间件的单点故障,那就要做集群,而且这个集群如果要满足高性能要求的话,还的是个高性能高可用的集群。高性能意味着这个集群能够分担客户端的请求流量,高可用意味着集群中的某一个节点宕机以后,不影响整个集群的数据和继续提供服务的可能性。
结论:所以这个中间件需要考虑到集群,而且这个集群还需要分摊客户端的请求流量。
2、接着上面那个结论再来思考,如果要满足这样的一个高性能集群,最直观的想法应该是每个节点都接收到请求,并且每个节点的数据都必须保持一致。实现各个节点的数据一致性,那就要一个leader节点负责协调和数据同步操作,如果在这样一个集群中没有leader节点,每个节点都可以接收到请求,那么这个集群的数据同步的复杂度是非常大的。
结论:所以这个集群中涉及到数据同步以及会存在leader节点。
3、继续思考,如何在这些节点中选举出leader节点,以及leader挂了以后,如何恢复?
结论:所以Zookeeper用了基于paxos理论所衍生出来的zab协议。
4、leader节点是如何和其他节点保证数据一致性,并且要求是强一致性的。在分布式系统中,每一个机器节点虽然都能够知道自己进行的事务操作过程是成功或失败。但是却无法直接获取其他分布式节点的操作结果。所以当一个事务操作涉及到跨节点的时候,就需要用到分布式事务,分布式事务的数据一致性协议有2PC协议和3PC协议。
基于这些猜想,基本上知道Zookeeper为什么要用到zab理论来做选举,为什么要做集群,为什么要用到分布式事务来实现数据一致性了。接下来逐步去剖析Zookeeper里面的这些内容。
关于2PC提交
(Two Phase Commitment Protocol)当一个事务操作需要跨越多个分布式节点的时候,为了保持事务的ACID特性,就需要引入一个“协调者”(TM(事务管理器))来统一调度所有分布式节点的执行逻辑,这些被调度的分布式节点被成为AP(应用程序)。TM负责调度AP的行为,并最终决定这些AP是否要把事务真正进行提交,因为整个事务是分为两个阶段提交,所以叫2PC。
阶段一:提交事务请求(投票)
1、事务询问
协调者向所有的参与者发送事务内容,询问是否可以执行事务提交操作,并开始等待各参与者的响应。
2、执行事务
各个参与者节点执行事务操作,并将Undo和Redo信息记录到事务日志中,尽量把提交过程中所有消耗时间的操作和准备都提前完成确保后面100%提交事务。
3、各个参与者向协调者反馈事务询问的响应
如果各个参与这成功执行了事务操作,那么就反馈给参与者yes的响应,表示事务可以执行,如果参与者没有成功执行事务,就反馈给协调者no的响应,如果参与者没有成功执行事务,就反馈给协调者no的响应,表示事务不可以执行,上面这个阶段有点类似协调者组织各个参与者对一次事务操作的投票表态过程,因此2PC协议的第一个阶段称为“投票阶段”,即各参与者投票表明是否需要继续执行接下去的事务提交操作。
阶段二:执行事务提交
在这个阶段,协调者会根据各参与者的反馈情况来决定最终是否可以进行事务提交操作,正常情况下包含两种可能:执行事务、中断事务。
Zookeeper的集群
在Zookeeper中,客户端会随机连接到Zookeeper集群中的一个节点,如果是读请求,就直接从当前节点中读取数据,如果是写请求,那么请求会被转发给leader提交事务,然后leader广播事务,只要有超过半数节点写入成功,那么请求就会被提交(类2PC事务)。
所有事务请求必须由一个全局唯一的服务器来协调处理,这个服务器就是leader服务器,其他的服务器就是follower。leader服务器把客户端的写请求转化成一个proposal(提议)并把这个proposal分发给集群中的所有follower服务器,之后leader服务器需要等待所有follower服务器的反馈,一旦超过半数的follower服务器进行了正确的反馈,那么leader就会再次向所有的follower服务器发送commit消息,要求各个follower节点对前面的一个proposal进行提交。
集群角色
leader角色
leader服务器是整个Zookeeper集群的核心,主要的工作任务有两项:
1、事务请求的唯一调度和处理者,保证集群事务处理的顺序性;
2、集群内部各服务器的调度者。
follower角色
follower角色的主要职责是:
1、处理客户端非事务请求,转发事务请求给leader服务器;
2、参与事务请求proposal的投票(需要板书以上服务器通过才能通知leader commit数据,leader发起的提案要follower投票);
3、参与leader选举的投票。
observer角色
observer是zookeeper3.3开始引入的一个全新的服务器角色,从字面理解,该角色充当了观察者的角色。观察zookeeper集群中的最新状态变化并将这些状态变化同步到observer服务器上。observer的工作原理与follower角色基本一致,而它和follower角色唯一的不同在于observer不参与任何形式的投票,包括事务请求proposal和leader选举。简单来说observer服务器只提供非事务请求服务,通常在于不影响集群事务处理能力的前提下提升集群非事务处理的能力。
集群组成
通常zookeeper是由2n+1台server组成,每个server都知道彼此的存在,对于2n+1台server,只要有n+1台(大多数)server可用,整个系统保持可用。一个zookeeper集群如果要对外提供可用的服务,那么集群中必须要有过半的机器正常工作并且彼此之间能够正常通信,基于这个特性,如果想搭建一个能够允许F台机器down掉的集群,那么就要部署2*F+1台服务器构成的zookeeper集群。因此3台机器构成的zookeeper集群,能够在挂掉一台依然能正常工作,一个5台机器集群的服务,能够对2台机器挂掉的情况下进行容灾,如果一个6台服务器构成的集群,同样只能挂掉2台机器。所以5台和6台在容灾能力上并没有明显优势,反而增加了网络通信负担,系统启动时,集群中的server会选举一台server为leader,其他的就作为follower(先不考虑observer)。
之所以要满足这样一个灯饰,是因为一个节点要成为集群中的leader,需要有超过集群中过半数的节点支持,这个涉及到leader选举算法,同时也涉及到事务请求的提交投票。
例子:
3台服务器,至少2台正常运行才行(3的半数为1.5,半数以上最少为2),正常运行可以允许1台服务器挂掉。
4台服务器,至少3台正常运行才行(4的半数为2,半数以上最少为3),正常运行可以允许1台服务器挂掉。
ZAB协议
ZAB(Zookeeper Atomic Broadcast)zookeeper原子广播协议,协议是为分布式协调服务zookeeper专门设计的一种支持崩溃恢复的原子广播协议。在zookeeper中,主要依赖ZAB协议来实现分布式数据一致性,基于该协议,zookeeper实现了一种主备模式的系统架构来保持集群中各个副本之间的数据一致性。
ZAB协议介绍
ZAB协议包含两种基本模式,分别是:
1、崩溃恢复;2、原子广播。
当整个集群在启动时,或者当leader节点出现网络中断、崩溃等情况,ZAB协议就会进入恢复模式并选举产生新的leader,当leader服务器选举出来后,并且集群中有过半的机器和该leader节点完成数据同步后(同步指的是数据同步,用来保证集群中过半的机器能够和leader服务器的数据状态保持一致),ZAB协议就会退出恢复模式。
当集群中已经有过半的follower节点完成了和leader同步以后,那么整个集群就进入了消息广播模式。这个时候,在leader节点正常工作时,启动一台新的服务器加入到集群,那这个服务器会直接进入数据恢复模式,和leader节点进行数据同步,同步完成后即可正常对外提供非事务请求的处理。
消息广播的实现原理
如果大家了解分布式事务的2pc、3pc协议的话,消息广播的过程实际上是一个简化版本的二阶段提交过程。
1、leader接收到消息请求后,将消息赋予一个全局唯一的64位自增id,叫zxid,通过zxid的大小比较既可以实现因果有序的特征;
2、leader为每个follower准备了一个FIFO队列(通过TCP协议来实现,以实现了全局有序这一特点)将带有zxid的消息作为一个proposal分发给所有的follower;
3、当follower接收到proposal,先把proposal写到磁盘,写入成功之后再向leader回复一个ack;
4、当leader接收到合法数量(超过半数几点)的ack后,leader就会向这些follower发送commit命令,同时会在本地执行该消息;
5、当follower收到消息的commit命令以后,会提交该消息,leader的投票过程,不需要observer的ack,observer不参与投票过程,observer只同步leader的数据。
崩溃恢复(数据恢复)
ZAB协议的这个基于原子广播协议的消息广播过程,在正常情况下是没有任何问题的,但是一旦leader节点崩溃,或者由于网络问题导致leader服务器失去了过半的follower节点的联系(可能是leader节点和follower节点之间产生了网络分区,那么此时的leader不再是合法的leader了),那么就会进入到崩溃恢复模式。在ZAB协议中,为了保证程序的正确运行,整个恢复过程结束后需要选举出一个新的leader,为了使leader挂了后系统能正常工作,需要解决两个问题:
1、已经被处理的消息不能丢失
当leader收到合法数量follower的ack后,就向各个follower广播commit命令,同时也会在本地执行commit并向连接的客户端返回【成功】。但是如果在各个follower收到commit之前leader就挂了,导致剩下的服务器没有执行这条消息。
leader对事务消息发起commit操作,但是该消息在follower1上执行了,但是follower2还没有收到commit就已经挂了,而实际上客户端已经收到该事务消息处理成功的回执了,所以在zab协议下需要保证所有机器都要执行这个事务消息。
2、被丢弃的消息不能再次出现
当leader接收到消息请求生成proposal后就挂了,其他follower并没有收到此proposal,因此经过恢复模式重新选了leader后,这条消息是被跳过的。此时,之前挂了的leader重新启动并注册成了follower,他保留了被跳过消息的proposal状态,与整个系统的状态是不一致的,需要将其删除。
ZAB协议需要满足上面两种情况,就必须要设计一个leader选举算法,能够确保已经被leader提交的事务proposal能够提交、同时丢弃已经被跳过的事务proposal。
针对这个要求
1、如果leader选举算法能够保证新选举出来的leader服务器拥有集群中所有机器最高编号(ZXID最大)的事务proposal,那么就可以保证这个新选举出来的leader一定具有已经提交的提案。因为所有提案被commit之前必须有超过半数的follower ack,即必须有超过半数节点的服务器的事务日志上有该提案的proposal,因此只要有合法数量的节点正常工作就必然有一个节点保存了所有被commit消息的proposal状态。还有,zxid是64位,高32位是epoch编号,每经过一次leader选举产生一个新的leader,新的leader会将epoch号+1,低32位是消息计数器,每接收到一条消息,这个值+1,新leader选举后这个值重置为0。这样设计的好处在于老的leader挂了以后重启,它不会被选举为leader,因此此时它的zxid肯定小于当前新的leader。当老的leader作为follower接入新的leader后,新的leader会让它将所有拥有旧的epoch号的未被commit的proposal清除。
关于zxid
zxid,也就是事务id,为了保证事务的顺序一致性,zookeeper采用了递增的事务id(zxid)来标识事务,所有的提议(proposal)都在被提出的时候加上了zxid。实现中zxid是一个64位的数字,它高32位是epoch(ZAB协议通过epoch编号来区分leader周期变化的策略)用来标识leader关系是否改变,每次一个leader被选举出来,它都会有一个新的epoch=(原来的epoch+1),标识当前属于哪个leader的统治时期。低32位用于递增计数。
epoch:可以理解为当前集群所处的年代或者周期,每个leader就像皇帝,都有自己的年号,所以每次改朝换代,leader变更之后,都会在前一个年代的基础上加1。这样就算就得leader崩溃恢复后,也没有人听他的了,因为follower只听从当前leader的命令。
epoch的变化可以做一个简单的实验:
1、启动一个zookeeper集群;
2、在/tmp/zookeeper/VERSION-2路径下会看到一个currentEpoch文件,文件中显示的是当前的epoch;
3、把leader节点停机,这个时候在看currentEpoch会有变化,随着每次选举新的leader,epoch都会发生变化。
leader选举
leader选举会分两个过程
启动的时候leader选举、leader崩溃的时候的选举
服务器启动时的leader选举
每个节点启动的时候状态都是LOOKING,处于观望状态,接下来就开始进行选主流程。
进行leader选举,至少需要两台机器,我们选取3台机器的服务器集群为例。在集群初始化阶段,当有一台服务器server1启动时,它本身是无法进行和完成leader选举,当第二胎服务器server2启动时,这个时候机器可以互相通信,每台机器都试图找到leader,于是进入leader选举过程,选举过程如下:
1、每个server发出一个投票,由于是初始情况,server1和server2都会将自己作为leader服务器来进行投票,每次投票都会包含所推举的服务器的myid和zxid、epoch,使用(myid、zxid、epoch)来表示,此时server1的投票为(1,0),server2的投票为(2,0),然后各自将这个投票发给集群中其他机器。
2、接受来自各个服务器的投票,集群的每个服务器收到投票后,首先判断该投票的有效性,如检查是否是本轮投票(epoch)、是否来自LOOKING状态的服务器。
3、处理投票,针对每一个投票,服务器都需要将别人的投票和自己的投票进行PK,PK规则如下:
i、优先检查zxid,zxid比较大的服务器优先作为leader;
ii、如果zxid相同,那么就比较myid,myid较大的服务器作为leader服务器。对于server1而言,它的投票是(1,0),接受server2的投票为(2,0),首先会比较两者的zxid,均为0,在比较myid,此时server2的myid最大,于是更新自己的投票为(2,0),然后重新投票,对于server2而言,它不需要更新自己的投票,只是再次向集群中所有机器发出上一次投票信息即可。
4、统计投票,每次投票后,服务器都会统计投票信息,判断是否已经有过半机器接受相同的投票信息,对于server1、server2而言,都统计出集群中已经有两台机器接受了(2,0)的投票信息,此时便认为已经选出了leader。
5、改变服务器状态,一旦确定了leader,每个服务器就会更新自己的状态,如果是follower,那么就变更为FOLLOWING,如果是leader就变更为LEADING。
运行过程中的leader选举
当集群中的leader服务器出现宕机或者不可用的情况时,那么整个集群将无法对外提供服务,而是进入新一轮的leader选举,服务器运行期间的leader选举和启动时期的leader选举基本过程是一致的。
1、变更状态,leader挂后,余下的非observer服务器都会将自己的服务器状态变更为LOOKING,然后开始进入leader选举过程。
2、每个server会发出一个投票,在运行期间,每个服务器上的zxid可能不同,此时假定server1的zxid为123,server3的zxid为122,在第一轮投票中,server1和server3都会投自己,产生投票(1,123),(3,122),然后各自将投票发送给集群中所有机器,接收来自各个服务器的投票,与启动时过程相同。
3、处理投票,与启动时过程相同,此时,server1将会成为leader。
4、统计投票,与启动时过程相同。
5、改变服务器的状态,与启动时过程相同。
学习来源https://www.gupaoedu.com/