Elasticsearch 7.x 选举算法改为基于 Raft 的实现,与标准 Raft 相比,最大的区别是允许选民可以投多票,当产生多个主节点的时候,让最后一个当选,这样,可以更快地选出主节点。但是这种机制同样也有缺点,就是会使竞选过程比较激烈。特别是当集群节点数量比较多的时候,候选人反复竞争可能会持续很长时间。
当遇到这种情况时,节点会有如下的日志:
master not discovered or elected yet,an election requires at least xx nodes
with ids from[] which is a quorum; discovery will continue using []
from hosts providers and [] from last-known cluster state; node term 14,
last-accepted version 71 in term 5
以及:
failed to join{...}
CoordinationStateRejectedException: incoming term 4996 does not match current term
但是这些报错和问题根因没有啥关系,探测到的节点已经能够达到 quorum,然后继续discovery,让人很费解。这时得把日志调到 DEBUG:
{
"persistent": {
"logger.org.elasticsearch.cluster.service.MasterService": "DEBUG",
"logger.org.elasticsearch.cluster.coordination": "DEBUG"
}
}
跟随报错日志”which is a quorum; discovery will“,从代码一路跟过去,发现只有becomeCandidate的时候才会触发,搜索 debug 日志”coordinator becoming CANDIDATE“,找到以下信息:
注意他上一个状态是FOLLOWER,在 Elasticsearch 的选举状态图里,只有加入集群才会切换到FOLLOWER状态。这说明有主节点被选出来了,继续搜 ”coordinator becoming FOLLOWER “,可以找到他切换到FOLLOWER时Leader的源地址:
然后看一下 Leader 节点的日志,发现有:
elected-as-master (.. nodes joined) 以及
failing [elected-as-master
可以看到节点起初被成功选为 Master,但是后来因为收到其他节点拉选票的RequestVote 请求(joinLeaderInTerm函数是对竞选请求的处理)取消集群状态的发布,切换到候选人状态。如果他成功发布了集群状态,新主就可以顺利当选了。
我们再观察候选人每次发起 RequestVote 的周期以及成功情况:
grep -E "starting election with|elected-as-master" logs/my-debug.log |less
发现他有时候甚至来不及被选为master,都没有走到发布集群状态的流程:
而来不及被选为 master 的原因是被其他候选人拉选票的请求打断:
在 Elasticsearch 的选举算法中,允许选民投多票,并让最后一个leader当选,这需要
候选人在拉选票的过程中,如果收到其他候选人的 RequestVote,则清空自己已经收到的选票
如果一个 leader 收到 RequestVote,则切换到候选人
决定竞选激烈程度的是几个超时时间控制的选举周期:
gracePeriod:固定的竞选时长限制,由cluster.election.duration配置,默认 500ms
maxTimeout:选举前的最大等待时间,由cluster.election.max_timeout配置,默认 10s
backoffTime:竞选失败进行重试时,用重试次数乘以 backoffTime,由cluster.election.back_off_time 配置,默认 100ms
候选人的竞选过程周期性执行,执行周期就是在cluster.election.duration的基础上加上不超过最大等待时间的随机值。
//重试次数,每次加 1
final long thisAttempt = attempt.getAndIncrement();
//最大延迟时间不超过cluster.election.max_timeout配置,每次递增cluster.election.back_off_time
final long maxDelayMillis = Math.min(maxTimeout.millis(), initialTimeout.millis() + thisAttempt * backoffTime.millis());
//执行延迟在cluster.election.duration基础上递增随机值
final long delayMillis = toPositiveLongAtMost(random.nextLong(), maxDelayMillis) + gracePeriod.millis();
threadPool.scheduleUnlessShuttingDown(TimeValue.timeValueMillis(delayMillis), Names.GENERIC, runnable);
这个延迟时间是一个比较小的时间,我们截取两个时间点可以大致看一下增长情况,经过 4 分钟左右,竞选周期从 1 秒内增长到 7 秒。这个过程是线性增长的。
[2021-02-18T00:13:48,869][DEBUG]scheduleNextElection{ delayMillis=658}
[2021-02-18T00:17:53,743][DEBUG]scheduleNextElection{ delayMillis=7597}
从上面实现可以看出,加大 cluster.election.duration可以降低选举激烈程度。
这里还有一个疑问,PreVote 是干什么去了,为什么没有拦截住?有两种情况:
选民收到首次集群状态,才认为集群存在 Leader 了,后续的 Prevote 返回 false,但是在竞争激烈的时候没有节点被选为 leader。
选民收到了首次集群状态,但在此之前又收到了其他节点的 RequestVote,导致自己的 term 更大了,首次集群状态因为 term 更低被忽略。如下图:
最后总结一下,虽然分析过程比较复杂,但是解决起来比较简单(不很完美):部署独立的主节点,并且可以考虑适当增大cluster.election.duration的配置。
关注本公众号
本文分享自微信公众号 - Elasticsearch 原理与实践(gh_81a98ec906ca)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。
来源:oschina
链接:https://my.oschina.net/u/3536667/blog/4956221