🌈 我是“没事学AI”,meishixueai, 欢迎咨询、交流,共同学习:
👁️ 【关注】我们一起挖 AI 的各种门道,看看它还有多少新奇玩法等着咱们发现
👍 【点赞】为这些有用的 AI 知识鼓鼓掌,让更多人知道学 AI 也能这么轻松
🔖 【收藏】把这些 AI 小技巧存起来,啥时候想练手了,翻出来就能用
💬 【评论】说说你学 AI 时的想法和疑问,让大家的思路碰出更多火花
👉 关注获取更多AI技术干货,点赞/收藏备用,欢迎评论区交流学习心得! 🚀
目录
一、Raft算法概述
1.1 算法定位
Raft是一种分布式一致性算法,旨在解决分布式系统中多个节点之间的数据同步问题。它通过简洁的选举机制和日志复制策略,确保在节点故障等异常情况下,系统仍能保持数据一致性。在分布式存储(如etcd)、服务发现、分布式锁等场景中被广泛应用。
1.2 核心模块构成
Raft算法主要包含三大核心模块:
- 角色与任期管理
- 选举机制
- 日志复制
二、角色与任期管理
2.1 角色定义
Raft集群中的节点存在三种角色:
- Leader(领导者):负责接收客户端请求,向Follower同步日志,维护集群一致性。
- Follower(跟随者):被动接收Leader的消息,不主动发起请求,若长时间未收到Leader心跳则转为Candidate。
- Candidate(候选者):当Follower检测到Leader故障时,转为Candidate并发起选举,争取成为新的Leader。
2.2 任期(Term)机制
- 任期是一个递增的整数,每个任期对应一次选举过程。
- 任期开始时进行选举,若某个Candidate赢得选举则成为该任期的Leader,直至任期结束或Leader故障。
- 节点间通过交换包含任期号的消息来维持任期同步,高任期号的消息会覆盖低任期号的状态。
三、选举机制核心原理
3.1 选举触发条件
Follower会维护一个选举超时计时器(通常为150-300ms的随机值),若在超时时间内未收到Leader的心跳消息(包含当前任期号的空日志条目),则触发选举:
- Follower将自身任期号加1,转为Candidate。
- 向集群中其他节点发送投票请求(RequestVote RPC)。
3.2 投票规则
Candidate在发起投票请求时,会携带自身的任期号和最后一条日志的索引与任期。其他节点在收到请求后,遵循以下规则投票:
- 仅对任期号不低于自身当前任期的请求进行处理。
- 每个任期内最多为一个Candidate投票,遵循“先到先得”原则。
- 若Candidate的日志完整性(最后一条日志的任期和索引)不低于自身,则投票支持,否则拒绝。
3.3 选举结果判定
- 成功:Candidate在选举超时时间内收到集群中过半数节点的投票,则当选为新Leader,并立即向所有节点发送心跳消息,重置其他节点的选举超时计时器。
- 失败:若在选举超时前未获得足够选票(如出现多个Candidate且票数分散),则重新随机设置选举超时时间,进入下一轮选举。
四、选举机制示例代码实现
4.1 核心数据结构定义
/**
* Raft节点状态类
*/
public class RaftNode {
// 节点角色:0-Follower,1-Candidate,2-Leader
private int role = 0;
// 当前任期号
private int currentTerm = 0;
// 投票给的节点ID(-1表示未投票)
private int votedFor = -1;
// 选举超时时间(150-300ms随机值)
private int electionTimeout;
// 最后一次收到心跳的时间戳
private long lastHeartbeatTime;
// 节点ID
private int nodeId;
// 集群中其他节点
private List<RaftNode> peers;
// 日志列表(简化版,仅记录索引和任期)
private List<LogEntry> logEntries = new ArrayList<>();
// 日志条目类
static class LogEntry {
int term; // 日志产生时的任期
int index; // 日志索引
// 构造函数及getter/setter省略
}
// 构造函数:初始化节点ID、集群节点列表、随机选举超时时间
public RaftNode(int nodeId, List<RaftNode> peers) {
this.nodeId = nodeId;
this.peers = peers;
this.electionTimeout = 150 + new Random().nextInt(150); // 150-300ms
this.lastHeartbeatTime = System.currentTimeMillis();
}
}
4.2 选举触发与投票处理
/**
* 检查是否需要发起选举
*/
public void checkElection() {
long currentTime = System.currentTimeMillis();
// 若为Follower且超过选举超时时间未收到心跳,转为Candidate并发起选举
if (role == 0 && currentTime - lastHeartbeatTime > electionTimeout) {
startElection();
}
}
/**
* 发起选举
*/
private void startElection() {
role = 1; // 转为Candidate
currentTerm++; // 任期加1
votedFor = nodeId; // 给自己投票
int voteCount = 1; // 初始票数为1(自己的票)
lastHeartbeatTime = System.currentTimeMillis(); // 重置计时
// 向所有其他节点发送投票请求
for (RaftNode peer : peers) {
if (peer.nodeId == nodeId) continue;
VoteResponse response = requestVote(peer);
if (response.isGranted()) {
voteCount++;
// 获得过半数选票,当选Leader
if (voteCount > peers.size() / 2) {
becomeLeader();
break;
}
}
}
}
/**
* 发送投票请求
*/
private VoteResponse requestVote(RaftNode peer) {
// 构建投票请求(包含当前任期、最后一条日志信息)
VoteRequest request = new VoteRequest(currentTerm, nodeId,
getLastLogIndex(), getLastLogTerm());
return peer.handleVoteRequest(request);
}
/**
* 处理投票请求
*/
public VoteResponse handleVoteRequest(VoteRequest request) {
VoteResponse response = new VoteResponse(currentTerm, false);
// 若请求任期小于当前任期,拒绝投票
if (request.getTerm() < currentTerm) {
return response;
}
// 若请求任期大于当前任期,更新自身任期并重置投票状态
if (request.getTerm() > currentTerm) {
currentTerm = request.getTerm();
role = 0; // 降为Follower
votedFor = -1;
}
// 若未投票且请求者日志完整性不低于自身,同意投票
if (votedFor == -1 && request.getLastLogTerm() >= getLastLogTerm() &&
(request.getLastLogTerm() > getLastLogTerm() || request.getLastLogIndex() >= getLastLogIndex())) {
votedFor = request.getCandidateId();
response.setGranted(true);
}
return response;
}
/**
* 成为Leader
*/
private void becomeLeader() {
role = 2;
// 立即发送心跳消息
sendHeartbeat();
}
/**
* 发送心跳消息
*/
private void sendHeartbeat() {
for (RaftNode peer : peers) {
if (peer.nodeId == nodeId) continue;
peer.handleHeartbeat(currentTerm);
}
}
/**
* 处理心跳消息
*/
public void handleHeartbeat(int term) {
if (term >= currentTerm) {
currentTerm = term;
role = 0; // 维持Follower角色
votedFor = -1;
lastHeartbeatTime = System.currentTimeMillis(); // 重置选举超时计时器
}
}
// 辅助方法:获取最后一条日志的索引和任期
private int getLastLogIndex() {
return logEntries.isEmpty() ? 0 : logEntries.get(logEntries.size() - 1).index;
}
private int getLastLogTerm() {
return logEntries.isEmpty() ? 0 : logEntries.get(logEntries.size() - 1).term;
}
// 投票请求与响应类定义
static class VoteRequest {
private int term;
private int candidateId;
private int lastLogIndex;
private int lastLogTerm;
// 构造函数及getter省略
}
static class VoteResponse {
private int term;
private boolean granted;
// 构造函数、getter及setter省略
}
4.3 选举流程示例
假设集群包含3个节点(Node1、Node2、Node3),初始状态均为Follower:
- 若Leader故障,所有Follower的选举超时计时器开始计时。
- 假设Node1的超时时间最先到期,它转为Candidate,任期变为1,向Node2和Node3发送投票请求。
- Node2和Node3检查到请求任期为1(高于自身初始任期0),且未投票,同时Node1的日志完整性符合要求,因此分别向Node1投赞成票。
- Node1收到2票(超过半数3/2=1.5),成功当选Leader,立即向Node2和Node3发送心跳消息,重置它们的选举超时计时器。
- 若出现多个Candidate(如Node1和Node2同时超时),可能导致票数分散,此时所有Candidate会重新设置随机超时时间,进入下一轮选举,最终由超时时间最短的Candidate赢得选举。