zookeeper启动时选举过程分析

679 阅读2分钟

在选举过程中,主要涉及到以下这些类。

先分析下这些类的关系,其中 FastLeaderElection 持有 QuorumCnxManager 通过该类管理服务器之间端到端的连接;

FastLeaderElection 还持有 Messenger ,通过该类中的 WorkerSender、WorkerReceiver 来收发选举过程中产生的消息。

连接建立

通过 Listener 绑定选举端口,之后调用 FastLeaderElection.lookForLeader 进行选举。

其中,服务器之间的连接只由 myid 大的主动发起连接到 myid 小的,这样减少了重复连接。如果 myid 小的服务器建立连接到 myid 大的服务器,那么将只发送自身的地址之后就断开连接。myid 大的服务器收到该地址消息后,主动发起请求,建立连接到 myid 小的服务器的通信。

发送选票

在 FastLeaderElection.lookForLeader 方法中,通过 sendNotifications 方法将选票放入队列 sendqueue 中;

该队列中的消息通过 FastLeaderElection 中的 Messager WorkerSender 进行处理,将该消息交付给 QuorumCnxManager 维护的对端的发送队列 。对端发送队列的映射关系如下:

// <sid,queue>
final ConcurrentHashMap<Long, BlockingQueue<ByteBuffer>> queueSendMap;

如果消息包交付之后,对端的连接还未建立则进行连接的建立。其中对端连接的接收、发送处理线程为 SendWorker、RecvWorker;通过以下结构维护:

// <sid,SendWorker> 其中 SendWorker 通过一个字段关联了 RecvWorker
final ConcurrentHashMap<Long, SendWorker> senderWorkerMap;

之后消息,就由 SendWorker 发送给对端了。

接收选票

服务器之间的选举消息由 RecvWorker 接收,将接收到的数据封装为 org.apache.zookeeper.server.quorum.QuorumCnxManager.Message 之后,投递到 QuorumCnxManager 的队列 recvQueue 中;

WorkerReceiver 从 QuorumCnxManager 的队列 recvQueue 中获取收到的消息,而 recvQueue 中的消息由对端连接的接收线程 RecvWorker 放入。消息被封装为 org.apache.zookeeper.server.quorum.QuorumCnxManager.Message。

WorkerReceiver 将收到的 Message 消息,处理封装为 org.apache.zookeeper.server.quorum.FastLeaderElection.Notification,并放入 FastLeaderElection 的队列 recvqueue 中;

在 FastLeaderElection.lookForLeader 方法中,如果节点的状态为 LOOKING 则证明选举未完毕。将不断轮训队列 recvqueue 获取 Notification 消息。将收到的消息进行比对,计算该选票的结果。选票处理规则如下:

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;
    }
	// 选举周期较大胜出
    // zxid 较大胜出
    // myId 较大胜出
    /*
     * 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)))));
}