配置高效能 ElasticSearch 搜尋引擎叢集的9個小貼士

楊振濤發表於2017-01-05

Loggly服務底層的很多核心功能都使用了ElasticSearch作為搜尋引擎。就像Jon Gifford(譯者注:Loggly部落格作者之一)在他近期關於“ElasticSearch vs Solr”的文章中所述,日誌管理在搜尋技術方面產生一些粗暴的需求,堅持下來以後,它必須能夠:

在超大規模資料集上可靠地進行準實時索引 – 在我們的案例中,每秒有超過100,000個日誌事件與此同時,在該索引上可靠高效地處理超大量的搜尋請求

當時我們正在構建Gen2日誌管理服務,想保證使用的所有ElasticSearch配置資訊,可以獲得最優的索引和搜尋效能。悲劇的是,我們發現想在ElasticSearch文件裡找到這樣的資訊非常困難,因為它們不只在一個地方。本文總結了我們的學習經驗,可作為一個配置屬性的參考檢查單(checklist)用於優化你自己應用中的ES。

小貼士1:規劃索引、分片 以及叢集增長情況

ES使得建立大量索引和超大量分片非常地容易,但更重要的是理解每個索引和分片都是一筆開銷。如果擁有太多的索引或分片,單單是管理負荷就會影響到ES叢集的效能,潛在地也會影響到可用性方面。這裡我們專注於管理負荷,但執行大量的索引/分片依然會非常顯著地影響到索引和檢索效能。

我們發現影響管理負荷的最大因素是叢集狀態資料的大小,因為它包含了叢集中每個索引的所有mapping資料。我們曾經一度有單個叢集擁有超過900MB的叢集狀態資料。該叢集雖然在執行但並不可用。

讓我們通過一些資料來了解到底發生了什麼 。。。。。。

假如有一個索引包含50k的mapping資料(我們當時是有700個欄位)。如果每小時生成一個索引,那麼每天將增加24 x 50k的叢集狀態資料,或者1.2MB。如果需要在系統中保留一年的資料,那麼叢集狀態資料將高達438MB(以及8670個索引,43800個分片)。如果與每天一個索引(18.25MB,365個索引,1825個分片)作比較,會看到每小時的索引策略將會是一個完全不同的境況。

幸運的是,一旦系統中有一些真實資料的話,實際上非常容易做這些預測。我們應當能夠看到叢集必須處理多少狀態資料和多少索引/分片。在上到生產環境之前真的應該演練一下,以便防止凌晨3:00收到叢集掛掉的電話告警。

在配置方面,我們完全可以控制系統中有多少索引(以及有多少分片),這將讓我們遠離危險地帶。

小貼士2:在配置前瞭解叢集的拓撲結構

Loggly通過獨立的master節點和data節點來執行ES。這裡不討論太多的部署細節(請留意後續博文),但為了做出正確的配置選擇,需要先確定部署的拓撲結構。

另外,我們為索引和搜尋使用單獨的ES client節點。這將減輕data節點的一些負載,更重要的是,這樣我們的管道就可以和本地客戶端通訊,從而與叢集的其他節點通訊。

可通過設定以下兩個屬性的值為true或false來建立ES的data節點和master節點:

Master node: node.master:true node.data:false

Data node: node.master:false node.data:true

Client node: node.master:false node.data:false

以上是相對容易的部分,現在來看一些值得關注的ES高階屬性。對大多數部署場景來說預設設定已經足夠了,但如果你的ES使用情況和我們在log管理中遇到的一樣難搞,你將會從下文的建議中受益良多。

小貼士3: 記憶體設定

Linux把它的物理RAM分成多個記憶體塊,稱之為分頁。記憶體交換(swapping)是這樣一個過程,它把記憶體分頁複製到預先設定的叫做交換區的硬碟空間上,以此釋放記憶體分頁。實體記憶體和交換區加起來的大小就是虛擬記憶體的可用額度。

記憶體交換有個缺點,跟記憶體比起來硬碟非常慢。記憶體的讀寫速度以納秒來計算,而硬碟是以毫秒來計算,所以訪問硬碟比訪問記憶體要慢幾萬倍。交換次數越多,程式就越慢,所以應該不惜一切代價避免記憶體交換的發生。

ES的mlockall屬性允許ES節點不交換記憶體。(注意只有Linux/Unix系統可設定。)這個屬性可以在yaml檔案中設定:

bootstrap.mlockall: true

在5.x版本中,已經改成了Bootstrap.memory_lock: true.

mlockall預設設定成false,即ES節點允許記憶體交換。一旦把這個值加到屬性檔案中,需要重啟ES節點才可生效。可通過以下方式來確定該值是否設定正確:

curl http://localhost:9200/_nodes/process?pretty

如果你正在設定這個屬性,請使用-DXmx選項或ES_HEAP_SIZE屬性來確保ES節點分配了足夠的記憶體。

小貼士4:discovery.zen屬性控制ElasticSearch的發現協議

Elasticsearch預設使用服務發現(Zen discovery)作為叢集節點間發現和通訊的機制。Azure、EC2和GCE也有使用其他的發現機制。服務發現由discovery.zen.*開頭的一系列屬性控制。

在0.x和1.x版本中同時支援單播和多播,且預設是多播。所以要在這些版本的ES中使用單播,需要設定屬性discovery.zen.ping.multicast.enabled為false。

從2.0開始往後服務發現就僅支援單播了。

首先需要使用屬性discovery.zen.ping.unicast.hosts指定一組通訊主機。方便起見,在叢集中的所有主機上為該屬性設定相同的值,使用叢集節點的名稱來定義主機列表。

屬性discovery.zen.minimum_master_nodes決定了有資格作為master的節點的最小數量,即一個應當“看見”叢集範圍內運作的節點。如果叢集中有2個以上節點,建議設定該值為大於1。一種計算方法是,假設叢集中的節點數量為N,那麼該屬性應該設定為N/2+1。

Data和master節點以兩種不同方式互相探測:

通過master節點ping叢集中的其他節點以驗證他們處於執行狀態通過叢集中的其他節點ping master節點以驗證他們處於執行狀態或者是否需要初始化一個選舉過程

節點探測過程通過discover.zen.fd.ping_timeout屬性控制,預設值是30s,決定了節點將會等待響應多久後超時。當執行一個較慢的或者擁堵的網路時,應該調整這個屬性;如果在一個慢速網路中,將該屬性調大;其值越大,探測失敗的機率就越小。

Loggly的discovery.zen相關屬性配置如下:

discovery.zen.fd.ping_timeout: 30s

discovery.zen.minimum_master_nodes: 2

discovery.zen.ping.unicast.hosts: [“esmaster01″,”esmaster02″,”esmaster03″]

以上屬性配置表示節點探測將在30秒內發生,因為設定了discovery.zen.fd.ping_timeout屬性。另外,其他節點應當探測到最少兩個master節點(我們有3個master)。我們的單播主機是esmaster01、 esmaster02、esmaster03。

小貼士5:當心DELETE _all

必須要了解的一點是,ES的DELETE API允許使用者僅僅通過一個請求來刪除索引,支援使用萬用字元,甚至可以使用_all作為索引名來代表所有索引。例如:

curl -XDELETE ‘http://localhost:9200/*/’

這個特性非常有用,但也非常危險,特別是在生產環境中。在我們的所有叢集中,已通過設定action.destructive_requires_name:true來禁用了它。

這項配置在1.0版本中開始引用,並取代了0.90版本中使用的配置屬性disable_delete_all_indices。

小貼士6:使用Doc Values

2.0及以上版本預設開啟Doc Values特性,但在更早的ES版本中必須顯式地設定。當進行大規模的排序和聚合操作時,Doc Values相比普通屬性有著明顯的優勢。本質上是將ES轉換成一個列式儲存,從而使ES的許多分析類特性在效能上遠超預期。

為了一探究竟,我們可以在ES裡比較一下Doc Values和普通屬性。

當使用一個普通屬性去排序或聚合時,該屬性會被載入到屬性資料快取中。一個屬性首次被快取時,ES必須分配足夠大的堆空間,以便能儲存每一個值,然後使用每個文件的值逐步填充。這個過程可能會耗費一些時間,因為可能需要從磁碟讀取他們的值。一旦這個過程完成,這些資料的任何相關操作都將使用這份快取資料,並且會很快。如果嘗試填充太多的屬性到快取,一些屬性將被回收,隨後再次使用到這些屬性時將會強制它們重新被載入到快取,且同樣有啟動開銷。為了更加高效,人們會想到最小化或淘汰,這意味著我們的屬性數量將受限於此種方式下的快取大小。

相比之下,Doc Values屬性使用基於硬碟的資料結構,且能被記憶體對映到程式空間,因此不影響堆使用,同時提供實質上與屬性資料快取一樣的效能。當這些屬性首次從硬碟讀取資料時仍然會有較小的啟動開銷,但這會由作業系統快取去處理,所以只有真正需要的資料會被實際讀取。

Doc Values因此最小化了堆的使用(因為垃圾收集),併發揮了作業系統檔案快取的優勢,從而可進一步最小化磁碟讀操作的壓力。

小貼士7:ElasticSearch配額類屬性設定指南

分片分配就是分配分片到節點的過程,可能會發生在初始化恢復、副本分配、或者叢集再平衡的階段,甚至發生在處理節點加入或退出的階段。

屬性cluster.routing.allocation.cluster_concurrent_rebalance決定了允許併發再平衡的分片數量。這個屬性需要根據硬體使用情況去適當地配置,比如CPU個數、IO負載等。如果該屬性設定不當,將會影響ES的索引效能。

cluster.routing.allocation.cluster_concurrent_rebalance:2

預設值是2,表示任意時刻只允許同時移動2個分片。最好將該屬性設定得較小,以便壓制分片再平衡,使其不影響索引。

另一個分片分配相關的屬性是cluster.routing.allocation.disk.threshold_enabled。如果該屬性裝置為true(預設值),在分配分片到一個節點時將會把可用的磁碟空間算入配額內。關閉該屬性會導致ES可能分配分片到一個磁碟可用空間不足的節點,從而影響分片的增長。

當開啟時,分片分配會將兩個閥值屬性加入配額:低水位和高水位。

低水位定義ES將不再分配新分片到該節點的磁碟使用百分比。(預設是85%)高水位定義分配將開始從該節點遷移走分片的磁碟使用百分比。(預設是90%)

這兩個屬性都可以被定義為磁碟使用的百分比(比如“80%”表示80%的磁碟空間已使用,或者說還有20%未使用),或者最小可用空間大小(比如“20GB”表示該節點還有20GB的可用空間)。

如果有很多的小分片,那麼預設值就非常保守了。舉個例子,如果有一個1TB的硬碟,分片是典型的10GB大小,那麼理論上可以在該節點上分配100個分片。在預設設定的情況下,只能分配80個分片到該節點上,之後ES就認為這個節點已經滿了。

為得到適合的配置引數,應該看看分片到底在變多大之後會結束他們的生命週期,然後從這裡反推,確認包括一個安全係數。在上面的例子中,只有5個分片寫入,所以需要一直確保有50GB的可用空間。對於一個1TB的硬碟,這個情形會變成95%的低水位線,並且沒有安全係數。額外的,比如一個50%的安全係數,意味著應該確保有75GB的可以空間,或者一個92.5%的低水位線。

小貼士8:Recovery屬性允許快速重啟

ES有很多恢復相關的屬性,可以提升叢集恢復和重啟的速度。最佳屬性設定依賴於當前使用的硬體(硬碟和網路是最常見的瓶頸),我們能給出的最好建議是測試、測試、還是測試。

想控制多少個分片可以在單個節點上同時恢復,使用:

cluster.routing.allocation.node_concurrent_recoveries

恢復分片是一個IO非常密集的操作,所以應當謹慎調整該值。在5.x版本中,該屬性分為了兩個:

cluster.routing.allocation.node_concurrent_incoming_recoveries

cluster.routing.allocation.node_concurrent_outgoing_recoveries

想控制單個節點上的併發初始化主分片數量,使用:

cluster.routing.allocation.node_initial_primaries_recoveries

想控制恢復一個分片時開啟的並行流數量,使用:

indices.recovery.concurrent_streams

與流數量密切相關的,是用於恢復的總可用網路頻寬:

indices.recovery.max_bytes_per_sec

除了所有這些屬性,最佳配置將依賴於所使用的硬體。如果有SSD硬碟以及萬兆光纖網路,那麼最佳配置將完全不同於使用普通磁碟和千兆網路卡。

以上所有屬性都將在叢集重啟後生效。

小貼士9:執行緒池屬性防止資料丟失

ElasticSearch節點有很多的執行緒池,用於提升一個節點中的執行緒管理效率。

在Loggly,索引時使用了批量操作模式,並且我們發現通過threadpool.bulk.queue_size屬性為批量操作的執行緒池設定正確的大小,對於防止因批量重試而可能引起的資料丟失是極其關鍵的。

threadpool.bulk.queue_size: 5000

這會告訴ES,當沒有可用執行緒來執行一個批量請求時,可排隊在該節點執行的分片請求的數量。該值應當根據批量請求的負載來設定。如果批量請求數量大於佇列大小,就會得到一個下文展示的RemoteTransportException異常。

正如上文所述,一個分片包含一個批量操作佇列,所以這個數字需要大於想傳送的併發批量請求的數量與這些請求的分片數的乘積。例如,一個單一的批量請求可能包含10個分片的資料,所以即使只傳送一個批量請求,佇列大小也必須至少為10。這個值設定太高,將會吃掉很多JVM堆空間(並且表明正在推送更多叢集無法輕鬆索引的資料),但確實能轉移一些排隊情況到ES,簡化了客戶端。

既要保持屬性值高於可接受的負載,又要平滑地處理客戶端程式碼的RemoteTransportException異常。如果不處理該異常,將會丟失資料。我們模擬使用一個大小為10的佇列來傳送大於10個的批處理請求,獲得了以下所示異常。

RemoteTransportException[[<Bantam>][inet[/192.168.76.1:9300]][bulk/shard]]; nested: 
EsRejectedExecutionException[rejected execution (queue capacity 10) on 
org.elasticsearch.action.support.replication.TransportShardReplicationOperationAction$AsyncShardOperationAction$1@13fe9be];

為2.0版本以前的使用者再贈送一個小貼士:最小化Mapping重新整理時間

如果你仍在使用2.0版本以前的ES,且經常會更新屬性mapping,那麼可能會發現叢集的任務等待佇列有一個較大的refresh_mappings請求數。對它自身來說,這並不壞,但可能會有滾雪球效應嚴重影響叢集效能。

如果確實遇到這種情況,ES提供了一個可配置引數來幫助應對。可按下述方式使用該引數:

indices.cluster.send_refresh_mapping: false

那麼,這是怎麼個意思,為什麼可以湊效?

當索引中出現一個新的屬性時,新增該屬性的資料節點會更新它自己的mapping,然後把新的mapping傳送給主節點。如果這個新的mapping還在主節點的等待任務佇列中,同時主節點發布了自己的下一個叢集狀態,那麼資料節點將接收到一個過時的舊版本mapping。通常這會讓它傳送一個更新mapping的請求到主節點,因為直到跟該資料節點有關,主節點一直都擁有錯誤的mapping資訊。這是一個糟糕的預設行為————該節點應該有所行動來保證主節點上擁有正確的mapping資訊,而重發新的mapping資訊是一個不錯的選擇。

但是,當有很多的mapping更新發生,並且主節點無法持續堅持時,會有一個亂序聚集(stampeding horde)效應,資料節點發給主節點的重新整理訊息就可能氾濫。

引數indices.cluster.send_refresh_mapping可以禁用掉預設行為,因此消除這些從資料節點傳送到主節點的refresh_mapping請求,可以讓主節點保持最新。即時沒有重新整理請求,主節點也最終會看到最初的mapping變更,並會釋出一個包含該變更的叢集狀態更新。

總結:ElasticSearch的可配置屬性是其彈性的關鍵

對Loggly來講ElasticSearch可深度配置的屬性是一個巨大的優勢,因為在我們的使用案例中已經最大限度發揮了ElasticSearch的引數威力(有時更甚)。如果在你自己應用進化的當前階段ES預設配置工作得足夠好了,請放心,隨著應用的發展你還會有很大的優化空間。

相關文章