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 ”獲取源碼。