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

Raft Leader選舉實現

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

 

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

整體結構如圖:

 

主要Package說明:

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

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

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

 

節點相關的類設計:

NodeStatus.class:

public enum NodeStatus {
    FOLLOWER,
    PRE_CANDIDATE,
    CANDIDATE,
    LEADER
}

 

Node.class:

// 記錄集羣中所有節點的元數據
private final ConcurrentMap<Integer, NodeMetadata> cluster =
new ConcurrentHashMap<Integer, NodeMetadata>();
// 設置節點的初始狀態爲Follower
private final AtomicReference<NodeStatus> status =
new AtomicReference<NodeStatus>(NodeStatus.FOLLOWER);

// 節點ID
private volatile int nodeId;
// 用於記錄已經投票的候選節點ID
private volatile int voteFor = -1;
// 記錄Leader ID
private volatile int leaderId = -1;

 

線程設計:

  • server端線程,用於接收其他節點發送的消息;

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

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

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

// 啓動Server線程
server = new NettyRemotingServer(new NettyServerConfig(
this.nodeConfig.getHost(), this.nodeConfig.getPort()));
server.registerProcessor(MessageType.VOTE,
new VoteRequestProcessor(this), executorService);
server.registerProcessor(MessageType.ENTRIES,
new EntryRequestProcessor(this), executorService);
server.registerProcessor(MessageType.MEMBERS,
new MembersRequestProcessor(this), executorService);
server.registerProcessor(MessageType.MEMBERSHIP,
new MembershipRequestProcessor(this), executorService);
server.registerProcessor(MessageType.CLIENT,
new ClientRequestProcessor(this), executorService);
server.start();
// 啓動Client線程。
client = new NettyRemotingClient(new NettyClientConfig());
client.start();
// 週期的執行心跳線程
scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
    @Override
    public void run() {
        heartbeat();
    }
}, 0, nodeConfig.getHeartbeatTimeout(), TimeUnit.MILLISECONDS);
// 週期的執行選舉線程
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
    @Override
    public void run() {
        election();
    }
}, 6000, 500, TimeUnit.MILLISECONDS);

 

節點啓動後,開始進行投票選舉:

流程如下圖:

投票消息類:VoteMessage.class

// 記錄候選節點ID
private int candidateId;
// 是否同意投票
private Boolean voteGranted;

 

選舉方法:election()

// 如果節點是Leader,則不發起選舉
if (status.get() == NodeStatus.LEADER) {
return;
}
// 如果沒有達到選舉超時時間,則不發起選舉,由選舉計時器控制。
if (!nodeConfig.resetElectionTick()) {
    return;
}
// 節點切換爲Candidate狀態
setStatus(NodeStatus.CANDIDATE);
logger.info("Node {} {} become CANDIDATE, current term {}", nodeId,
localAddr, metadata.getCurrentTerm());
// 任值週期加1
metadata.getCurrentTerm().incrementAndGet();
voteFor = nodeId;
// 設置投票消息
VoteMessage voteMsg = VoteMessage.getInstance();
voteMsg.setNodeId(nodeId);
voteMsg.setTerm(metadata.getCurrentTerm().get());
voteMsg.setCandidateId(nodeId);
for (Map.Entry<Integer, NodeMetadata> entry : cluster.entrySet()) {
    if (nodeId == entry.getKey()) {
        continue;
    }
    executorService.submit(new Runnable() {
        @Override
        public void run() {
            try {
logger.info("Vote request {} to {}", voteMsg,
entry.getValue().getNodeAddress());
                // 對其他節點進行異步請求投票
                client.invokeAsync(entry.getValue().getNodeAddress(),
voteMsg.request(), 3*1000, new VoteRequestCallback(Node.this));
            } catch (Exception e) {
                logger.error(e);
            }
        }
    });
}

 

投票消息處理類:VoteRequestProcessor.class

long currentTerm = node.getMetadata().getCurrentTerm().get();
// 請求投票節點的任值週期小於節點的當前週期,則不進行投票
if (result.getTerm() < currentTerm) {
result.setTerm(currentTerm);
    result.setVoteGranted(false);
    return result.response(request);
}
// 請求投票節點的任值週期大於節點的當前週期,則節點切換爲Follower
if (result.getTerm() > currentTerm) {
    node.comedown(result.getTerm());
}
// 如果節點沒有投過票
if (node.getVoteFor() == -1 || node.getVoteFor() ==
    result.getCandidateId()) {
    // 節點切換爲Follower
    node.setStatus(NodeStatus.FOLLOWER);
    // 任值週期設置成請求節點的週期
    node.getMetadata().getCurrentTerm().compareAndSet(currentTerm,
    result.getTerm());
    // 對請求節點進行投票
    node.getMetadata().setVoteGrant(true);
    node.setVoteFor(result.getNodeId());
    result.setTerm(currentTerm);
    result.setNodeId(node.getNodeId());
    result.setVoteGranted(true);
    logger.info("Send vote response: " + result);
    return result.response(request);
}

 

投票消息回調處理類:VoteRequestCallback.class

// 更新對端節點元數據信息
NodeMetadata peer = node.getCluster().get(resVoteMsg.getNodeId());
peer.setVoteGrant(resVoteMsg.getVoteGranted());
peer.getCurrentTerm().set(resVoteMsg.getTerm());
long currentTerm = node.getMetadata().getCurrentTerm().get();
if (node.getStatus() != NodeStatus.CANDIDATE) {
    return;
}

if (resVoteMsg.getTerm() > currentTerm) {
    node.comedown(resVoteMsg.getTerm());
}else {
if (resVoteMsg.getVoteGranted()) {
        // 先給自己投票
        int voteNums = 1;
        for (Map.Entry<Integer, NodeMetadata> entry :
            node.getCluster().entrySet()) {
            if (entry.getKey() == node.getNodeId()) {
                continue;
            }
            // 對端節點如果同意投票,則票數加1
            if (entry.getValue().getVoteGrant()) {
                voteNums += 1;
            }
        }
        // 得票數超過半數,則節點切換到Leader狀態
        if (voteNums > node.getCluster().size() / 2) {
            logger.info("Vote, leaderId={}, become leader ...", node.getNodeId());
            node.becomeLeader();
        }
    }else {
        logger.info("Vote peer:{} term:{}, local term:{}", resVoteMsg.getNodeId(), peer.getCurrentTerm(), currentTerm);
    }
}

 

發送心跳消息:heartbeat()

// 如果節點不是Leader,則不發送心跳
if (status.get() != NodeStatus.LEADER) {
return;
}
// 如果沒有達到心跳超時時間,則不發送
if (!nodeConfig.resetHeartbeatTick()) {
    return;
}

long currentTerm = metadata.getCurrentTerm().get();
for (Map.Entry<Integer, NodeMetadata> entry : cluster.entrySet()) {
    if (nodeId == entry.getKey()) {
        continue;
    }

    NodeMetadata peer = entry.getValue();
    LogEntry logEntry = null;
    if (metadata.getCommitIndex() > peer.getCommitIndex()) {
        logEntry = log.getLastLogEntry();
    }
    // 設置心跳消息
EntryMessage heartbeat = committedEntryMessage(
                        EntryMessage.Type.HEARTBEAT, logEntry, peer);
    executorService.submit(new Runnable() {
        @Override
        public void run() {
            try {
logger.info("Heartbeat request:{} to {}", heartbeat,
                 peer.getNodeId());
                // 向其他節點發送同步心跳消息
                RemotingMessage response = client.invokeSync(
                peer.getNodeAddress(), heartbeat.request(), 3 * 1000);
                EntryMessage resEntryMsg = EntryMessage.getInstance().parseMessage(response);
                logger.info("Heartbeat response:{} from {}",
                resEntryMsg, resEntryMsg.getNodeId());
                peer.setCommitIndex(resEntryMsg.getCommitIndex());
                // 如果返回的對端節點任值週期大於節點的任值週期,
                // 則節點切換到Follower狀態。
                if (resEntryMsg.getTerm() > currentTerm) {
comedown(resEntryMsg.getTerm());
                }
            } catch (Exception e) {
                logger.error(e);
            }
        }
    });
}

 

心跳消息處理類:EntryRequestProcessor.class

// 重置選舉計時器
node.getNodeConfig().setPreElectionTime(System.currentTimeMillis());
// 重置心跳計時器
node.getNodeConfig().setPreHeartbeatTime(System.currentTimeMillis());
// 設置LeaderId
node.setLeaderId(result.getLeaderId());
// 當前節點轉換爲Follower狀態
node.setStatus(NodeStatus.FOLLOWER);
node.getMetadata().getCurrentTerm().set(result.getTerm());

 

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

下一篇文章《分佈式選舉-ZAB算法-1 Leader選舉 原理》講解與Raft算法相似的Zab算法。


獲取Raft算法的實現代碼,請關注公衆號,後臺回覆“ Raft ”獲取源碼。

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