分布式选举-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算法。

相关代码地址:https://github.com/Justin02180218?tab=repositories


更多【分布式专辑】系列文章,请关注公众号

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值