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

来源:互联网 发布:江恩矩阵图软件 编辑:程序博客网 时间:2024/05/17 22:37

分布式系统中的协调者

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

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的架构组成


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系统布署图如下

 

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

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

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

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

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

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

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

 

 

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


2.    选主实现交互时序图

服务端启动的入口为

org.apache.zookeeper.server.quorum.QuorumPeerMain

 

 

 

3.    选主流程

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


             (图片来自互联网)

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

 

0 0
原创粉丝点击