elastic elasticsearch 源码解析之选主选举过程

选主

选举算法

角色定义

这里的选主为什么提角色? 是因为不同角色在选主中起到不同的作用.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) 发起加入其他节点集群请求

(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);
                }
            }
        }
    }

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Elasticsearch是一个基于Lucene的分布式搜索和分析引擎,其源码解析与优化可以帮助我们更好地理解和利用Elasticsearch的功能和性能。下面是对Elasticsearch源码解析与优化实战的一些探讨: 1. 源码解析: - 阅读和理解Elasticsearch的核心模块、数据结构和算法,如索引、搜索、分片、路由等。 - 掌握Elasticsearch的分布式架构,包括节点之间的通信、协调和负载均衡等机制。 - 理解Elasticsearch的查询语法和DSL,了解查询执行的流程和原理。 - 深入研究Elasticsearch使用的Lucene库,掌握其索引和搜索的底层实现细节。 2. 性能优化: - 通过源码分析,找到性能瓶颈所在,如磁盘IO、网络通信、GC等。 - 针对性优化各个模块的代码,比如索引模块、搜索模块、聚合模块等。 - 优化查询性能,例如通过合理的查询DSL设计、索引优化、缓存策略等提升查询效率。 - 配置优化,如调整线程池大小、内存限制、缓冲区设置等,以平衡性能和资源消耗之间的关系。 3. 扩展与定制: - 根据业务需求,定制Elasticsearch的功能和行为,比如自定义插件、脚本、分析器等。 - 扩展Elasticsearch的集群规模和数据容量,实现水平扩展和负载均衡。 - 集成其他工具和技术,如Logstash、Kibana、Beats等,以构建完整的数据处理和可视化平台。 需要注意的是,对于大部分用户而言,直接阅读和修改Elasticsearch源码并不是必要的,因为Elasticsearch提供了丰富的配置选项和插件机制,可以通过合理的配置和扩展来满足大部分需求。源码解析与优化更多地是为那些对Elasticsearch内部工作原理有深入需求的开发人员和运维人员而言。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值