搞懂分布式技术4:ZAB协议概述与选主流程详解

搞懂分布式技术4:ZAB协议概述与选主流程详解

ZAB协议

  1. ZAB(Zookeeper Atomic Broadcast)协议是专门为zookeeper实现分布式协调功能而设计。zookeeper主要是根据ZAB协议是实现分布式系统数据一致性。
  2. zookeeper根据ZAB协议建立了主备模型完成zookeeper集群中数据的同步。这里所说的主备系统架构模型是指,在zookeeper集群中,只有一台leader负责处理外部客户端的事物请求(或写操作),然后leader服务器将客户端的写操作数据同步到所有的follower节点中。
  3. ZAB的协议核心是在整个zookeeper集群中只有一个节点即Leader将客户端的写操作转化为事物(或提议proposal)。Leader节点再数据写完之后,将向所有的follower节点发送数据广播请求(或数据复制),等待所有的follower节点反馈。在ZAB协议中,只要超过半数follower节点反馈OK,Leader节点就会向所有的follower服务器发送commit消息。即将leader节点上的数据同步到follower节点之上。 (先写在提交)
  4. ZAB协议中主要有两种模式,第一是消息广播模式;第二是崩溃恢复模式

消息广播模式

  1. 在zookeeper集群中数据副本的传递策略就是采用消息广播模式。zookeeper中数据副本的同步方式与二阶段提交相似但是却又不同。二阶段提交的要求协调者必须等到所有的参与者全部反馈ACK确认消息后,再发送commit消息。要求所有的参与者要么全部成功要么全部失败。二阶段提交会产生严重阻塞问题。
  2. ZAB协议中Leader等待follower的ACK反馈是指”只要半数以上的follower成功反馈即可,不需要收到全部follower反馈”
  3. 图中展示了消息广播的具体流程图
  4. zookeeper中消息广播的具体步骤如下: 4.1. 客户端发起一个写操作请求 4.2. Leader服务器将客户端的request请求转化为事物proposql提案,同时为每个proposal分配一个全局唯一的ID,即ZXID。 4.3. leader服务器与每个follower之间都有一个队列,leader将消息发送到该队列 4.4. follower机器从队列中取出消息处理完毕(写入本地事物日志中)后,向leader服务器发送ACK确认。 4.5. leader服务器收到半数以上的follower的ACK后,即认为可以发送commit 4.6. leader向所有的follower服务器发送commit消息。
  5. zookeeper采用ZAB协议的核心就是只要有一台服务器提交了proposal,就要确保所有的服务器最终都能正确提交proposal。这也是CAP/BASE最终实现一致性的一个体现。
  6. leader服务器与每个follower之间都有一个单独的队列进行收发消息,使用队列消息可以做到异步解耦。leader和follower之间只要往队列中发送了消息即可。如果使用同步方式容易引起阻塞。性能上要下降很多。

崩溃恢复

  1. zookeeper集群中为保证任何所有进程能够有序的顺序执行,只能是leader服务器接受写请求,即使是follower服务器接受到客户端的请求,也会转发到leader服务器进行处理。
  2. 如果leader服务器发生崩溃,则zab协议要求zookeeper集群进行崩溃恢复和leader服务器选举。
  3. ZAB协议崩溃恢复要求满足如下2个要求: 3.1. 确保已经被leader提交的proposal必须最终被所有的follower服务器提交。 3.2. 确保丢弃掉leader服务器中的没有被提交的proposal。
  4. 根据上述要求,新选举出来的leader不能包含未提交的proposal,即新选举的leader必须都是已经提交了的proposal的follower服务器节点。同时,新选举的leader节点中含有最高的ZXID。这样做的好处就是可以避免了leader服务器检查proposal的提交和丢弃工作
  5. leader服务器发生崩溃时分为如下场景: 5.1. leader在提出proposal时未提交之前崩溃,则经过崩溃恢复之后,新选举的leader一定不能是刚才的leader。因为这个leader存在未提交的proposal。 5.2 leader在发送commit消息之后,崩溃。即消息已经发送到队列中。经过崩溃恢复之后,参与选举的follower服务器(刚才崩溃的leader有可能已经恢复运行,也属于follower节点范畴)中有的节点已经是消费了队列中所有的commit消息。即该follower节点将会被选举为最新的leader。剩下动作就是数据同步过程。

数据同步

  1. 在zookeeper集群中新的leader选举成功之后,leader会将自身的提交的最大proposal的事物ZXID发送给其他的follower节点。follower节点会根据leader的消息进行回退或者是数据同步操作。最终目的要保证集群中所有节点的数据副本保持一致
  2. 数据同步完之后,zookeeper集群如何保证新选举的leader分配的ZXID是全局唯一呢?这个就要从ZXID的设计谈起。 2.1 ZXID是一个长度64位的数字,其中低32位是按照数字递增,即每次客户端发起一个proposal,低32位的数字简单加1。高32位是leader周期的epoch编号,至于这个编号如何产生(我也没有搞明白),每当选举出一个新的leader时,新的leader就从本地事物日志中取出ZXID,然后解析出高32位的epoch编号,进行加1,再将低32位的全部设置为0。这样就保证了每次新选举的leader后,保证了ZXID的唯一性而且是保证递增的。

ZAB协议原理

  1. ZAB协议要求每个leader都要经历三个阶段,即发现,同步,广播。
  2. 发现:即要求zookeeper集群必须选择出一个leader进程,同时leader会维护一个follower可用列表。用于将来客户端可以和这些follower中的节点进行通信。
  3. 同步:leader要负责将本身的数据与follower完成同步,做到多副本存储。这样也是体现了CAP中高可用和分区容错。follower将队列中未处理完的请求消费完成后,写入本地事物日志中。
  4. 广播:leader可以接受客户端新的proposal请求,将新的proposal请求广播给所有的follower。

Zookeeper设计目标

  1. zookeeper作为当今最流行的分布式系统应用协调框架,采用zab协议的最大目标就是建立一个高可用可扩展的分布式数据主备系统。即在任何时刻只要leader发生宕机,都能保证分布式系统数据的可靠性和最终一致性。
  2. 深刻理解ZAB协议,才能更好的理解zookeeper对于分布式系统建设的重要性。以及为什么采用zookeeper就能保证分布式系统中数据最终一致性,服务的高可用性。

Zab与Paxos Zab的作者认为Zab与paxos并不相同,之所以没有采用Paxos是因为Paxos保证不了全序顺序: Because multiple leaders can propose a value for a given instance two problems arise. First, proposals can conflict. Paxos uses ballots to detect and resolve conflicting proposals. Second, it is not enough to know that a given instance number has been committed, processes must also be able to fi gure out which value has been committed. Paxos算法的确是不关心请求之间的逻辑顺序,而只考虑数据之间的全序,但很少有人直接使用paxos算法,都会经过一定的简化、优化。

Paxos算法优化 Paxos算法在出现竞争的情况下,其收敛速度很慢,甚至可能出现活锁的情况,例如当有三个及三个以上的proposer在发送prepare请求后,很难有一个proposer收到半数以上的回复而不断地执行第一阶段的协议。因此,为了避免竞争,加快收敛的速度,在算法中引入了一个Leader这个角色,在正常情况下同时应该最多只能有一个参与者扮演Leader角色,而其它的参与者则扮演Acceptor的角色。 在这种优化算法中,只有Leader可以提出议案,从而避免了竞争使得算法能够快速地收敛而趋于一致;而为了保证Leader的健壮性,又引入了Leader选举,再考虑到同步的阶段,渐渐的你会发现对Paxos算法的简化和优化已经和上面介绍的ZAB协议很相似了。

总结 Google的粗粒度锁服务Chubby的设计开发者Burrows曾经说过:“所有一致性协议本质上要么是Paxos要么是其变体”。这句话还是有一定道理的,ZAB本质上就是Paxos的一种简化形式。

ZAB与FastLeaderElection选主算法流程详解

这篇主要分析leader的选主机制,zookeeper提供了三种方式:

  • LeaderElection
  • AuthFastLeaderElection
  • FastLeaderElection

默认的算法是FastLeaderElection,所以这篇主要分析它的选举机制。

选择机制中的概念

服务器ID

比如有三台服务器,编号分别是1,2,3。

编号越大在选择算法中的权重越大。

数据ID

服务器中存放的最大数据ID.

值越大说明数据越新,在选举算法中数据越新权重越大。

逻辑时钟

或者叫投票的次数,同一轮投票过程中的逻辑时钟值是相同的。每投完一次票这个数据就会增加,然后与接收到的其它服务器返回的投票信息中的数值相比,根据不同的值做出不同的判断。

选举状态

  • LOOKING,竞选状态。
  • FOLLOWING,随从状态,同步leader状态,参与投票。
  • OBSERVING,观察状态,同步leader状态,不参与投票。
  • LEADING,领导者状态。

选举消息内容

在投票完成后,需要将投票信息发送给集群中的所有服务器,它包含如下内容。

  • 服务器ID
  • 数据ID
  • 逻辑时钟
  • 选举状态

选举流程图

因为每个服务器都是独立的,在启动时均从初始状态开始参与选举,下面是简易流程图。

下面详细解释一下这个流程:

首先给出几个名词定义:

(1)Serverid:在配置server时,给定的服务器的标示id。

(2)Zxid:服务器在运行时产生的数据id,zxid越大,表示数据越新。

(3)Epoch:选举的轮数,即逻辑时钟。随着选举的轮数++

(4)Server状态:LOOKING,FOLLOWING,OBSERVING,LEADING

FastLeaderElection步骤:

一、 Server刚启动(宕机恢复或者刚启动)准备加入集群,此时读取自身的zxid等信息。

二、 所有Server加入集群时都会推荐自己为leader,然后将(leader id 、 zxid 、 epoch)作为广播信息,广播到集群中所有的服务器(Server)。然后等待集群中的服务器返回信息。

三、 收到集群中其他服务器返回的信息,此时要分为两类:该服务器处于looking状态,或者其他状态。

(1) 服务器处于looking状态

首先判断逻辑时钟 Epoch:

a) 如果接收到Epoch大于自己目前的逻辑时钟(说明自己所保存的逻辑时钟落伍了)。更新本机逻辑时钟Epoch,同时 Clear其他服务发送来的选举数据(这些数据已经OUT了)。然后判断是否需要更新当前自己的选举情况(一开始选择的leader id 是自己)

判断规则rules judging:保存的zxid最大值和leader Serverid来进行判断的。先看数据zxid,数据zxid大者胜出;其次再判断leader Serverid, leader Serverid大者胜出;然后再将自身最新的选举结果(也就是上面提到的三种数据(leader Serverid,Zxid,Epoch)广播给其他server)

b) 如果接收到的Epoch小于目前的逻辑时钟。说明对方处于一个比较OUT的选举轮数,这时只需要将自己的 (leader Serverid,Zxid,Epoch)发送给他即可。

c) 如果接收到的Epoch等于目前的逻辑时钟。再根据a)中的判断规则,将自身的最新选举结果广播给其他 server。

同时Server还要处理2种情况:

a) 如果Server接收到了其他所有服务器的选举信息,那么则根据这些选举信息确定自己的状态(Following,Leading),结束Looking,退出选举。

b) 即使没有收到所有服务器的选举信息,也可以判断一下根据以上过程之后最新的选举leader是不是得到了超过半数以上服务器的支持,如果是则尝试接受最新数据,倘若没有最新的数据到来,说明大家都已经默认了这个结果,同样也设置角色退出选举过程。

(2) 服务器处于其他状态(Following, Leading)

a) 如果逻辑时钟Epoch相同,将该数据保存到recvset,如果所接收服务器宣称自己是leader,那么将判断是不是有半数以上的服务器选举它,如果是则设置选举状态退出选举过程

b) 否则这是一条与当前逻辑时钟不符合的消息,那么说明在另一个选举过程中已经有了选举结果,于是将该选举结果加入到outofelection集合中,再根据outofelection来判断是否可以结束选举,如果可以也是保存逻辑时钟,设置选举状态,退出选举过程。

以上就是FAST选举过程。

Zookeeper具体的启动日志如下图所示:

以上就是我自己配置的Zookeeper选主日志,从一开始LOOKING,然后new election, my id = 1, proposedzxid=0x0 也就是选自己为Leader,之后广播选举并重复之前Fast选主算法,最终确定Leader。

源码分析

QuorumPeer

主要看这个类,只有LOOKING状态才会去执行选举算法。每个服务器在启动时都会选择自己做为领导,然后将投票信息发送出去,循环一直到选举出领导为止。

public void run() {        //.......?        try {            while (running) {                switch (getPeerState()) {                case LOOKING:                    if (Boolean.getBoolean("readonlymode.enabled")) {                        //...                        try {                           //投票给自己...                            setCurrentVote(makeLEStrategy().lookForLeader());                        } catch (Exception e) {                            //...                        } finally {                            //...                        }                    } else {                        try {                           //...                            setCurrentVote(makeLEStrategy().lookForLeader());                        } catch (Exception e) {                            //...                        }                                            }                    break;                case OBSERVING:                    //...                    break;                case FOLLOWING:                    //...                    break;                case LEADING:                    //...                    break;                }                            }        } finally {            //...        }    }

FastLeaderElection

它是zookeeper默认提供的选举算法,核心方法如下:具体的可以与本文上面的流程图对照。

public Vote lookForLeader() throws InterruptedException {        //...        try {            HashMap<Long, Vote> recvset = new HashMap<Long, Vote>();?            HashMap<Long, Vote> outofelection = new HashMap<Long, Vote>();?            int notTimeout = finalizeWait;?            synchronized(this){                //给自己投票                logicalclock.incrementAndGet();                updateProposal(getInitId(), getInitLastLoggedZxid(), getPeerEpoch());            }?            //将投票信息发送给集群中的每个服务器            sendNotifications();?            //循环,如果是竞选状态一直到选举出结果?            while ((self.getPeerState() == ServerState.LOOKING) &&                    (!stop)){                            Notification n = recvqueue.poll(notTimeout,                        TimeUnit.MILLISECONDS);?                //没有收到投票信息                if(n == null){                    if(manager.haveDelivered()){                        sendNotifications();                    } else {                        manager.connectAll();                    }?                    //...                }                 //收到投票信息                else if (self.getCurrentAndNextConfigVoters().contains(n.sid)) {                                        switch (n.state) {                    case LOOKING:                                               // 判断投票是否过时,如果过时就清除之前已经接收到的信息                                              if (n.electionEpoch > logicalclock.get()) {                            logicalclock.set(n.electionEpoch);                            recvset.clear();                            //更新投票信息                            if(totalOrderPredicate(n.leader, n.zxid, n.peerEpoch,                                    getInitId(), getInitLastLoggedZxid(), getPeerEpoch())) {                                updateProposal(n.leader, n.zxid, n.peerEpoch);                            } else {                                updateProposal(getInitId(),                                        getInitLastLoggedZxid(),                                        getPeerEpoch());                            }                            //发送投票信息                            sendNotifications();                        } else if (n.electionEpoch < logicalclock.get()) {                            //忽略                            break;                        } else if (totalOrderPredicate(n.leader, n.zxid, n.peerEpoch,                                proposedLeader, proposedZxid, proposedEpoch)) {                            //更新投票信息                            updateProposal(n.leader, n.zxid, n.peerEpoch);                            sendNotifications();                        }                     ?                        recvset.put(n.sid, new Vote(n.leader, n.zxid, n.electionEpoch, n.peerEpoch));                        //判断是否投票结束                        if (termPredicate(recvset,                                new Vote(proposedLeader, proposedZxid,                                        logicalclock.get(), proposedEpoch))) {?                            // Verify if there is any change in the proposed leader                            while((n = recvqueue.poll(finalizeWait,                                    TimeUnit.MILLISECONDS)) != null){                                if(totalOrderPredicate(n.leader, n.zxid, n.peerEpoch,                                        proposedLeader, proposedZxid, proposedEpoch)){                                    recvqueue.put(n);                                    break;                                }                            }?                                                      if (n == null) {                                self.setPeerState((proposedLeader == self.getId()) ?                                        ServerState.LEADING: learningState());?                                Vote endVote = new Vote(proposedLeader,                                        proposedZxid, proposedEpoch);                                leaveInstance(endVote);                                return endVote;                            }                        }                        break;                    case OBSERVING:                        //忽略                        break;                    case FOLLOWING:                    case LEADING:                        //如果是同一轮投票                        if(n.electionEpoch == logicalclock.get()){                            recvset.put(n.sid, new Vote(n.leader, n.zxid, n.electionEpoch, n.peerEpoch));                            //判断是否投票结束                            if(termPredicate(recvset, new Vote(n.leader,                                            n.zxid, n.electionEpoch, n.peerEpoch, n.state))                                            && checkLeader(outofelection, n.leader, n.electionEpoch)) {                                self.setPeerState((n.leader == self.getId()) ?                                        ServerState.LEADING: learningState());?                                Vote endVote = new Vote(n.leader, n.zxid, n.peerEpoch);                                leaveInstance(endVote);                                return endVote;                            }                        }?                        //记录投票已经完成                        outofelection.put(n.sid, new Vote(n.leader,                                 IGNOREVALUE, IGNOREVALUE, n.peerEpoch, n.state));                        if (termPredicate(outofelection, new Vote(n.leader,                                IGNOREVALUE, IGNOREVALUE, n.peerEpoch, n.state))                                && checkLeader(outofelection, n.leader, IGNOREVALUE)) {                            synchronized(this){                                logicalclock.set(n.electionEpoch);                                self.setPeerState((n.leader == self.getId()) ?                                        ServerState.LEADING: learningState());                            }                            Vote endVote = new Vote(n.leader, n.zxid, n.peerEpoch);                            leaveInstance(endVote);                            return endVote;                        }                        break;                    default:                        //忽略                        break;                    }                } else {                    LOG.warn("Ignoring notification from non-cluster member " + n.sid);                }            }            return null;        } finally {            //...        }    }

判断是否已经胜出

默认是采用投票数大于半数则胜出的逻辑。

选举流程简述

目前有5台服务器,每台服务器均没有数据,它们的编号分别是1,2,3,4,5,按编号依次启动,它们的选择举过程如下:

  • 服务器1启动,给自己投票,然后发投票信息,由于其它机器还没有启动所以它收不到反馈信息,服务器1的状态一直属于Looking。
  • 服务器2启动,给自己投票,同时与之前启动的服务器1交换结果,由于服务器2的编号大所以服务器2胜出,但此时投票数没有大于半数,所以两个服务器的状态依然是LOOKING。
  • 服务器3启动,给自己投票,同时与之前启动的服务器1,2交换信息,由于服务器3的编号最大所以服务器3胜出,此时投票数正好大于半数,所以服务器3成为领导者,服务器1,2成为小弟。
  • 服务器4启动,给自己投票,同时与之前启动的服务器1,2,3交换信息,尽管服务器4的编号大,但之前服务器3已经胜出,所以服务器4只能成为小弟。
  • 服务器5启动,后面的逻辑同服务器4成为小弟。

几种领导选举场景

集群启动领导选举

初始投票给自己 集群刚启动时,所有服务器的logicClock都为1,zxid都为0。

各服务器初始化后,都投票给自己,并将自己的一票存入自己的票箱,如下图所示。

在上图中,(1, 1, 0)第一位数代表投出该选票的服务器的logicClock(epoch),第二位数代表被推荐的服务器的myid(leaderserverId),第三位代表被推荐的服务器的最大的zxid。由于该步骤中所有选票都投给自己,所以第二位的myid即是自己的myid,第三位的zxid即是自己的zxid。

此时各自的票箱中只有自己投给自己的一票。

更新选票

服务器收到外部投票后,进行选票PK,相应更新自己的选票并广播出去,并将合适的选票存入自己的票箱,如下图所示。

服务器1收到服务器2的选票(1, 2, 0)和服务器3的选票(1 ,3, 0)后,由于所有的logicClock都相等,所有的zxid都相等,因此根据myid判断应该将自己的选票按照服务器3的选票更新为(1, 3, 0),并将自己的票箱全部清空,再将服务器3的选票与自己的选票存入自己的票箱,接着将自己更新后的选票广播出去。此时服务器1票箱内的选票为(1, 3),(3, 3)。

注意:(1,3)表示1号自己投票给了3,(3,3)表示根据收到的3号请求,3号投票的是给自己3,zxid不用存入信箱

同理,服务器2收到服务器3的选票后也将自己的选票更新为(1, 3, 0)并存入票箱然后广播。此时服务器2票箱内的选票为(2, 3),(3,3)。

服务器3根据上述规则,无须更新选票,自身的票箱内选票仍为(3, 3)。

服务器1与服务器2更新后的选票广播出去后,由于三个服务器最新选票都相同,最后三者的票箱内都包含三张投给服务器3的选票。

根据选票确定角色

根据上述选票,三个服务器一致认为此时服务器3应该是Leader。因此服务器1和2都进入FOLLOWING状态,而服务器3进入LEADING状态。之后Leader发起并维护与Follower间的心跳。

Follower重启

Follower重启投票给自己 Follower重启,或者发生网络分区后找不到Leader,会进入LOOKING状态并发起新的一轮投票。

发现已有Leader后成为Follower 服务器3收到服务器1的投票后,将自己的状态LEADING以及选票返回给服务器1。服务器2收到服务器1的投票后,将自己的状态FOLLOWING及选票返回给服务器1。此时服务器1知道服务器3是Leader,并且通过服务器2与服务器3的选票可以确定服务器3确实得到了超过半数的选票。因此服务器1进入FOLLOWING状态。

Leader重启

Follower发起新投票 Leader(服务器3)宕机后,Follower(服务器1和2)发现Leader不工作了,因此进入LOOKING状态并发起新的一轮投票,并且都将票投给自己。

广播更新选票 服务器1和2根据外部投票确定是否要更新自身的选票。这里有两种情况

  • 服务器1和2的zxid相同。例如在服务器3宕机前服务器1与2完全与之同步。此时选票的更新主要取决于myid的大小
  • 服务器1和2的zxid不同。在旧Leader宕机之前,其所主导的写操作,只需过半服务器确认即可,而不需所有服务器确认。换句话说,服务器1和2可能一个与旧Leader同步(即zxid与之相同)另一个不同步(即zxid比之小)。此时选票的更新主要取决于谁的zxid较大

在上图中,服务器1的zxid为11,而服务器2的zxid为10,因此服务器2将自身选票更新为(3, 1, 11),如下图所示。

选出新Leader 经过上一步选票更新后,服务器1与服务器2均将选票投给服务器1,因此服务器2成为Follower,而服务器1成为新的Leader并维护与服务器2的心跳。

旧Leader恢复后发起选举 旧的Leader恢复后,进入LOOKING状态并发起新一轮领导选举,并将选票投给自己。此时服务器1会将自己的LEADING状态及选票(3, 1, 11)返回给服务器3,而服务器2将自己的FOLLOWING状态及选票(3, 1, 11)返回给服务器3。如下图所示。

旧Leader成为Follower 服务器3了解到Leader为服务器1,且根据选票了解到服务器1确实得到过半服务器的选票,因此自己进入FOLLOWING状态。

一致性保证

ZAB协议保证了在Leader选举的过程中,已经被Commit的数据不会丢失,未被Commit的数据对客户端不可见。

Commit过的数据不丢失

自己总结:因为根据judge原则commit过的数据的ZXID肯定比没有提交的数据大,所以,肯定可以成为leader

Failover前状态 为更好演示Leader Failover过程,本例中共使用5个Zookeeper服务器。A作为Leader,共收到P1、P2、P3三条消息,并且Commit了1和2,且总体顺序为P1、P2、C1、P3、C2。根据顺序性原则,其它Follower收到的消息的顺序肯定与之相同。其中B与A完全同步,C收到P1、P2、C1,D收到P1、P2,E收到P1,如下图所示。

这里要注意

  • 由于A没有C3,意味着收到P3的服务器的总个数不会超过一半,也即包含A在内最多只有两台服务器收到P3。在这里A和B收到P3,其它服务器均未收到P3
  • 由于A已写入C1、C2,说明它已经Commit了P1、P2,因此整个集群有超过一半的服务器,即最少三个服务器收到P1、P2。在这里所有服务器都收到了P1,除E外其它服务器也都收到了P2

选出新Leader 旧Leader也即A宕机后,其它服务器根据上述FastLeaderElection算法选出B作为新的Leader。C、D和E成为Follower且以B为Leader后,会主动将自己最大的zxid发送给B,B会将Follower的zxid与自身zxid间的所有被Commit过的消息同步给Follower,如下图所示。

在上图中

  • P1和P2都被A Commit,因此B会通过同步保证P1、P2、C1与C2都存在于C、D和E中
  • P3由于未被A Commit,同时幸存的所有服务器中P3未存在于大多数据服务器中,因此它不会被同步到其它Follower

通知Follower可对外服务 同步完数据后,B会向D、C和E发送NEWLEADER命令并等待大多数服务器的ACK(下图中D和E已返回ACK,加上B自身,已经占集群的大多数),然后向所有服务器广播UPTODATE命令。收到该命令后的服务器即可对外提供服务。

未Commit过的消息对客户端不可见

在上例中,P3未被A Commit过,同时因为没有过半的服务器收到P3,因此B也未Commit P3(如果有过半服务器收到P3,即使A未Commit P3,B会主动Commit P3,即C3),所以它不会将P3广播出去。

具体做法是,B在成为Leader后,先判断自身未Commit的消息(本例中即P3)是否存在于大多数服务器中从而决定是否要将其Commit。然后B可得出自身所包含的被Commit过的消息中的最小zxid(记为min_zxid)与最大zxid(记为max_zxid)。C、D和E向B发送自身Commit过的最大消息zxid(记为max_zxid)以及未被Commit过的所有消息(记为zxid_set)。B根据这些信息作出如下操作

  • 如果Follower的max_zxid与Leader的max_zxid相等,说明该Follower与Leader完全同步,无须同步任何数据
  • 如果Follower的max_zxid在Leader的(min_zxid,max_zxid)范围内,Leader会通过TRUNC命令通知Follower将其zxid_set中大于Follower的max_zxid(如果有)的所有消息全部删除

上述操作保证了未被Commit过的消息不会被Commit从而对外不可见。

上述例子中Follower上并不存在未被Commit的消息。但可考虑这种情况,如果将上述例子中的服务器数量从五增加到七,服务器F包含P1、P2、C1、P3,服务器G包含P1、P2。此时服务器F、A和B都包含P3,但是因为票数未过半,因此B作为Leader不会Commit P3,而会通过TRUNC命令通知F删除P3。如下图所示。

总结

  • 由于使用主从复制模式,所有的写操作都要由Leader主导完成,而读操作可通过任意节点完成,因此Zookeeper读性能远好于写性能,更适合读多写少的场景
  • 虽然使用主从复制模式,同一时间只有一个Leader,但是Failover机制保证了集群不存在单点失败(SPOF)的问题
  • ZAB协议保证了Failover过程中的数据一致性
  • 服务器收到数据后先写本地文件再进行处理,保证了数据的持久性

原文地址:https://www.cnblogs.com/itxiaok/p/10356657.html

时间: 2024-12-24 14:28:24

搞懂分布式技术4:ZAB协议概述与选主流程详解的相关文章

搞懂分布式技术2:分布式一致性协议与Paxos,Raft算法

搞懂分布式技术2:分布式一致性协议与Paxos,Raft算法 2PC 由于BASE理论需要在一致性和可用性方面做出权衡,因此涌现了很多关于一致性的算法和协议.其中比较著名的有二阶提交协议(2 Phase Commitment Protocol),三阶提交协议(3 Phase Commitment Protocol)和Paxos算法. 本文要介绍的2PC协议,分为两个阶段提交一个事务.并通过协调者和各个参与者的配合,实现分布式一致性. 两个阶段事务提交协议,由协调者和参与者共同完成. 角色 XA概

搞懂分布式技术21:浅谈分布式消息技术 Kafka

搞懂分布式技术21:浅谈分布式消息技术 Kafka 浅谈分布式消息技术 Kafka 本文主要介绍了这几部分内容: 1基本介绍和架构概览 2kafka事务传输的特点 3kafka的消息存储格式:topic和parition 4副本(replication)策略:主从broker部署和partition备份,以及选主机制 5kafka消息分组,通过comsumergroup实现主体订阅 6push和pull的区别,顺序写入和消息读取,零拷贝机制 Kafka的基本介绍 Kafka是最初由Linkedi

搞懂分布式技术11:分布式session解决方案与一致性hash

搞懂分布式技术11:分布式session解决方案与一致性hash session一致性架构设计实践 原创: 58沈剑 架构师之路 2017-05-18 一.缘起 什么是session? 服务器为每个用户创建一个会话,存储用户的相关信息,以便多次请求能够定位到同一个上下文. Web开发中,web-server可以自动为同一个浏览器的访问用户自动创建session,提供数据存储功能.最常见的,会把用户的登录信息.用户信息存储在session中,以保持登录状态. 什么是session一致性问题? 只要

搞懂分布式技术3:初探分布式协调服务zookeeper

搞懂分布式技术3:初探分布式协调服务zookeeper 1.Zookeepr是什么 Zookeeper是一个典型的分布式数据一致性的解决方案,分布式应用程序可以基于它实现诸如数据发布/订阅,负载均衡,命名服务,分布式协调/通知.集群管理,Master选举,分布式锁和分布式队列等功能. 2.zookeeper可以保证的分布式一致性 a.顺序一致性 从一个客户端发起的事务请求,最终将会严格地按照其发起顺序被应用到zookeeper中去 b.原子性 所有事务请求的处理结果在整个集群中所有机器上的应用情

搞懂分布式技术9:Nginx负载均衡原理与实践

搞懂分布式技术9:Nginx负载均衡原理与实践 本篇摘自<亿级流量网站架构核心技术>第二章 Nginx负载均衡与反向代理 部分内容. 当我们的应用单实例不能支撑用户请求时,此时就需要扩容,从一台服务器扩容到两台.几十台.几百台.然而,用户访问时是通过如的方式访问,在请求时,浏览器首先会查询DNS服务器获取对应的IP,然后通过此IP访问对应的服务. 因此,一种方式是域名映射多个IP,但是,存在一个最简单的问题,假设某台服务器重启或者出现故障,DNS会有一定的缓存时间,故障后切换时间长,而且没有对

搞懂分布式技术7:负载均衡概念与主流方案

搞懂分布式技术7:负载均衡概念与主流方案 负载均衡的原理 原创: 刘欣 码农翻身 4月23日 这是1998年一个普通的上午. 一上班,老板就把张大胖叫进了办公室,一边舒服地喝茶一边发难:"大胖啊,我们公司开发的这个网站,现在怎么越来越慢了? " 还好张大胖也注意到了这个问题,他早有准备,一脸无奈地说: "唉,我昨天检查了一下系统,现在的访问量已经越来越大了,无论是CPU,还是硬盘.内存都不堪重负了,高峰期的响应速度越来越慢." 顿了一下,他试探地问道:"老

搞懂分布式技术15:缓存更新的套路

搞懂分布式技术15:缓存更新的套路 缓存更新的套路 看到好些人在写更新缓存数据代码时,先删除缓存,然后再更新数据库,而后续的操作会把数据再装载的缓存中.然而,这个是逻辑是错误的.试想,两个并发操作,一个是更新操作,另一个是查询操作,更新操作删除缓存后,查询操作没有命中缓存,先把老数据读出来后在放到缓存中之前更新操作获得了执行权更新了数据库,于是,在缓存中的数据还是老的数据,导致缓存中的数据是脏的,而且还一直这样脏下去了. 我不知道为什么这么多人用的都是这个逻辑,当我在微博上发了这个贴以后,我发现

搞懂分布式技术17,18:分布式事务总结

搞懂分布式技术17:浅析分布式事务 众所周知,数据库能实现本地事务,也就是在同一个数据库中,你可以允许一组操作要么全都正确执行,要么全都不执行.这里特别强调了本地事务,也就是目前的数据库只能支持同一个数据库中的事务.但现在的系统往往采用微服务架构,业务系统拥有独立的数据库,因此就出现了跨多个数据库的事务需求,这种事务即为"分布式事务".那么在目前数据库不支持跨库事务的情况下,我们应该如何实现分布式事务呢?本文首先会为大家梳理分布式事务的基本概念和理论基础,然后介绍几种目前常用的分布式事

搞懂分布式技术10:LVS实现负载均衡的原理与实践

搞懂分布式技术10:LVS实现负载均衡的原理与实践 浅析负载均衡及LVS实现 原创: fireflyc 写程序的康德 2017-09-19 负载均衡 负载均衡(Load Balance,缩写LB)是一种网络技术,它在多个备选资源中做资源分配,以达到选择最优.这里有三个关键字: 网络技术,LB要解决的问题本质上是网络的问题,所以它实际上就是通过修改数据包中MAC地址.IP地址字段来实现数据包的"中转": 资源,这里的资源不仅仅是计算机也可以是交换机.存储设备等: 最优,它则是针对业务而言