Elasticsearch Raft选举策略深度解析:基于源码的优化设计与实现
目录
- 引言
- 标准Raft协议概述
- Elasticsearch Raft实现架构
- 预投票机制(Pre-Vote)
- 投票配置与Quorum机制
- 选举策略优化
- Leader心跳与租约机制
- 故障检测与自动恢复
- 场景分析与流程图
- 性能与稳定性优化
- 总结
引言
Elasticsearch作为分布式搜索引擎,其集群的一致性和可用性至关重要。在Elasticsearch 7.0版本中,官方引入了基于Raft算法的集群协调机制,替换了之前的Zen Discovery。本文将深入分析Elasticsearch 9.0.2版本的Raft选举实现,通过源码解析揭示其相对于标准Raft协议的优化策略。
标准Raft协议概述
Raft协议核心组件
标准Raft协议包含以下核心概念:
- Term(任期):每个leader任期有唯一的term编号
- Log Entries(日志条目):包含状态机命令的日志
- Leader Election(leader选举):当leader失效时选举新的leader
- Log Replication(日志复制):leader将日志条目复制到followers
标准选举流程
- Follower超时:follower在election timeout内未收到leader心跳
- 转为Candidate:递增term,投票给自己,请求其他节点投票
- 收集投票:获得大多数节点投票则成为leader
- 开始任期:发送心跳建立权威
Elasticsearch Raft实现架构
核心组件架构
// 协调器 - ES Raft实现的核心
public class Coordinator extends AbstractLifecycleComponent {
private final ElectionStrategy electionStrategy;
private final PreVoteCollector preVoteCollector;
private final PeerFinder peerFinder;
private final LeaderChecker leaderChecker;
private final FollowersChecker followersChecker;
// ...
}
// 协调状态 - 实现Raft状态机
public class CoordinationState {
private VoteCollection joinVotes;
private boolean electionWon;
private VotingConfiguration lastCommittedConfiguration;
private VotingConfiguration lastAcceptedConfiguration;
// ...
}
关键设计理念
ES的Raft实现有以下独特设计:
- 预投票机制:避免网络分区导致的无效选举
- 灵活的投票配置:支持动态调整quorum大小
- 基于版本的一致性:结合cluster state version进行一致性检查
- 健康状态集成:考虑节点健康状态进行选举决策
预投票机制(Pre-Vote)
标准Raft的问题
标准Raft协议存在一个问题:当网络分区的节点重新加入时,可能会触发不必要的选举,导致集群不稳定。
ES的Pre-Vote解决方案
ES引入了预投票机制,在正式选举前先进行预投票:
public class StatefulPreVoteCollector extends PreVoteCollector {
// 预投票请求处理
private PreVoteResponse handlePreVoteRequest(final PreVoteRequest request) {
updateMaxTermSeen.accept(request.getCurrentTerm());
// 检查节点健康状态
if (nodeHealthService.getHealth().getStatus() == UNHEALTHY) {
throw new NodeHealthCheckFailureException("rejecting on unhealthy node");
}
// 如果已有leader,拒绝预投票
if (leader != null && !leader.equals(request.getSourceNode())) {
throw new CoordinationStateRejectedException("rejecting as there is already a leader");
}
return response;
}
}