zookeeper的选主流程(源码分析)

分布式系统中的协调者

在分布式系统中,协调者是一个很重要的角色,在我们淘系的系统中,大多数中间件系统的设计都引入了协调者这样一个概念。像我们工作中常用的

Tair的configServer:管理dataServer,维护/监控dataServer的使用状态

OceanBase中的RootServer:管理基线服务器ChunkServer

还有google的Hadoop里面的Zookpeer

主要工作是维护系统的元数据。和CS保持心跳,负责数据节点的租约管理等。虽然实现的细节上都大不相同,但概念上是一致的,做为一个中间角色统一管理数据资源,以降低数据资源的访问路径,同时还能起到负载均衡的作用。

 

协调者之间如何保证高可用性

Tair的configServer:使用的是主备的方式

OceanBase中的RootServer:使用Ha主备的方式

今天介绍的是Hadoop的Zookpeer中的互备方式。使用的是基于Paxos算法的实现

Paxos是分布式系统中的关键算法。在google的BigTable以及hadoop的hdfs底层为了实现服务器的高可用性,都使用了这个的实现。

 

首先了解一下Zookeeper,它是hadoop下最重要的一个子项目之一。

鸟览一下hadoop的架构组成

zookeeper的选主流程(源码分析)_第1张图片

Hive:基于MapReduce之上的一个数据查询工具。可以将SQL转换为MR语句执行

MapReduce:MapReduce处理

Hbase:分布式数据库

Hdfs:分布式存储

Zookeeper:分布式一致问题

从hadoop的众多组件中。我们可以得知zookeeper是支撑起hadoop云计算大厦的地基。拥有它。Hadoop的服务器集群才有了无限扩容的可能。

在Zookeeper的实际使用中,它主要被用来做为集群管理来使用,用于监控hadoop的hdfs的服务器的使用状态

主要原理是zookeeper和hdfs服务持保持通信,保持服务器的状态,以确保hdfs服务器是存活的。但zookeeper服务器之间也需要做容灾。即如果某个zookeeper服务挂了,需要有新的zookeeper顶上,保持和hdfs服务器的通信,否则,hadoop的文件就不知道存储到哪些节点了。

 

Zookeeper系统布署图如下

 zookeeper的选主流程(源码分析)_第2张图片

Server:指专门用于管理HDFS服务器集群的服务器。HDFS文件服务器的存储状态,信息,都会保存在服务器上。

Client:是指HDFS服务器集群。用于存放数据块。

1.      服务器定义了三个角色。leader(领导者), follow(跟随者),Observer (跟随者)。

2.      leader.一般会默认指定。会接受所有follow的请求后发起请求到所有的follow.,负责和所有的节点进行数据交换

3.      follow,会参与leader发起的请求,并做出请求的回复。

4.      客户端不参与投票的请求,同会与leader做数据同步

5.      所有的server都会跟client通信,提供服务。

 

 

1.    选主实现主要类结构图

zookeeper的选主流程(源码分析)_第3张图片

2.    选主实现交互时序图

服务端启动的入口为

org.apache.zookeeper.server.quorum.QuorumPeerMain

 zookeeper的选主流程(源码分析)_第4张图片

 

 

3.    选主流程

(描述流程的过程同时代码对应的在连接中,可以直接接ctrl+鼠标点击查看):

zookeeper的选主流程(源码分析)_第5张图片

             (图片来自互联网)

1.      接收投票消息。投票消息会包括id,zxid,epoch,state,这四种信息,分别代表

Id: 唯一标识一台机器,存储在myid文件中

Zxid: 标识了本机想要选举谁为leader,是本机目前所见到的最大的id值

Epoch: 逻辑时钟。用于判断选举是否过期

State: 本机的状态信息(包括looking,leading,following,observing)

见1. Notification数据结构

2.      判断PeerState状态,如果是looking状态,则继续.如果是leading,foolowing,observing则走别的流程

见2. PeerState枚举状态

3.      收到票后,会判断发送过来的逻辑时钟是否大于目前的逻辑时钟,如果是说明集群已经进入了新一轮的投票了。

4.      清空投票箱。因为这个之前的投票都是上一次投票期间维护的。

5.      如果等于目前的逻辑时钟,说明是当前的,则更新最大的leader id和提案id

判断是否需要更新当前自己的选举情况.在这里是根据选举leader id,保存的最大数据id来进行判断的,这两种数据之间对这个选举结果的影响的权重关系是:首先看数据id,数据id大者胜出;其次再判断leader id,leader id大者胜出

判读投票结果代码

 

6.      发送通知,通知其他的QuorumPeer更新leader信息.同时将更新后的leader信息放入投票箱

检查是否已经接收到了所有服务器的投票代码参考。如果是的,则设置自己的选择结果

如果没有接收到所有服务器的投票,那判读这个leadId是否得到了一半以后的服务器的投票代码参考,如果是则返回

 

 

以上流程描述的是在zookeeper中,参考使用的算法是FastLeaderElection

在zookeeper的的选主的流程,另外还提供了LeaderElection和AuthFastLeaderElection的实现

LeaderElection的实现比较简单。以(id,zxid)做为投票的依据.并且它的实现是同步的,需要等待所有服务器返回后再统计结果。

而相比FastLeaderElection是每次收到回复都会计算投票结果,效率上会比LeaderElection更好一些。

4. 代码细节

1.Notification数据结构

static public class Notification {

        /*

         * Proposed leader

         */

        long leader;

 

        /*

         * zxid of the proposed leader

         */

        long zxid;

 

        /*

         * Epoch

         */

        long epoch;

 

        /*

         * current state of sender

         */

        QuorumPeer.ServerState state;

 

        /*

         * Address of the sender

         */

        InetSocketAddress addr;

}


2. ServerState

public enumServerState {

      LOOKING, FOLLOWING, LEADING;

   }

public enum LearnerType {
        PARTICIPANT, OBSERVER;
    }


3. 判断逻辑时钟逻辑

见FastLeaderElection类

         //如果发送的逻辑时钟,大于服务器的逻辑时钟
if (n.epoch > logicalclock) {
                            logicalclock = n.epoch;
//清空投票箱
                            recvset.clear();
//判断是否需要更新leaderId
                            if(totalOrderPredicate(n.leader, n.zxid,
                                    self.getId(), self.getLastLoggedZxid()))
//如果发送的逻辑时钟大于本地,则发送发送的逻辑时钟以及相关信息
                                updateProposal(n.leader, n.zxid);
                            else
                                updateProposal(self.getId(),
                                        self.getLastLoggedZxid());
//发送
                            sendNotifications();
                         } else if (n.epoch < logicalclock) {
//如果发送的逻辑时钟小于本地的。说明对方在一个相对较早的选举进程中                            LOG.info("n.epoch < logicalclock");
                            break;
                        } else if (totalOrderPredicate(n.leader, n.zxid,
//如果逻辑时钟相同判断是否需要更新本机的数据。发送
                                proposedLeader, proposedZxid)) {
                            LOG.info("Updating proposal");
                            updateProposal(n.leader, n.zxid);
                            sendNotifications();
                        }


 

----------------------------------分割线--------------------------------

 

//判断对比的关键类。
private boolean totalOrderPredicate(long newId, long newZxid, long curId, long curZxid) {
        LOG.debug("id: " + newId + ", proposed id: " + curId + ", zxid: " + newZxid + ", proposed zxid: " + curZxid);
//获得当前serverId的权重是否为0
        if(self.getQuorumVerifier().getWeight(newId) == 0){
            return false;
        }
        //1.如果新的选举ID大于当前保存的选主ID
//2. 或者新的选举ID等当前保存的。判断机器ID是否相同,如果新的大于,则返//回true.如果返回true说明本机需要更新id
        if ((newZxid > curZxid)
                || ((newZxid == curZxid) && (newId > curId)))
            return true;
        else
            return false;
    }


4. 检查是否收到了大多数人投票

见FastLeaderElection类

 if ((self.quorumPeers.size() == recvset.size()) &&
                                (self.getQuorumVerifier().getWeight(proposedLeader) != 0)){

                            self.setPeerState((proposedLeader == self.getId()) ? 
                                    ServerState.LEADING: ServerState.FOLLOWING);
                            leaveInstance();
                            return new Vote(proposedLeader, proposedZxid);
                            
                        } else if (termPredicate(recvset,
                                new Vote(proposedLeader, proposedZxid,
                                        logicalclock))) {
                            //Otherwise, wait for a fixed amount of time
                            LOG.debug("Passed predicate");
    
                            // Verify if there is any change in the proposed leader
                            while((n = recvqueue.poll(finalizeWait,
                                    TimeUnit.MILLISECONDS)) != null){
                                if(totalOrderPredicate(n.leader, n.zxid,
                                        proposedLeader, proposedZxid)){
                                    recvqueue.put(n);
                                    break;
                                }
                            }
                        
                            if (n == null) {
                                self.setPeerState((proposedLeader == self.getId()) ? 
                                    ServerState.LEADING: ServerState.FOLLOWING);
                                LOG.info("About to leave instance:"
                                        + proposedLeader + ", " + 
                                        proposedZxid + ", " + self.getId()
                                        + ", " + self.getPeerState());
                                leaveInstance();
                                return new Vote(proposedLeader,
                                    proposedZxid);
                            }
                        }


5. 判断是否获得一半的票数

 

private boolean termPredicate(
            HashMap<Long, Vote> votes, 
            Vote vote) {
        HashSet<Long> set = new HashSet<Long>();
        
        /*
         * 迭代投票箱,判断是否和当前的leader是否是同一个,是则添加到set里面
         */
        for (Map.Entry<Long,Vote> entry : votes.entrySet()) {
            if (vote.equals(entry.getValue())){
                set.add(entry.getKey());
            }
        }
        //判读票数是否过半             
        if(self.getQuorumVerifier().containsQuorum(set))
            return true;
        else
            return false;
    }
self.getQuorumVerifier().containsQuorum(set)的实现很简单.如下
--------------------分割线--------------------
public boolean containsQuorum(HashSet<Long> set){
        return (set.size() > half);
    }


5. 参考资料

Zookeeper源码地址:

http://zookeeper.apache.org/releases.html

 

你可能感兴趣的:(zookeeper的选主流程(源码分析))