选主
选举算法
角色定义
这里的选主为什么提角色? 是因为不同角色在选主中起到不同的作用.master的非voting_only
节点不但参与投票同时还可以参与竞选, master 的voting_only
角色仅投票不参与竞选,其余角色不参与.
支持的角色
master
data
data_content
data_hot
data_warm
data_cold
data_frozen
ingest
ml
remote_cluster_client
transform
如果不设置node.roles
则默认有所有角色, 如果配置了,以配置的为准.
remote_cluster_client
跨集群搜索和副本角色
只有master角色才允许voting_only
高可用集群最少需要3个master
data角色如果分层的话,又可以分为
data_content,data_hot,data_warm,data_cold,或data_frozen
不同的层在存储时间,是否压缩,访问性能等进行差异化管理.以达到空间和性能的最优化.
法定人数机制
. 法定人数一般大于半数,主要目的是提高集群健壮性同时要避免脑裂问题.这个数值一般是多少? 一般
- 奇数投票节点
- n/2+1个quorum
正常情况quorum等于候选节点+投票节点之和
选举完成之后提交集群状态
选主协议
选主协议应该包含至少4个部分
-
检测
-
发起
-
选主核心逻辑(互斥和一致性)
-
发布状态
先发起的会成功,如果同时发起则失败.依据是什么? 时间戳?
为了避免选举冲突每个节点发起选举流程都是随机调度.
检测
一般提供两种, 一种检测master, 一种检测node, 一般通过ping,默认1s,连续失败3次判定节点失效.
发起
当检测判定主节点失效则会发起投票
选主核心逻辑
如何避免冲突?
- 如果发现节点不是自己,则加入. 所以即使同时发起选举也不影响,大家会加入相同的集群,除非发生脑裂分区等异常
如何避免脑裂?
- 检查自己集群法定人数如果自己集群法定人数(过半),则放弃,重新加入其他集群.这个主要是避免脑裂
选举常见问题
增减节点同步信息未完成发生选举
如果增减节点同步信息未完成发生选举,是否有不一致和集群可用性问题
投票设置
增减节点动态调整
增减节点动态调整投票配置,以增强集群的弹性
这个一般发生增减节点时,通过自动通信机制来实现.
节点数
一般需要奇数
如果不是奇数它会取消一个节点的选举权
奇数某些情况可以提高网络分区可用性问题
比如4个节点集群, 会有3个候选节点, 法定投票人数最少是2,那么一旦分区之后,至少有个一个分区包含2个节点,这时候仍然是可用的
如果每个节点配置的集群期望节点数不一致会导致什么问题?
数据丢失.这种场景一般发生在初次启动时, 已经启动之后因为有节点发现协议,它会慢慢的读取到正确的节点.
对集群来说它自己没法提供一个完全安全的引导启动配置, 即使所有节点的引导启动配置一致,也无法保证集群完全安全的启动.比如这样一个有4台node的集群,他们通过自动化同时启动很可能造成分区.因为4个node,只能有奇数(3)个node有投票权, 那么2个node投票就可以形成集群(如果是通过集群动态调整投票节点的话, 如果你固定配置某个节点没有投票权当然可以避免)
所以quorum是集群启动的必选项.
选主流程
选举临时Master->[本节点是Master?确人orelse加入]->启动NodeFD OR 启动MasterFD
为什么选举临时Master
需要足够票数
临时Master选举流程
ping所有节点->获取所有节点reposne+本节点也加入response->构建两个列表activeMaster和(活跃的Master节点)
正常情况activeMaster只能有一个,这里为什么是列表, 因为可能存在不一致情况.并且构建active master过程中如果节点不具备资格根据配置ignore_non_master_pings
会忽略.
另一个要维护的列表是masterCandidates
两个列表构建完成之后, 如果没有active master则从候选master列表选择进行选举
选举临时Master
它的选主逻辑很简单就是从节点找出版本最高的然后最小的节点. 源码对应的方法是electMaster
public MasterCandidate electMaster(Collection<MasterCandidate> candidates) {
assert hasEnoughCandidates(candidates);
List<MasterCandidate> sortedCandidates = new ArrayList<>(candidates);
sortedCandidates.sort(MasterCandidate::compare);
return sortedCandidates.get(0);
}
// 取version最大及节点最小的作为主节点
public static int compare(MasterCandidate c1, MasterCandidate c2) {
// we explicitly swap c1 and c2 here. the code expects "better" is lower in a sorted
// list, so if c2 has a higher cluster state version, it needs to come first.
int ret = Long.compare(c2.clusterStateVersion, c1.clusterStateVersion);
if (ret == 0) {
ret = compareNodes(c1.getNode(), c2.getNode());
}
return ret;
}
但是从源码我们看出来, 它没有判断角色,其实候选master是需要对应角色的. 这块的逻辑是怎么样的?
/** master nodes go before other nodes, with a secondary sort by id **/
private static int compareNodes(DiscoveryNode o1, DiscoveryNode o2) {
// 优先选择是master node的节点
if (o1.isMasterNode() && o2.isMasterNode() == false) {
return -1;
}
if (o1.isMasterNode() == false && o2.isMasterNode()) {
return 1;
}
// 如果都是或者都不是再比较节点id,取小的
return o1.getId().compareTo(o2.getId());
}
那么我们可能会有个疑问不具备master角色的节点为什么有资格参与竞选主节点呢? 其实这部分逻辑主要是为了列表排序,并不是真正的选主,选主逻辑会判断它是否具备资格. 那为什么不在这里直接过滤呢?
我能回答的是,它有过滤但是没有在这里过滤
ZenDiscovery#handleJoinRequest->NodeJoinController.ElectionContext#getPendingMasterJoinsCount
public synchronized int getPendingMasterJoinsCount() {
int pendingMasterJoins = 0;
for (DiscoveryNode node : joinRequestAccumulator.keySet()) {
// 这个逻辑是用来判定候选节点数是否达到法定人数的, 在这里排除掉了非master节点, 但是如果非master节点依然在候选列表中
if (node.isMasterNode()) {
pendingMasterJoins++;
}
}
return pendingMasterJoins;
}
如果候选节点是自己
(1) 等待节点加入自己集群
(2) 超时重新选举
(3) 成功发布通知
private void innerJoinCluster() {
...
// 如果临时Master是本节点
if (transportService.getLocalNode().equals(masterNode)) {
final int requiredJoins = Math.max(0, electMaster.minimumMasterNodes() - 1); // we count as one
// 等待足够多的具备Master资格的节点加入本节点(投票达到法定人数),以完成选举。
nodeJoinController.waitToBeElectedAsMaster(
requiredJoins,
// 超时(默认为30秒,可配置)后还没有满足数量的join请求,则选举失败,需要进行新一轮选举。
masterElectionWaitForJoinsTimeout,
new NodeJoinController.ElectionCallback() {
// 成功后发布新的clusterState。
@Override
public void onElectedAsMaster(ClusterState state) {
synchronized (stateMutex) {
joinThreadControl.markThreadAsDone(currentThread);
}
}
@Override
public void onFailure(Throwable t) {
...
}
}
);
} else {
...
}
}
如果不是自己
如果其他节点被选为Master:
(1) 停止接受节点加入自己集群请求
(2) 发起加入其他节点集群请求文章来源:https://www.toymoban.com/news/detail-604603.html
(3)等待回复文章来源地址https://www.toymoban.com/news/detail-604603.html
private void innerJoinCluster() {
...
if (transportService.getLocalNode().equals(masterNode)) {
...
} else {
// process any incoming joins (they will fail because we are not the master)
// 不再接受其他节点的join请求
nodeJoinController.stopElectionContext(masterNode + " elected");
// send join request
//向Master发送加入请求,并等待回复。超时时间默认为1分钟(可配置),如果遇到异常,则默认重试3次(可配置)。这个步骤在joinElectedMaster方法中实现。
final boolean success = joinElectedMaster(masterNode);
// 最终当选的Master会先发布集群状态,才确认客户的join请求,因此,joinElectedMaster返回代表收到了join请求的确认,并且已经收到了集群状态。本步骤检查收到的集群状态中的Master节点如果为空,或者当选的Master不是之前选择的节点,则重新选举。
synchronized (stateMutex) {
if (success) {
DiscoveryNode currentMasterNode = this.clusterState().getNodes().getMasterNode();
if (currentMasterNode == null) {
// Post 1.3.0, the master should publish a new cluster state before acking our join request. we now should have
// a valid master.
logger.debug("no master node is set, despite of join request completing. retrying pings.");
joinThreadControl.markThreadAsDoneAndStartNew(currentThread);
} else if (currentMasterNode.equals(masterNode) == false) {
// update cluster state
joinThreadControl.stopRunningThreadAndRejoin("master_switched_while_finalizing_join");
}
joinThreadControl.markThreadAsDone(currentThread);
} else {
// failed to join. Try again...
joinThreadControl.markThreadAsDoneAndStartNew(currentThread);
}
}
}
}
到了这里,关于elastic elasticsearch 源码解析之选主选举过程的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!