Elasticsearch的选主流程分析

猩球驿站 6月前 ⋅ 159 阅读

Elasticsearch在搜索领域中市场比较大,除了产品功能外,其产品设计也是我们研究的重点,本文主要研究Elasticsearch5.6.12的选主流程。

1. 先说说Bully选主算法

在分析源码之前,如果我们提前看下官方文档对架构的介绍会起到事半公倍是效果,因为Elasticsearch选主算法是基于Bully的改造,因此我们先简单看下Bully算法。

1.1 bully算法是干什么的

Bully算法是分布式系统中的一个选主算法,在系统需要选主或者主节点挂掉的时候,这个算法可以选举新的主节点,并且让其它节点知道谁是主节点。

1.2 bully算法的使用场景

根据bully算法的定位,目前用的比较多的场景有2个:

  • 集群管理

    Bully算法被广泛应用于分布式系统,通过Bully算法选取主节点来管理整个集群的状态,分配任务,确保集群稳定运行。

  • 故障恢复

    在主节点出现故障时,Bully算法可以快速选取新的主节点替代,确保集群的可用性。

1.3 Bully算法的原理

​ 通过Bully算法的选主过程,每个节点都可以发起选主,每个节点同一时刻只能发起一次。正如Bully算法的名字,算法以“恃强”原则,根据节点定义的优先级ID,取ID最大值的节点作为主节点。该选举过程分为3个节点:

  • 初始化过程

    在该阶段,当前节点探测到失效的主节点。

  • 选主过程

    当前节点向小于其ID的所有节点发送“election”消息,如果当前节点没有收到“alive”消息,则该节点会成为主节点。否则,当前节点会被返回“alive”消息的节点取代,继续发起选主过程。

  • 通知过程

    新的主节点选定,主节点会发送“victory”消息给其它节点,告知新的主节点信息。

1.4 Bully算法的局限性

​ Bully算法整个选主过程非常简单高效,但可以发现,因为每次选主要取最大ID的节点,如果在网络不稳定时,最大ID节点频繁出现宕机加入集群时,会触发集群频繁选主。此外,该算法在出现网络分区时,因为各分区在局部进行选主,会产生脑裂问题。最后,在集群规模比较大时,大量的“election”,“alive”消息造成的网络拥塞也是在实际场景中需要考虑的点。

2. Elasticsearch的选主过程

​ ES的选主过程是基于Bully算法的,正如上文讲述的Bully算法的局限性。ES在此进行了一些改进,下面我们从源码层面简单看看ES的整个选主过程。整体来说,ES选主过程主要涉及两个类:ZenDiscovery和ElectMasterService/MasterCandidate。

​ ZenDiscovery: 确定触发选主的条件、判断哪些节点参与选主。

​ ElectMasterService/MasterCandidate: 确定如何选取候选节点中的master。

​ 下面具体展开:

​ 首先确定参与选主的节点,在这里对于参与选主的节点可以根据集群规模进行设置是否仅master节点参与选主。

List<ZenPing.PingResponse> fullPingResponses = pingAndWait(pingTimeout).toList();
// 可以根据masterElectionIgnoreNonMasters参数选择过滤非master属性的节点参与选主,加快选主过程
final List<ZenPing.PingResponse> pingResponses = filterPingResponses(fullPingResponses, masterElectionIgnoreNonMasters, logger);
List<ElectMasterService.MasterCandidate> masterCandidates = new ArrayList<>();
       for (ZenPing.PingResponse pingResponse : pingResponses) {
           if (pingResponse.node().isMasterNode()) {
               masterCandidates.add(new ElectMasterService.MasterCandidate(pingResponse.node(), pingResponse.getClusterStateVersion()));
           }
       }

根据目前是否存在活跃的master节点确定是否开启选主

        //没有活跃的master
        if (activeMasters.isEmpty()) {
            //候选的master节点过半(配置的master节点总数)
            if (electMaster.hasEnoughCandidates(masterCandidates)) {
                final ElectMasterService.MasterCandidate winner = electMaster.electMaster(masterCandidates);
                logger.trace("candidate {} won election", winner);
                return winner.getNode();
            }
            }

在这里开始进行ElectMasterService#electMaster的过程,这里实现怎么从众多候选者中选出master节点,主要还是根据集群的最新状态和节点ID的最大值确定。

    public MasterCandidate electMaster(Collection<MasterCandidate> candidates) {
        assert hasEnoughCandidates(candidates);
        List<MasterCandidate> sortedCandidates = new ArrayList<>(candidates);
        sortedCandidates.sort(MasterCandidate::compare);
        return sortedCandidates.get(0);
    }
    
      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) {
                //其次看节点的ID
                ret = compareNodes(c1.getNode(), c2.getNode());
            }
            return ret;
        }
    

选主完成后告诉其它节点

 public void onElectedAsMaster(ClusterState state) {
                            joinThreadControl.markThreadAsDone(currentThread);
                            // we only starts nodesFD if we are master (it may be that we received a cluster state while pinging)
                            nodesFD.updateNodesAndPing(state); // start the nodes FD
                        }

3. ES选主的改进点

从整个代码分析来看,ES中Bully算法的实现上有通过过半以及候选节点过滤原则来避免Bully算法本身的局限性。


全部评论: 0

    我有话说: