设定集群中有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 cluster =
new ConcurrentHashMap();
// 设置节点的初始状态为Follower
private final AtomicReference status =
new AtomicReference(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 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 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 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算法。
相关代码地址:https://github.com/Justin02180218?tab=repositories
更多【分布式专辑】系列文章,请关注公众号