共識是分布式系統(tǒng)的一項基本挑戰(zhàn)。它要求系統(tǒng)中的所有進程/節(jié)點必須對給定數(shù)據(jù)的值/狀態(tài)達成共識。已經(jīng)有很多共識算法諸如Raft、Paxos等,從數(shù)學(xué)上的證明了是行得通的。但是,Elasticsearch卻實現(xiàn)了自己的共識系統(tǒng)(zen discovery),Elasticsearch之父Shay Banon在這篇文章中解釋了其中的原因。zen discovery模塊包含兩個部分:
Elasticsearch是端對端的系統(tǒng),其中的所有節(jié)點彼此相連,有一個master節(jié)點保持活躍,它會更新和控制集群內(nèi)的狀態(tài)和操作。建立一個新的Elasticsearch集群要經(jīng)過一次選舉,選舉是ping過程的一部分,在所有符合條件的節(jié)點中選取一個master,其他節(jié)點將加入這個master節(jié)點。ping間隔參數(shù)ping_interval
的默認值是1秒,ping超時參數(shù)ping_timeout
的默認值是3秒。因為節(jié)點要加入,它們會發(fā)送一個請求給master節(jié)點,加入超時參數(shù)join_timeout
的默認值是ping_timeout
值的20倍。如果master出現(xiàn)問題,那么群集中的其他節(jié)點開始重新ping以啟動另一次選舉。這個ping的過程還可以幫助一個節(jié)點在忽然失去master時,通過其他節(jié)點發(fā)現(xiàn)master。
注意:默認情況下,client節(jié)點和data節(jié)點不參與這個選舉過程。可以在elasticsearch.yml配置文件中,通過設(shè)置discovery.zen.master_election.filter_client
屬性和discovery.zen.master_election.filter_data
屬性為false
來改變這種默認行為。
故障檢測的原理是這樣的,master節(jié)點會ping所有其他節(jié)點,以檢查它們是否還活著;然后所有節(jié)點ping回去,告訴master他們還活著。
如果使用默認的設(shè)置,Elasticsearch有可能遭到裂腦問題的困擾。在網(wǎng)絡(luò)分區(qū)的情況下,一個節(jié)點可以認為master死了,然后選自己作為master,這就導(dǎo)致了一個集群內(nèi)出現(xiàn)多個master。這可能會導(dǎo)致數(shù)據(jù)丟失,也可能無法正確合并數(shù)據(jù)??梢园凑杖缦鹿?,根據(jù)有資格參加選舉的節(jié)點數(shù),設(shè)置法定票數(shù)屬性的值,來避免爆裂的發(fā)生。
discovery.zen.minimum_master_nodes = int(# of master eligible nodes/2)+1
這個屬性要求法定票數(shù)的節(jié)點加入新當(dāng)選的master節(jié)點,來完成并獲得新master節(jié)點接受的master身份。對于確保群集穩(wěn)定性和在群集大小變化時動態(tài)地更新,這個屬性是非常重要的。圖a和b演示了在網(wǎng)絡(luò)分區(qū)的情況下,設(shè)置或不設(shè)置minimum_master_nodes
屬性時,分別發(fā)生的現(xiàn)象。
注意:對于一個生產(chǎn)集群來說,建議使用3個節(jié)點專門做master,這3個節(jié)點將不服務(wù)于任何客戶端請求,而且在任何給定時間內(nèi)總是只有1個活躍。
我們已經(jīng)搞清楚了Elasticsearch中共識的處理,現(xiàn)在讓我們來看看它是如何處理并發(fā)的。
Elasticsearch是一個分布式系統(tǒng),支持并發(fā)請求。當(dāng)創(chuàng)建/更新/刪除請求到達主分片時,它也會被平行地發(fā)送到分片副本上。但是,這些請求到達的順序可能是亂序的。在這種情況下,Elasticsearch使用樂觀并發(fā)控制,來確保文檔的較新版本不會被舊版本覆蓋。
每個被索引的文檔都擁有一個版本號,版本號在每次文檔變更時遞增并應(yīng)用到文檔中。這些版本號用來確保有序接受變更。為了確保在我們的應(yīng)用中更新不會導(dǎo)致數(shù)據(jù)丟失,Elasticsearch的API允許我們指定文件的當(dāng)前版本號,以使變更被接受。如果在請求中指定的版本號比分片上存在的版本號舊,請求失敗,這意味著文檔已經(jīng)被另一個進程更新了。如何處理失敗的請求,可以在應(yīng)用層面來控制。Elasticsearch還提供了其他的鎖選項,可以通過這篇來閱讀。
當(dāng)我們發(fā)送并發(fā)請求到Elasticsearch后,接下來面對的問題是——如何保證這些請求的讀寫一致?現(xiàn)在,還無法清楚回答,Elasticsearch應(yīng)落在CAP三角形的哪條邊上,我不打算在這篇文章里解決這個素來已久的爭辯。
但是,我們要一起看下如何使用Elasticsearch實現(xiàn)寫讀一致。
對于寫操作而言,Elasticsearch支持的一致性級別,與大多數(shù)其他的數(shù)據(jù)庫不同,允許預(yù)檢查,來查看有多少允許寫入的可用分片??蛇x的值有quorum、one和all。默認的設(shè)置為quorum,也就是說只有當(dāng)大多數(shù)分片可用時才允許寫操作。即使大多數(shù)分片可用,還是會因為某種原因發(fā)生寫入副本失敗,在這種情況下,副本被認為故障,分片將在一個不同的節(jié)點上重建。
對于讀操作而言,新的文檔只有在刷新時間間隔之后,才能被搜索到。為了確保搜索請求的返回結(jié)果包含文檔的最新版本,可設(shè)置replication為sync(默認),這將使操作在主分片和副本碎片都完成后才返回寫請求。在這種情況下,搜索請求從任何分片得到的返回結(jié)果都包含的是文檔的最新版本。即使我們的應(yīng)用為了更高的索引率而設(shè)置了replication=async,我們依然可以為搜索請求設(shè)置參數(shù)_preference為primary。這樣,搜索請求將查詢主分片,并確保結(jié)果中的文檔是最新版本。
我們已經(jīng)了解了Elasticsearch如何處理共識、并發(fā)和一致,讓我們來看看分片內(nèi)部的一些主要概念,正是這些特點讓Elasticsearch成為一個分布式搜索引擎。
因為關(guān)系數(shù)據(jù)庫的發(fā)展,預(yù)寫日志(WAL)或者事務(wù)日志(translog)的概念早已遍及數(shù)據(jù)庫領(lǐng)域。在發(fā)生故障的時候,translog能確保數(shù)據(jù)的完整性。translog的基本原理是,變更必須在數(shù)據(jù)實際的改變提交到磁盤上之前,被記錄下來并提交。
當(dāng)新的文檔被索引或者舊的文檔被更新時,Lucene索引將發(fā)生變更,這些變更將被提交到磁盤以持久化。這是一個很昂貴的操作,如果在每個請求之后都被執(zhí)行。因此,這個操作在多個變更持久化到磁盤時被執(zhí)行一次。正如我們在上一篇文章中描述的那樣,Lucene提交的沖洗(flush)操作默認每30分鐘執(zhí)行一次或者當(dāng)translog變得太大(默認512MB)時執(zhí)行。在這樣的情況下,有可能失去2個Lucene提交之間的所有變更。為了避免這種問題,Elasticsearch采用了translog。所有索引/刪除/更新操作被寫入到translog,在每個索引/刪除/更新操作執(zhí)行之后(默認情況下是每5秒),translog會被同步以確保變更被持久化。translog被同步到主分片和副本之后,客戶端才會收到寫請求的確認。
在兩次Lucene提交之間發(fā)生硬件故障的情況下,可以通過重放translog來恢復(fù)自最后一次Lucene提交前的任何丟失的變更,所有的變更將會被索引所接受。
注意:建議在重啟Elasticsearch實例之前顯式地執(zhí)行沖洗translog,這樣啟動會更快,因為要重放的translog被清空。POST /_all/_flush命令可用于沖洗集群中的所有索引。
使用translog的沖洗操作,在文件系統(tǒng)緩存中的段被提交到磁盤,使索引中的變更持久化?,F(xiàn)在讓我們來看看Lucene的段。
Lucene索引是由多個段組成,段本身是一個功能齊全的倒排索引。段是不可變的,允許Lucene將新的文檔增量地添加到索引中,而不用從頭重建索引。對于每一個搜索請求而言,索引中的所有段都會被搜索,并且每個段會消耗CPU的時鐘周、文件句柄和內(nèi)存。這意味著段的數(shù)量越多,搜索性能會越低。
為了解決這個問題,Elasticsearch會合并小段到一個較大的段(如下圖所示),提交新的合并段到磁盤,并刪除那些舊的小段。
這會在后臺自動執(zhí)行而不中斷索引或者搜索。由于段合并會耗盡資源,影響搜索性能,Elasticsearch會節(jié)制合并過程,為搜索提供足夠的可用資源。