天天看點

Elasticsearch中的Zen Discovery選主流程背景選主過程

文章目錄

  • 背景
    • 為什麼使用主從模式?
    • 選舉算法
    • 什麼時候觸發選主?
  • 選主過程
    • 選舉臨時Master
    • 投票與得票的實作
    • 确立Master或加入叢集
    • 選舉完成

elasticsearch中的Discovery子產品負責發現叢集中的節點,以及選擇主節點。在Elasticsearch 7.0以前,内置的實作稱為Zen Discovery。Zen Discovery封裝了節點發送(ping)、選主等實作過程。

背景

為什麼使用主從模式?

ES的典型場景中的另一個簡化是叢集中沒有那麼多節點。通常,節點的數量遠遠小于單個節點能夠維護的連接配接數,并且網絡環境不必經常處理節點的加入和離開。

選舉算法

Bully算法是Leader選舉的基本算法之一。它假定所有節點都有一個唯一的ID,使用該ID對節點進行排序。任何時候的目前Leader都是參與叢集的最高ID節點。該算法的優點是易于實作。

但是,當擁有最大ID的節點處于不穩定狀态的場景下會有問題。例如,Master負載過重而假死,叢集擁有第二大ID的節點被選為新主,這時原來的Master恢複,再次被選為新主,然後又假死…。ES通過推遲選舉,直到目前的Master失效來解決上述問題,隻要目前主節點不挂掉,就不重新選主。

但是容易産生腦裂(雙主),為此,再通過“法定得票人數過半”解決腦裂問題。

7.X之後的ES,采用一種新的選主算法,實際上是Raft的實作,但并非嚴格按照Raft論文實作,而是做了一些調整。

什麼時候觸發選主?

主要在以下三個場景會觸發選主:

  1. 叢集啟動初始化;
  2. 叢集的Master崩潰的時候;
  3. 任何一個節點發現目前叢集中的Master節點沒有得到

    n/2 + 1

    節點認可的時候。

不執行失效檢測可能會産生腦裂(雙主或多主),是以需要啟動兩種失效探測器:

  • 在Master節點,啟動NodesFaultDetection,簡稱NodesFD。定期探測加入叢集的節點是否活躍。檢查一下目前叢集總節點數是否達到法定節點數(過半),如果不足,則會放棄Master身份,重新加入叢集。
  • 在非Master節點啟動MasterFaultDetection,簡稱MasterFD。定期探測Master節點是否活躍。探測Master離線的處理很簡單,重新加入叢集。本質上就是該節點重新執行一遍選主的流程。
NodesFaultDetection和MasterFaultDetection都是通過定期(預設為1秒)發送的ping請求探測節點是否正常的,當失敗達到一定次數(預設為3次),或者收到來自底層連接配接子產品的節點離線通知時,開始處理節點離開事件。

選主過程

ZenDiscovery選主的整體流程可以概括為:選舉臨時Master,如果本節點當選,則等待确立Master,如果其他節點當選,則嘗試加入叢集,最後選舉完成。流程圖如下:

Elasticsearch中的Zen Discovery選主流程背景選主過程

選舉臨時Master

通過ZenDiscovery#findMaster确定臨時master。

為什麼是臨時Master?因為還需要等待下一個步驟,該節點的得票數足夠時,才确立為真正的Master。
private DiscoveryNode findMaster() {
    // 1. PING 所有節點,擷取各節點儲存的叢集資訊
    List<ZenPing.PingResponse> fullPingResponses = pingAndWait(pingTimeout).toList();
    if (fullPingResponses == null) {
        logger.trace("No full ping responses");
        return null;
    }

	// 2. 由于上面是擷取的其他節點的資訊,這裡需要将本節點加上
    final DiscoveryNode localNode = transportService.getLocalNode();
    // add our selves
    assert fullPingResponses.stream().map(ZenPing.PingResponse::node)
        .filter(n -> n.equals(localNode)).findAny().isPresent() == false;
    fullPingResponses.add(new ZenPing.PingResponse(localNode, null, this.clusterState()));


    // 3. 若設定了 master_election_ignore_non_masters 則去掉沒有 master 資格(node.master: false)的節點
    final List<ZenPing.PingResponse> pingResponses = filterPingResponses(fullPingResponses, masterElectionIgnoreNonMasters, logger);

	// 4. 将各節點認為的 master 加入 activeMasters 清單
    List<DiscoveryNode> activeMasters = new ArrayList<>();
    for (ZenPing.PingResponse pingResponse : pingResponses) {
        // 避免未經其他節點檢查就将本節點選為 master
        if (pingResponse.master() != null && !localNode.equals(pingResponse.master())) {
            activeMasters.add(pingResponse.master());
        }
    }

    // 5. 将 PING 到的具有 master 資格的節點加入 masterCandidates 清單作為候選節點
    List<ElectMasterService.MasterCandidate> masterCandidates = new ArrayList<>();
    for (ZenPing.PingResponse pingResponse : pingResponses) {
        if (pingResponse.node().isMasterNode()) {
            masterCandidates.add(new ElectMasterService.MasterCandidate(pingResponse.node(), pingResponse.getClusterStateVersion()));
        }
    }

    if (activeMasters.isEmpty()) {
		// 6. 沒有活躍的 master
        if (electMaster.hasEnoughCandidates(masterCandidates)) {
			// 7. 擁有足夠的候選節點,則進行選舉
            final ElectMasterService.MasterCandidate winner = electMaster.electMaster(masterCandidates);
            return winner.getNode();
        } else {
            // 8. 無法選舉,無法得到 master,傳回 null
            return null;
        }
    } else {
		// 9. 有活躍的 master,從 activeMasters 中選擇
        assert !activeMasters.contains(localNode) :
            "local node should never be elected as master when other nodes indicate an active master";
        // lets tie break between discovered nodes
        return electMaster.tieBreakActiveMasters(activeMasters);
    }
}
           

臨時Master的選舉過程如下:

  1. “ping”所有節點,擷取節點清單fullPingResponses,ping結果不包含本節點,把本節點單獨添加到fullPingResponses中。
  2. 建構兩個清單。activeMasters清單:存儲叢集目前活躍Master清單。周遊第一步擷取的所有節點,将每個節點所認為的目前Master節點加入activeMasters清單中(不包括本節點)。在周遊過程中,如果配置了discovery.zen.master_election.ignore_non_master_pings為true(預設為false),而節點又不具備Master資格,則跳過該節點。masterCandidates清單:存儲master候選者清單。周遊第一步擷取清單,去掉不具備Master資格的節點,添加到這個清單中。
    Elasticsearch中的Zen Discovery選主流程背景選主過程
  3. 如果activeMaster為空,則從masterCandidates中選舉,結果可能選舉成功,也可能選舉失敗。如果不為空,則從acitveMasters中選擇最合适的作為Master。
  • 從masterCandidates中選主:與選主的具體細節實作封裝在ElectMasterService類中。首先需要判斷 目前候選者人數是否達到法定人數,否則選主失敗。接着從候選者中選一個出來做Master,優先把叢集狀态版本号高的節點放在前面。
  • 從activeMasters清單中選擇:清單存儲着叢集目前存在活躍的Master,從中選擇一個作為選舉結果。上面第9步通過 ElectMasterService::tieBreakAcitveMasters使用ElectMasterService::compareNodes的規則從activeMasters中選擇。

投票與得票的實作

在ES中,發送投票就是發送加入叢集(JoinRequest)請求。

// ZenDiscovery#joinElectedMaster,加入一個新選出來的主節點
private boolean joinElectedMaster(DiscoveryNode masterNode) {
    while (true) {
        membership.sendJoinRequestBlocking(masterNode, transportService.getLocalNode(), joinTimeout);
	}
}
           

得票就是申請加入該節點的請求的數量。收集投票,進行統計的實作在ZenDiscovery#handleJoinRequest方法中,其又調用了NodeJoinController#handleJoinRequest方法,收到的連接配接被存儲到ElectionContext#joinRequestAccumulator中。當節點檢查收到的投票是否足夠時,就是檢查加入它的連接配接數是否足夠,其中會去掉沒有Master資格節點的投票。

public synchronized int getPendingMasterJoinsCount() {
    int pendingMasterJoins = 0;
	// 周遊目前收到的join請求
    for (DiscoveryNode node : joinRequestAccumulator.keySet()) {
		// 過濾不具備master資格的節點
        if (node.isMasterNode()) {
            pendingMasterJoins++;
        }
    }
    return pendingMasterJoins;
}
           

确立Master或加入叢集

選舉出的臨時Master有兩種情況:該臨時Master是本節點或非本節點。為此單獨處理。現在準備向其發送投票。根據選出的臨時 master是否本節點等待或投票

// ZenDiscovery#innerJoinCluster
// join線程的主函數。該函數被用來保證是加入叢集或是當失敗時建立一個新的join線程。
private void innerJoinCluster() {
    if (transportService.getLocalNode().equals(masterNode)) {
		// 選出的臨時 master 是本節點,則等待被選舉為真正的 master
        final int requiredJoins = Math.max(0, electMaster.minimumMasterNodes() - 1); // we count as one
        nodeJoinController.waitToBeElectedAsMaster(requiredJoins, masterElectionWaitForJoinsTimeout,
                new NodeJoinController.ElectionCallback() {
                    @Override
                    public void onElectedAsMaster(ClusterState state) {
						// 成功被選舉為 master
                        synchronized (stateMutex) {
                            joinThreadControl.markThreadAsDone(currentThread);
                        }
                    }

                    @Override
                    public void onFailure(Throwable t) {
                        // 等待逾時,重新開始選舉流程
                        synchronized (stateMutex) {
                            joinThreadControl.markThreadAsDoneAndStartNew(currentThread);
                        }
                    }
                }

        );
    } else {
        // 選出的臨時 master 不是本節點,不再接收其他節點的 join 請求
        nodeJoinController.stopElectionContext(masterNode + " elected");

        // 向臨時節點發送 join 請求(投票),被選舉的臨時 master 在确認成為 master 并釋出新的叢集狀态後才會傳回
        final boolean success = joinElectedMaster(masterNode);

		// 成功加入之前選擇的臨時 master 節點,則結束線程,否則重新選舉
        synchronized (stateMutex) {
            if (success) {
                DiscoveryNode currentMasterNode = this.clusterState().getNodes().getMasterNode();
                if (currentMasterNode == null) {
                    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);
            }
        }
    }
}
           

如果臨時Master是本節點:

  1. 等待足夠多的具備Master資格的節點加入本節點(投票達到法定人數),以完成選舉。
  2. 逾時(預設為30秒,可配置)後還沒有滿足數量的join請求,則選舉失敗,需要進行新一輪選舉。
  3. 成功後釋出新的clusterState.

如果其他節點被選為Master:

  1. 不再接受其他節點的join請求。
  2. 向Master發送加入請求,并等待回複。逾時時間預設為1分鐘(可配置),如果遇到異常,則預設重試3次(可配置)。這個步驟在joinElectedMaster方法中實作。
  3. 最終當選的Master會先釋出叢集狀态,才确認客戶的join請求,是以,joinElectedMaster傳回代表收到了join請求的确認,并且已經收到了叢集狀态。本步驟檢查收到的叢集狀态中的Master節點如果為空,或者當選的Master不是之前選擇的節點,則重新選舉。

選舉完成

NodeJoinController::checkPendingJoinsAndElectIfNeeded在節點獲得足夠的得票時使節點成為正式master,并釋出新的叢集狀态

private synchronized void checkPendingJoinsAndElectIfNeeded() {
    // 計算節點得票數
    final int pendingMasterJoins = electionContext.getPendingMasterJoinsCount();
    if (electionContext.isEnoughPendingJoins(pendingMasterJoins) == false) {
        ...
    } else {
        // 得票數足夠,成為 master
        electionContext.closeAndBecomeMaster();
    }
}

public synchronized int getPendingMasterJoinsCount() {
    int pendingMasterJoins = 0;
    // 統計節點得票數,隻計算擁有 master 資格節點的投票
    for (DiscoveryNode node : joinRequestAccumulator.keySet()) {
        if (node.isMasterNode()) {
            pendingMasterJoins++;
        }
    }
    return pendingMasterJoins;
}
           

繼續閱讀