这里的选主为什么提角色? 是因为不同角色在选主中起到不同的作用.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
不同的层在存储时间,是否压缩,访问性能等进行差异化管理.以达到空间和性能的最优化.
. 法定人数一般大于半数,主要目的是提高集群健壮性同时要避免脑裂问题.这个数值一般是多少? 一般
正常情况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
需要足够票数
ping所有节点->获取所有节点reposne+本节点也加入response->构建两个列表activeMaster和(活跃的Master节点)
正常情况activeMaster只能有一个,这里为什么是列表, 因为可能存在不一致情况.并且构建active master过程中如果节点不具备资格根据配置ignore_non_master_pings
会忽略.
另一个要维护的列表是masterCandidates
两个列表构建完成之后, 如果没有active 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) 发起加入其他节点集群请求
(3)等待回复
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);
}
}
}
}