zoukankan      html  css  js  c++  java
  • 分布式ZAB协议

    提到ZAB,恐怕大家第一时间就会想到Zookeeper,然后由Zookeeper又会联想到Paxos。这之间的联系是不是因为有本畅销书叫《从Paxos到Zookeeper分布式一致性原理与实践》,使得大家常常把Zookeeper和Paxos关联起来,毕竟“买了就是读了”,开个玩笑,ZAB和Paxos的确也是有很多相似之处的,理解了Paxos也的确对学习其它分布式一致性或共识算法非常有帮助。不过Paxos的内容我们以后再说,在这里只讨论ZAB。
     
    ZAB的全称为Zookeeper Atomic Broadcast,其实大家看到“Atomic”和“Broadcast”两个词,应该就能大概明白ZAB的主要工作方式了。他是一个为Zookeeper量身定制的支持崩溃恢复的原子广播协议,用于保障Zookeeper各副本之间在正常或异常情况下的数据一致性。
     
    既然是支持崩溃恢复的原子广播协议,那我们介绍ZAB就可以从他的这两个名词说起,分别是“原子广播“和“崩溃恢复“。
     
    原子广播
     
    在ZAB协议中,存在两种角色,Leader和Follower(在Zookeeper中实际上还有一个角色叫Observer,但他和ZAB协议没有直接关系,所以在这里不做讨论。),Leader负责数据的读和写请求,Follower只负责读请求,外部应用可以给任意的Zookeeper端发送请求,所以如果是写请求,就会转给Leader处理,读的话则就地响应。这样做同时也是为了达到所有写请求都能有序处理的效果。
     
    “原子”可以理解为事务,在Zookeeper收到一个数据写请求后,对其分配一个全局唯一且递增的Zxid,然后将该请求转化为事务Proposal,严格按照请求的接收次序放到针对每个Follower的FIFO队列中,即向集群中所有Follower广播该数据,Follower成功收到数据后,会发送Ack给Leader,当Leader收到的Ack超过半数,则向Follower发送commit命令,完成事务的提交。
      
    可以看出在这个分布式事务的提交过程中是遵循了2PC协议的,即事务的预处理请求和事务提交是分成两阶段进行的,不同之处在于2PC要求所有副本应答,而ZAB只要求超过半数的副本应答即可,这样也避免了2PC单点超时造成阻塞的问题。
     
    崩溃恢复
     
    Zookeeper作为一个典型的CP(一致性/分区容错性)系统,在设计上必须考虑节点异常的情况,所以ZAB针对崩溃恢复的设计是必不可少的,这也是Zookeeper抛弃可用性的证明,在崩溃恢复过程中,Zookeeper服务对外是不可用的。
     
    崩溃恢复的过程可以分成两个阶段来说:一是Leader选举,二是数据同步。
     
    1、Leader选举
     
    如果Leader节点崩溃,则Follower节点的状态会从FOLLOWING变为LOOKING,这里节点的状态是用一个枚举标识的(码 1),即进入选举状态,选举的方式简单来讲就是看谁能得到超过半数的选票。
     
    // 码 1 QuorumPeer.java:节点状态枚举
    public enum ServerStatepublic enum ServerState {
            LOOKING,
            FOLLOWING,
            LEADING,
            OBSERVING
    }
     
    选票的信息见class Vote(码 2),还记得刚才提到的Leader为每个事务分配的Zxid吧,该字段为一个64位长整形,其中高32位称作Epoch,低32位是一个递增的计数器(码 3)。Epoch代表了Leader的编号,每次选举出了新的Leader,该数值就被+1,并将Counter清0,之后该Leader每收到一个请求,都会将Counter+1。
     
    // 码 2 Vote.java:选票结构
    public class Vote {
        private final int version;
        private final long id; //服务器ID
        private final long zxid; //Epoch + Counter
        private final long electionEpoch; //选举轮次
        private final long peerEpoch; //被推举的Leader所在的选举轮次
        private final ServerState state; //当前服务器状态
    }
     
    // 码 3 ZxidUtils.java:Zxid结构
    public class ZxidUtils {
        public static long getEpochFromZxid(long zxid) {
            return zxid >> 32L;
        }
        public static long getCounterFromZxid(long zxid) {
            return zxid & 0xffffffffL;
        }
        public static long makeZxid(long epoch, long counter) {
            return (epoch << 32L) | (counter & 0xffffffffL);
        }
        public static String zxidToString(long zxid) {
            return Long.toHexString(zxid);
        }
    }
     
    在选举初期,每个节点都会初始化自身选票(Vote实例化),节点默认都是推举自己做Leader的,之后将自己的信息填到选票后放到队列中发送给其它节点,也包括他自己,当其他节点收到了选票之后,先对比electionEpoch是否和自身一样,如果比自身大,则清空自身的Vote和已收到的选票,更新electionEpoch后重新对比。如果比自身小,则直接丢弃该选票。如果和自身一样,则会进行下一阶段的对比,这个对比次序依次为peerEpoch、zxid和id,规则为比自身大,则更新自身选票,比自身小,则丢弃(码 4)。所有对比更新完成后,发出自身选票。最后统计所有选票,当某个节点的票数超过一半(Quorum规则),则该节点被推举为新的Leader。
     
    // 码 4 FastLeaderElection.java:选票对比
    protected boolean totalOrderPredicate(long newId, long newZxid, long newEpoch, long curId, long curZxid, long curEpoch) {
            LOG.debug(
                "id: {}, proposed id: {}, zxid: 0x{}, proposed zxid: 0x{}",
                newId,
                curId,
                Long.toHexString(newZxid),
                Long.toHexString(curZxid));
            if (self.getQuorumVerifier().getWeight(newId) == 0) {
                return false;
            }
            /*
             * We return true if one of the following three cases hold:
             * 1- New epoch is higher
             * 2- New epoch is the same as current epoch, but new zxid is higher
             * 3- New epoch is the same as current epoch, new zxid is the same
             * as current zxid, but server id is higher.
             */
             return ((newEpoch > curEpoch)
                    || ((newEpoch == curEpoch)
                        && ((newZxid > curZxid)
                            || ((newZxid == curZxid)
                                && (newId > curId)))));
        }
     
    在新Leader被选举出后,则将自己的状态从LOOKING更新为LEADING,其它节点变为FOLLOWING,然后进入数据同步阶段。
     
    2、数据同步
     
    在新Leader正式开始工作之前,每个Follower会主动和Leader建立连接,然后将自己的zxid发送给Leader,Leader从中选出最大的Epoch并将其+1,作为新的Epoch同步给每个Follower,在Leader收到超过半数的Follower返回Epoch同步成功的信息之后,进入数据对齐的阶段。对齐的过程也比较直接,如果Follower上的事务比Leader多,则删除,比Leader少,则补充,重复这个过程直到过半的Follower上的数据和Leader保持一致了,数据对齐的过程结束,Leader正式开始对外提供服务。
     
    还有一种情况是,在新Leader选举出来之后,原来挂掉的Leader又重新连接上了,那么此时因为原Leader所持有的Epoch已经比新Leader的Epoch小了,故原Leader将会变为Follower,并和新的Leader完成数据对齐。
     
    在这个数据对齐过程中还是有很多细节的,感兴趣的人可以从源码再进行更深入的研究,我这里就不做介绍了。
     
    * 所用源码均引自 apache-zookeeper-3.6.0 
    https://zookeeper.apache.org/releases.html
  • 相关阅读:
    新浪微盘又是一个给力的产品啊,
    InfoQ: 百度数据库架构演变与设计
    列式数据库——Sybase IQ
    MapR初体验 淘宝共享数据平台 tbdata.org
    IBM正式发布新一代zEnterprise大型机(组图) 大型机,IBM,BladeCenter,美国,纽约 TechWeb News
    1TB is equal to the number of how many GB? 1PB equal to is equal to the number of TB? 1EB PB? | PCfault.com
    Cassandra vs HBase | WhyNosql
    The Hadoop Community Effect
    雅虎剥离开源软件平台 Hadoop ,与风投新建 Hortonworks 公司 品味雅虎
    RowOriented Database 、ColumnOriented Database 、KeyValue Store Database 、DocumentOriented Database
  • 原文地址:https://www.cnblogs.com/aspirant/p/13308093.html
Copyright © 2011-2022 走看看