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:falseData node: node.master:false node.data:trueClient 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: 30sdiscovery.zen.minimum_master_nodes: 2discovery.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%未使用),或者最小可用空間大?。ū热?ldquo;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[[為2.0版本以前的用戶再贈送一個小貼士:最小化Mapping刷新時間
如果你仍在使用2.0版本以前的ES,且經常會更新屬性mapping,那么可能會發現集群的任務等待隊列有一個較大的refresh_mappings請求數。對它自身來說,這并不壞,但可能會有滾雪球效應嚴重影響集群性能。
如果確實遇到這種情況,ES提供了一個可配置參數來幫助應對??砂聪率龇绞绞褂迷搮担?/p> 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默認配置工作得足夠好了,請放心,隨著應用的發展你還會有很大的優化空間。