分佈式選舉-ZAB算法-2 Leader選舉 代碼實現

ZAB Leader選舉實現

設定集羣中有3個節點,通過ZAB算法實現選主。節點之間的通信使用的是自我實現的Remoting組件,基於Netty開發,可以以同步,異步的方式發起通信。在後續《分佈式通信》系列的文章中,會向大家詳細分析Remoting組件。

 

分佈式選舉的項目名稱:justin-distribute-election

整體結構如圖:

 

主要Package說明:

callback:異步請求消息的回調處理器集合

client:數據存取接口

message:投票、心跳等消息的集合

processor:接收請求消息的處理器集合

 

節點相關的類設計:

節點狀態類:NodeStatus.class

public enum NodeStatus {
    LOOKING,
    LEADING,
    FOLLOWING,
}

節點類:Node.class

// 投票箱,記錄集羣中節點的投票信息
private final ConcurrentMap<Integer, Vote> voteBox =
new ConcurrentHashMap<>();
// 記錄集羣中Follower節點的數據ID
private final ConcurrentMap<Integer, ZxId> zxIdMap =
new ConcurrentHashMap<>();
// 記錄集羣中Follower節點的數據ID
private final ConcurrentMap<Integer, Boolean> snapshotMap =
new ConcurrentHashMap<>();
// 節點初始爲Following狀態
private volatile NodeStatus status = NodeStatus.FOLLOWING;
private Vote myVote;

投票類:Vote.class

public class Vote implements Comparable<Vote>{
    // 投票的節點ID
    private int nodeId;
    // 投票週期
    private volatile long epoch;
    // 被投票的節點ID
    private volatile int voteId;
    // 節點的數據ID
    private volatile ZxId lastZxId;
    
    // 先比較數據ID,如果相等再比較節點ID
    @Override
    public int compareTo(Vote o) {
        if (this.lastZxId.compareTo(o.lastZxId) != 0) {
            return this.lastZxId.compareTo(o.lastZxId);
        }else if (this.nodeId < o.nodeId) {
            return -1;
        }else if (this.nodeId > o.nodeId) {
            return 1;
        }
        return 0;
    }

線程設計:

  • mgrServer端線程,用於接收管理集羣節點的消息;

  • server端線程,用於接收節點發送投票及數據同步的消息;

  • client端線程,用於向節點發送消息;

  • 心跳線程,用於Leader節點向Follower節點發送心跳消息;

  • 選舉線程,用於在集羣內選舉主節點;

  • 節點發現線程,用於新節點加入集羣;

 

    具體實現就不列出來了,很容易理解,大家看代碼吧。

 

選舉流程:

選舉線程調用的方法:election()

private void election() {
    if (status == NodeStatus.LEADING) {
        return;
    }
    if (!nodeConfig.resetElectionTick()) {
        return;
    }

    status = NodeStatus.LOOKING;
    // 選舉週期加1
    epoch += 1;
    // 清空集羣中節點的數據ID記錄
    zxIdMap.clear();
    // 獲取最新的數據ID,組裝投票信息
    this.myVote = new Vote(nodeConfig.getNodeId(),
                        nodeConfig.getNodeId(), 0, getLastZxId());
    this.myVote.setEpoch(epoch);
    // 將本地節點的投票放入投票箱
    this.voteBox.put(nodeConfig.getNodeId(), myVote);
    VoteMessage voteMessage = VoteMessage.getInstance();
    voteMessage.setVote(myVote);
    // 單向發送投票消息,不需要回復
    sendOneWayMsg(voteMessage.request());
}

投票消息處理類:VoteRequestProcessor.class

// 將其他節點的投票消息記入投票箱
node.getVoteBox().put(peerVote.getNodeId(), peerVote);
// 比較選舉週期
if (peerVote.getEpoch() > node.getMyVote().getEpoch()) {
    node.getMyVote().setEpoch(peerVote.getEpoch());
    node.getMyVote().setVoteId(peerVote.getNodeId());
    node.setStatus(NodeStatus.LOOKING);
}else if (peerVote.getEpoch() == node.getMyVote().getEpoch()) {
    // 週期相同,則比較數據ID和節點ID
    if (peerVote.compareTo(node.getMyVote()) == 1) {
    // 比較後,如果對端數據ID或節點ID大,則將投票ID記錄爲對端節點ID
node.getMyVote().setVoteId(peerVote.getNodeId());
        node.setStatus(NodeStatus.LOOKING);
    }
}
// 得票超過半數,則成爲Leader
if (node.isHalf()) {
    logger.info("Node:{} become leader!", node.getNodeConfig().getNodeId());
    node.becomeLeader();
}else if (node.getStatus() == NodeStatus.LOOKING){
    // 向其他節點發送新的投票消息
VoteMessage voteMsg = VoteMessage.getInstance();
    voteMsg.setVote(node.getMyVote());
    node.sendOneWayMsg(voteMsg.request());
}

心跳線程調用的方法:heartbeat()

long index = -1;
// 獲取Follower節點應該同步的數據ID
if (zxIdMap.containsKey(entry.getKey())) {
    index = zxIdMap.get(entry.getKey()).getCounter();
}else {
    index = dataManager.getLastIndex();
}
// 獲取數據
Data data = dataManager.read(index);
if (data.getZxId().getEpoch() == 0) {
    data.getZxId().setEpoch(epoch);
}
// 組裝數據消息
DataMessage dataMsg = DataMessage.getInstance();
dataMsg.setNodeId(nodeConfig.getNodeId());
dataMsg.setType(DataMessage.Type.SYNC);
dataMsg.setData(data);
executorService.submit(new Runnable() {
    @Override
    public void run() {
        try {
            // 發送數據消息
            RemotingMessage response = client.invokeSync(
                       entry.getValue(), dataMsg.request(), 3*1000);
            DataMessage res = DataMessage.getInstance()
                                .parseMessage(response);    
            // 將Follower節點應該同步的數據ID放入zxIdMap中
            if (res.getSuccess()) {
int peerId = res.getNodeId();
                ZxId peerZxId = res.getData().getZxId();
                zxIdMap.put(peerId, peerZxId);
            }
        } catch (Exception e) {
            logger.error(e);
        }
    }
});

心跳消息處理類:DataRequestProcessor.class

logger.info("Receive heartbeat message: " + dataMsg);
// 重置選舉計時器和心跳計時器
node.getNodeConfig().setPreElectionTime(System.currentTimeMillis());
node.getNodeConfig().setPreHeartbeatTime(System.currentTimeMillis());
// 節點切換爲Following狀態
node.setStatus(NodeStatus.FOLLOWING);
// 設置主節點ID
node.setLeaderId(dataMsg.getNodeId());

至此,ZAB算法的Leader選舉代碼實現完成。

 

接下來的文章我們來分析分佈式數據複製的原理,同時完善Raft算法數據複製部分的代碼。

代碼地址:https://github.com/Justin02180218?tab=repositories


更多【分佈式專輯】系列文章,請關注公衆號

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章