Elasticsearch分佈式一致性原理剖析之節點篇

前言

“Elasticsearch分佈式一致性原理剖析”系列將會對Elasticsearch的分佈式一致性原理進行詳細的剖析,介紹其實現方式、原理以及其存在的問題等(基於6.2版本)。

ES目前是最流行的分佈式搜索引擎系統,其使用Lucene作爲單機存儲引擎並提供強大的搜索查詢能力。學習其搜索原理,則必須瞭解Lucene,而學習ES的架構,就必須瞭解其分佈式如何實現,而一致性是分佈式系統的核心之一。

本篇將介紹ES的集羣組成、節點發現與Master選舉,錯誤檢測與擴縮容相關的內容。ES在處理節點發現與Master選舉等方面沒有選擇Zookeeper等外部組件,而是自己實現的一套,本文會介紹ES的這套機制是如何工作的,存在什麼問題。本文的主要內容如下:

  • ES集羣構成

  • 節點發現

  • Master選舉

  • 錯誤檢測

  • 集羣擴縮容

  • 與Zookeeper、raft等實現方式的比較

  • 小結

ES集羣構成

首先,一個Elasticsearch集羣(下面簡稱ES集羣)是由許多節點(Node)構成的,Node可以有不同的類型,通過以下配置,可以產生四種不同類型的Node:

conf/elasticsearch.yml:
    node.master: true/false
    node.data: true/false

四種不同類型的Node是一個node.master和node.data的true/false的兩兩組合。當然還有其他類型的Node,比如IngestNode(用於數據預處理等),不在本文討論範圍內。

當node.master爲true時,其表示這個node是一個master的候選節點,可以參與選舉,在ES的文檔中常被稱作master-eligible node,類似於MasterCandidate。ES正常運行時只能有一個master(即leader),多於1個時會發生腦裂。

當node.data爲true時,這個節點作爲一個數據節點,會存儲分配在該node上的shard的數據並負責這些shard的寫入、查詢等。

此外,任何一個集羣內的node都可以執行任何請求,其會負責將請求轉發給對應的node進行處理,所以當node.master和node.data都爲false時,這個節點可以作爲一個類似proxy的節點,接受請求並進行轉發、結果聚合等。

 

上圖是一個ES集羣的示意圖,其中NodeA是當前集羣的Master,NodeB和NodeC是Master的候選節點,其中NodeA和NodeB同時也是數據節點(DataNode),此外,NodeD是一個單純的數據節點,Node_E是一個proxy節點。每個Node會跟其他所有Node建立連接。

到這裏,我們提一個問題,供讀者思考:一個ES集羣應當配置多少個master-eligible node,當集羣的存儲或者計算資源不足,需要擴容時,新擴上去的節點應該設置爲何種類型?

節點發現

ZenDiscovery是ES自己實現的一套用於節點發現和選主等功能的模塊,沒有依賴Zookeeper等工具,官方文檔:

https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-discovery-zen.html

簡單來說,節點發現依賴以下配置:

conf/elasticsearch.yml:
    discovery.zen.ping.unicast.hosts: [1.1.1.1, 1.1.1.2, 1.1.1.3]

這個配置可以看作是,在本節點到每個hosts中的節點建立一條邊,當整個集羣所有的node形成一個聯通圖時,所有節點都可以知道集羣中有哪些節點,不會形成孤島。

官方推薦這裏設置爲所有的master-eligible node,讀者可以想想這樣有何好處:

It is recommended that the unicast hosts list be maintained as the list of master-eligible nodes in the cluster.

Master選舉

上面提到,集羣中可能會有多個master-eligible node,此時就要進行master選舉,保證只有一個當選master。如果有多個node當選爲master,則集羣會出現腦裂,腦裂會破壞數據的一致性,導致集羣行爲不可控,產生各種非預期的影響。

爲了避免產生腦裂,ES採用了常見的分佈式系統思路,保證選舉出的master被多數派(quorum)的master-eligible node認可,以此來保證只有一個master。這個quorum通過以下配置進行配置:

conf/elasticsearch.yml:
    discovery.zen.minimum_master_nodes: 2

這個配置對於整個集羣非常重要。

1 master選舉誰發起,什麼時候發起?

master選舉當然是由master-eligible節點發起,當一個master-eligible節點發現滿足以下條件時發起選舉:

  1. 該master-eligible節點的當前狀態不是master。

  2. 該master-eligible節點通過ZenDiscovery模塊的ping操作詢問其已知的集羣其他節點,沒有任何節點連接到master。

  3. 包括本節點在內,當前已有超過minimum_master_nodes個節點沒有連接到master。

總結一句話,即當一個節點發現包括自己在內的多數派的master-eligible節點認爲集羣沒有master時,就可以發起master選舉。

2 當需要選舉master時,選舉誰?

首先是選舉誰的問題,如下面源碼所示,選舉的是排序後的第一個MasterCandidate(即master-eligible node)。

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) {
        ret = compareNodes(c1.getNode(), c2.getNode());
    }
    return ret;
}

如上面源碼所示,先根據節點的clusterStateVersion比較,clusterStateVersion越大,優先級越高。clusterStateVersion相同時,進入compareNodes,其內部按照節點的Id比較(Id爲節點第一次啓動時隨機生成)。

總結一下:

  1. 當clusterStateVersion越大,優先級越高。這是爲了保證新Master擁有最新的clusterState(即集羣的meta),避免已經commit的meta變更丟失。因爲Master當選後,就會以這個版本的clusterState爲基礎進行更新。(一個例外是集羣全部重啓,所有節點都沒有meta,需要先選出一個master,然後master再通過持久化的數據進行meta恢復,再進行meta同步)。

  2. 當clusterStateVersion相同時,節點的Id越小,優先級越高。即總是傾向於選擇Id小的Node,這個Id是節點第一次啓動時生成的一個隨機字符串。之所以這麼設計,應該是爲了讓選舉結果儘可能穩定,不要出現都想當master而選不出來的情況。

3 什麼時候選舉成功?

當一個master-eligible node(我們假設爲Node_A)發起一次選舉時,它會按照上述排序策略選出一個它認爲的master。

  • 假設Node_A選Node_B當Master:

Node_A會向Node_B發送join請求,那麼此時:

(1) 如果Node_B已經成爲Master,Node_B就會把Node_A加入到集羣中,然後發佈最新的cluster_state, 最新的cluster_state就會包含Node_A的信息。相當於一次正常情況的新節點加入。對於Node_A,等新的cluster_state發佈到Node_A的時候,Node_A也就完成join了。

(2) 如果Node_B在競選Master,那麼Node_B會把這次join當作一張選票。對於這種情況,Node_A會等待一段時間,看Node_B是否能成爲真正的Master,直到超時或者有別的Master選成功。

(3) 如果Node_B認爲自己不是Master(現在不是,將來也選不上),那麼Node_B會拒絕這次join。對於這種情況,Node_A會開啓下一輪選舉。

  • 假設Node_A選自己當Master:

此時NodeA會等別的node來join,即等待別的node的選票,當收集到超過半數的選票時,認爲自己成爲master,然後變更cluster_state中的master node爲自己,並向集羣發佈這一消息。

有興趣的同學可以看看下面這段源碼:

if (transportService.getLocalNode().equals(masterNode)) {
            final int requiredJoins = Math.max(0, electMaster.minimumMasterNodes() - 1); // we count as one
            logger.debug("elected as master, waiting for incoming joins ([{}] needed)", requiredJoins);
            nodeJoinController.waitToBeElectedAsMaster(requiredJoins, masterElectionWaitForJoinsTimeout,
                    new NodeJoinController.ElectionCallback() {
                        @Override
                        public void onElectedAsMaster(ClusterState state) {
                            synchronized (stateMutex) {
                                joinThreadControl.markThreadAsDone(currentThread);
                            }
                        }

                        @Override
                        public void onFailure(Throwable t) {
                            logger.trace("failed while waiting for nodes to join, rejoining", t);
                            synchronized (stateMutex) {
                                joinThreadControl.markThreadAsDoneAndStartNew(currentThread);
                            }
                        }
                    }

            );
        } else {
            // process any incoming joins (they will fail because we are not the master)
            nodeJoinController.stopElectionContext(masterNode + " elected");

            // send join request
            final boolean success = joinElectedMaster(masterNode);

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

按照上述流程,我們描述一個簡單的場景來幫助大家理解:

假如集羣中有3個master-eligible node,分別爲Node_A、 Node_B、 Node_C, 選舉優先級也分別爲Node_A、Node_B、Node_C。三個node都認爲當前沒有master,於是都各自發起選舉,選舉結果都爲Node_A(因爲選舉時按照優先級排序,如上文所述)。於是Node_A開始等join(選票),Node_B、Node_C都向Node_A發送join,當Node_A接收到一次join時,加上它自己的一票,就獲得了兩票了(超過半數),於是Node_A成爲Master。此時cluster_state(集羣狀態)中包含兩個節點,當Node_A再收到另一個節點的join時,cluster_state包含全部三個節點。

4 選舉怎麼保證不腦裂?

基本原則還是多數派的策略,如果必須得到多數派的認可才能成爲Master,那麼顯然不可能有兩個Master都得到多數派的認可。

上述流程中,master候選人需要等待多數派節點進行join後才能真正成爲master,就是爲了保證這個master得到了多數派的認可。但是我這裏想說的是,上述流程在絕大部份場景下沒問題,聽上去也非常合理,但是卻是有bug的。

因爲上述流程並沒有限制在選舉過程中,一個Node只能投一票,那麼什麼場景下會投兩票呢?比如NodeB投NodeA一票,但是NodeA遲遲不成爲Master,NodeB等不及了發起了下一輪選主,這時候發現集羣裏多了個Node0,Node0優先級比NodeA還高,那NodeB肯定就改投Node0了。假設Node0和NodeA都處在等選票的環節,那顯然這時候NodeB其實發揮了兩票的作用,而且投給了不同的人。

那麼這種問題應該怎麼解決呢,比如raft算法中就引入了選舉週期(term)的概念,保證了每個選舉週期中每個成員只能投一票,如果需要再投就會進入下一個選舉週期,term+1。假如最後出現兩個節點都認爲自己是master,那麼肯定有一個term要大於另一個的term,而且因爲兩個term都收集到了多數派的選票,所以多數節點的term是較大的那個,保證了term小的master不可能commit任何狀態變更(commit需要多數派節點先持久化日誌成功,由於有term檢測,不可能達到多數派持久化條件)。這就保證了集羣的狀態變更總是一致的。

而ES目前(6.2版本)並沒有解決這個問題,構造類似場景的測試case可以看到會選出兩個master,兩個node都認爲自己是master,向全集羣發佈狀態變更,這個發佈也是兩階段的,先保證多數派節點“接受”這次變更,然後再要求全部節點commit這次變更。很不幸,目前兩個master可能都完成第一個階段,進入commit階段,導致節點間狀態出現不一致,而在raft中這是不可能的。那麼爲什麼都能完成第一個階段呢,因爲第一個階段ES只是將新的cluster_state做簡單的檢查後放入內存隊列,如果當前cluster_state的master爲空,不會對新的clusterstate中的master做檢查,即在接受了NodeA成爲master的cluster_state後(還未commit),還可以繼續接受NodeB成爲master的cluster_state。這就使NodeA和NodeB都能達到commit條件,發起commit命令,從而將集羣狀態引向不一致。當然,這種腦裂很快會自動恢復,因爲不一致發生後某個master再次發佈cluster_state時就會發現無法達到多數派條件,或者是發現它的follower並不構成多數派而自動降級爲candidate等。

這裏要表達的是,ES的ZenDiscovery模塊與成熟的一致性方案相比,在某些特殊場景下存在缺陷,下一篇文章講ES的meta變更流程時也會分析其他的ES無法滿足一致性的場景。

錯誤檢測

1. MasterFaultDetection與NodesFaultDetection

這裏的錯誤檢測可以理解爲類似心跳的機制,有兩類錯誤檢測,一類是Master定期檢測集羣內其他的Node,另一類是集羣內其他的Node定期檢測當前集羣的Master。檢查的方法就是定期執行ping請求。ES文檔:

There are two fault detection processes running. The first is by the master, to ping all the other nodes in the cluster and verify that they are alive. And on the other end, each node pings to master to verify if its still alive or an election process needs to be initiated.

 

如果Master檢測到某個Node連不上了,會執行removeNode的操作,將節點從cluste_state中移除,併發布新的cluster_state。當各個模塊apply新的cluster_state時,就會執行一些恢復操作,比如選擇新的primaryShard或者replica,執行數據複製等。

如果某個Node發現Master連不上了,會清空pending在內存中還未commit的new cluster_state,然後發起rejoin,重新加入集羣(如果達到選舉條件則觸發新master選舉)。

2. rejoin

除了上述兩種情況,還有一種情況是Master發現自己已經不滿足多數派條件(>=minimumMasterNodes)了,需要主動退出master狀態(退出master狀態並執行rejoin)以避免腦裂的發生,那麼master如何發現自己需要rejoin呢?

  • 上面提到,當有節點連不上時,會執行removeNode。在執行removeNode時判斷剩餘的Node是否滿足多數派條件,如果不滿足,則執行rejoin。

        if (electMasterService.hasEnoughMasterNodes(remainingNodesClusterState.nodes()) == false) {
                final int masterNodes = electMasterService.countMasterNodes(remainingNodesClusterState.nodes());
                rejoin.accept(LoggerMessageFormat.format("not enough master nodes (has [{}], but needed [{}])",
                                                         masterNodes, electMasterService.minimumMasterNodes()));
                return resultBuilder.build(currentState);
            } else {
                return resultBuilder.build(allocationService.deassociateDeadNodes(remainingNodesClusterState, true, describeTasks(tasks)));
            }
  • 在publish新的cluster_state時,分爲send階段和commit階段,send階段要求多數派必須成功,然後再進行commit。如果在send階段沒有實現多數派返回成功,那麼可能是有了新的master或者是無法連接到多數派個節點等,則master需要執行rejoin。

    try {
            publishClusterState.publish(clusterChangedEvent, electMaster.minimumMasterNodes(), ackListener);
        } catch (FailedToCommitClusterStateException t) {
            // cluster service logs a WARN message
            logger.debug("failed to publish cluster state version [{}] (not enough nodes acknowledged, min master nodes [{}])",
                newState.version(), electMaster.minimumMasterNodes());

            synchronized (stateMutex) {
                pendingStatesQueue.failAllStatesAndClear(
                    new ElasticsearchException("failed to publish cluster state"));

                rejoin("zen-disco-failed-to-publish");
            }
            throw t;
        }
  • 在對其他節點進行定期的ping時,發現有其他節點也是master,此時會比較本節點與另一個master節點的cluster_state的version,誰的version大誰成爲master,version小的執行rejoin。

    if (otherClusterStateVersion > localClusterState.version()) {
            rejoin("zen-disco-discovered another master with a new cluster_state [" + otherMaster + "][" + reason + "]");
        } else {
            // TODO: do this outside mutex
            logger.warn("discovered [{}] which is also master but with an older cluster_state, telling [{}] to rejoin the cluster ([{}])", otherMaster, otherMaster, reason);
            try {
                // make sure we're connected to this node (connect to node does nothing if we're already connected)
                // since the network connections are asymmetric, it may be that we received a state but have disconnected from the node
                // in the past (after a master failure, for example)
                transportService.connectToNode(otherMaster);
                transportService.sendRequest(otherMaster, DISCOVERY_REJOIN_ACTION_NAME, new RejoinClusterRequest(localClusterState.nodes().getLocalNodeId()), new EmptyTransportResponseHandler(ThreadPool.Names.SAME) {

                    @Override
                    public void handleException(TransportException exp) {
                        logger.warn((Supplier<?>) () -> new ParameterizedMessage("failed to send rejoin request to [{}]", otherMaster), exp);
                    }
                });
            } catch (Exception e) {
                logger.warn((Supplier<?>) () -> new ParameterizedMessage("failed to send rejoin request to [{}]", otherMaster), e);
            }
        }

集羣擴縮容

上面講了節點發現、Master選舉、錯誤檢測等機制,那麼現在我們可以來看一下如何對集羣進行擴縮容。

1 擴容DataNode

假設一個ES集羣存儲或者計算資源不夠了,我們需要進行擴容,這裏我們只針對DataNode,即配置爲:

conf/elasticsearch.yml:
    node.master: false
    node.data: true

然後需要配置集羣名、節點名等其他配置,爲了讓該節點能夠加入集羣,我們把discovery.zen.ping.unicast.hosts配置爲集羣中的master-eligible node。

conf/elasticsearch.yml:
    cluster.name: es-cluster
    node.name: node_Z
    discovery.zen.ping.unicast.hosts: ["x.x.x.x", "x.x.x.y", "x.x.x.z"]

然後啓動節點,節點會自動加入到集羣中,集羣會自動進行rebalance,或者通過reroute api進行手動操作。具體見下:

https://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-reroute.html

https://www.elastic.co/guide/en/elasticsearch/reference/current/shards-allocation.html

2 縮容DataNode

假設一個ES集羣使用的機器數太多了,需要縮容,我們怎麼安全的操作來保證數據安全,並且不影響可用性呢?

首先,我們選擇需要縮容的節點,注意本節只針對DataNode的縮容,MasterNode縮容涉及到更復雜的問題,下面再講。

然後,我們需要把這個Node上的Shards遷移到其他節點上,方法是先設置allocation規則,禁止分配Shard到要縮容的機器上,然後讓集羣進行rebalance。

PUT _cluster/settings
{
  "transient" : {
    "cluster.routing.allocation.exclude._ip" : "10.0.0.1"
  }
}

等這個節點上的數據全部遷移完成後,節點可以安全下線。

更詳細的操作方式可以參考官方文檔:

https://www.elastic.co/guide/en/elasticsearch/reference/current/allocation-filtering.html

3 擴容MasterNode

假如我們想擴容一個MasterNode(master-eligible node), 那麼有個需要考慮的問題是,上面提到爲了避免腦裂,ES是採用多數派的策略,需要配置一個quorum數:

conf/elasticsearch.yml:
    discovery.zen.minimum_master_nodes: 2

假設之前3個master-eligible node,我們可以配置quorum爲2,如果擴容到4個master-eligible node,那麼quorum就要提高到3。

所以我們應該先把discovery.zen.minimum_master_nodes這個配置改成3,再擴容master,更改這個配置可以通過API的方式:

curl -XPUT localhost:9200/_cluster/settings -d '{
    "persistent" : {
        "discovery.zen.minimum_master_nodes" : 3
    }
}'

這個API發送給當前集羣的master,然後新的值立即生效,然後master會把這個配置持久化到cluster meta中,之後所有節點都會以這個配置爲準。

但是這種方式有個問題在於,配置文件中配置的值和cluster meta中的值很可能出現不一致,不一致很容易導致一些奇怪的問題,比如說集羣重啓後,在恢復cluster meta前就需要進行master選舉,此時只可能拿配置中的值,拿不到cluster meta中的值,但是cluster meta恢復後,又需要以cluster meta中的值爲準,這中間肯定存在一些正確性相關的邊界case。

總之,動master節點以及相關的配置一定要謹慎,master配置錯誤很有可能導致腦裂甚至數據寫壞、數據丟失等場景。

4 縮容MasterNode

縮容MasterNode與擴容跟擴容是相反的流程,我們需要先把節點縮下來,再把quorum數調下來,不再詳細描述。

與Zookeeper、raft等實現方式的比較

1. 與使用Zookeeper相比

本篇講了ES集羣中節點相關的幾大功能的實現方式:

  • 節點發現

  • Master選舉

  • 錯誤檢測

  • 集羣擴縮容

試想下,如果我們使用Zookeeper來實現這幾個功能,會帶來哪些變化?

Zookeeper介紹

我們首先介紹一下Zookeeper,熟悉的同學可以略過。

Zookeeper分佈式服務框架是Apache Hadoop 的一個子項目,它主要是用來解決分佈式應用中經常遇到的一些數據管理問題,如:統一命名服務、狀態同步服務、集羣管理、分佈式應用配置項的管理等。

簡單來說,Zookeeper就是用於管理分佈式系統中的節點、配置、狀態,並完成各個節點間配置和狀態的同步等。大量的分佈式系統依賴Zookeeper或者是類似的組件。

Zookeeper通過目錄樹的形式來管理數據,每個節點稱爲一個znode,每個znode由3部分組成:

  • stat. 此爲狀態信息, 描述該znode的版本, 權限等信息.

  • data. 與該znode關聯的數據.

  • children. 該znode下的子節點.

stat中有一項是ephemeralOwner,如果有值,代表是一個臨時節點,臨時節點會在session結束後刪除,可以用來輔助應用進行master選舉和錯誤檢測。

Zookeeper提供watch功能,可以用於監聽相應的事件,比如某個znode下的子節點的增減,某個znode本身的增減,某個znode的更新等。

怎麼使用Zookeeper實現ES的上述功能

  • 節點發現:每個節點的配置文件中配置一下Zookeeper服務器的地址,節點啓動後到Zookeeper中某個目錄中註冊一個臨時的znode。當前集羣的master監聽這個目錄的子節點增減的事件,當發現有新節點時,將新節點加入集羣。

  • master選舉:當一個master-eligible node啓動時,都嘗試到固定位置註冊一個名爲master的臨時znode,如果註冊成功,即成爲master,如果註冊失敗則監聽這個znode的變化。當master出現故障時,由於是臨時znode,會自動刪除,這時集羣中其他的master-eligible node就會嘗試再次註冊。使用Zookeeper後其實是把選master變成了搶master。

  • 錯誤檢測:由於節點的znode和master的znode都是臨時znode,如果節點故障,會與Zookeeper斷開session,znode自動刪除。集羣的master只需要監聽znode變更事件即可,如果master故障,其他的候選master則會監聽到master znode被刪除的事件,嘗試成爲新的master。

  • 集羣擴縮容:擴縮容將不再需要考慮minimum_master_nodes配置的問題,會變得更容易。

使用Zookeeper的優劣點

使用Zookeeper的好處是,把一些複雜的分佈式一致性問題交給Zookeeper來做,ES本身的邏輯就可以簡化很多,正確性也有保證,這也是大部分分佈式系統實踐過的路子。而ES的這套ZenDiscovery機制經歷過很多次bug fix,到目前仍有一些邊角的場景存在bug,而且運維也不簡單。

那爲什麼ES不使用Zookeeper呢,大概是官方開發覺得增加Zookeeper依賴後會多依賴一個組件,使集羣部署變得更復雜,用戶在運維時需要多運維一個Zookeeper。

那麼在自主實現這條路上,還有什麼別的算法選擇嗎?當然有的,比如raft。

2. 與使用raft相比

raft算法是近幾年很火的一個分佈式一致性算法,其實現相比paxos簡單,在各種分佈式系統中也得到了應用。這裏不再描述其算法的細節,我們單從master選舉算法角度,比較一下raft與ES目前選舉算法的異同點:

相同點

  • 多數派原則:必須得到超過半數的選票才能成爲master。

  • 選出的leader一定擁有最新已提交數據:在raft中,數據更新的節點不會給數據舊的節點投選票,而當選需要多數派的選票,則當選人一定有最新已提交數據。在es中,version大的節點排序優先級高,同樣用於保證這一點。

不同點

  • 正確性論證:raft是一個被論證過正確性的算法,而ES的算法是一個沒有經過論證的算法,只能在實踐中發現問題,做bug fix,這是我認爲最大的不同。

  • 是否有選舉週期term:raft引入了選舉週期的概念,每輪選舉term加1,保證了在同一個term下每個參與人只能投1票。ES在選舉時沒有term的概念,不能保證每輪每個節點只投一票。

  • 選舉的傾向性:raft中只要一個節點擁有最新的已提交的數據,則有機會選舉成爲master。在ES中,version相同時會按照NodeId排序,總是NodeId小的人優先級高。

看法

raft從正確性上看肯定是更好的選擇,而ES的選舉算法經過幾次bug fix也越來越像raft。當然,在ES最早開發時還沒有raft,而未來ES如果繼續沿着這個方向走很可能最終就變成一個raft實現。

raft不僅僅是選舉,下一篇介紹meta數據一致性時也會繼續比較ES目前的實現與raft的異同。

小結

本篇介紹了Elasticsearch集羣的組成、節點發現、master選舉、故障檢測和擴縮容等方面的實現,與一般的文章不同,本文對其原理、存在的問題也進行了一些分析,並與其他實現方式進行了比較。

文章來自:架構師

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章