編者按 :Elasticsearch(簡稱ES)作為一種分散式、高擴充套件、高實時的搜尋與資料分析引擎,能使資料在生產環境變得更有價值,自ES從誕生以來,其應用越來越廣泛,特別是大資料領域,功能也越來越強大。但當前,ES多資料中心大規模叢集依然面臨著資料量大、查詢週期長、叢集規模大、聚合分析要求高等諸多挑戰。
本文針對當前面臨的問題,結合百分點大資料技術團隊在某海外國家級多資料中心的ES叢集建設經驗,總結了ES叢集規劃與效能調優方法,供工程師們參考
一、ES叢集建設實踐
1. 叢集拆分
叢集規模過大會導致Master節點壓力比較大,造成索引的建立刪除、分片分配等操作較慢,嚴重影響叢集穩定性。所以將叢集進行拆分,業務上ES叢集儲存三種業務型別資料A、B、C,資料佔比大約A:B:C=8:3:1,根據業務型別,拆分A資料型別6個叢集,B資料型別2個叢集,C資料型別2個叢集,儲存在兩個中心機房,每個叢集不超過100個節點。(推薦叢集節點數不要超過伺服器核心數 * 5)使用跨叢集搜尋(Cross-cluster search),客戶端在查詢資料的時候連線Query叢集,透過Query叢集查詢10個資料叢集的資料。
2. 角色分離
ES叢集中有多種角色,協調(coordinator)節點,主(master)節點,資料(data)節點。
一臺節點可以配置成多種角色,角色分離可以避免各種角色效能互相影響。比如一個節點既是資料節點也是協調節點,可能協調角色聚合時佔用大量資源導致資料角色寫入資料出現異常。
透過配置elasticsearch.yml來使一個節點只承擔一種角色。
主節點:當有一半主節點下線整個叢集也就不可用了,一般一個叢集設定3臺主節點,可以容許一臺主節點下線。不要配置偶數臺主節點,因為配置4臺主節點也僅能容許一臺主節點下線。
node.master: true node.data: false
資料節點:根據自己的資料情況配置合適的節點數量。資料節點下線會導致資料不完整,叢集仍能正常工作。
node.master: false node.data: true
協調節點:任何一個節點都可以是協調節點,我們透過配置幾個僅協調節點來單獨行使協調功能。
node.master: false node.data: false
3. 版本選擇
在當初專案選型的時候,ES剛剛釋出7.X,我們選擇相對穩定的6.7.2版本,在後期大規模測試過程中發現,6.X版本有些侷限性,此時ES已經發布7.8版本了。透過調研最終選用7.6.2版本,原因主要有下面兩點:
(1)後設資料壓力
在6.7.2版本,叢集shard個數達到5w時,更新template或建立index會出現大於30s的情況。詳細參考問題頁:https://github.com/elastic/elasticsearch/pull/47817
在7.6.2版本,叢集shard個數達到5w時,更新template或建立index在3s內。
我們shard個數最多的叢集達到了4.4w。
(2)跨叢集搜尋(Cross-cluster search)
當存在三個叢集:Query叢集、data1叢集、data2叢集時,配置data1、data2叢集為Query叢集的遠端叢集,此時可以透過向Query叢集傳送請求來獲取data1、data2叢集的資料。
跨叢集搜尋提供了兩個處理網路延遲的選項:
最小化網路傳輸
您向Query叢集傳送跨叢集搜尋請求,Query叢集中的協調節點接收並解析請求;
協調節點向每個叢集(包括Query叢集)傳送單個搜尋請求,每個叢集獨立執行搜尋請求,將其自己的叢集級別設定應用於請求;
每個遠端叢集將其搜尋結果傳送回Query叢集的協調節點;
從每個叢集收集結果後,Query叢集的協調節點在跨叢集搜尋響應中返回最終結果。
不使用最小化網路傳輸
您向Query叢集傳送跨叢集搜尋請求,Query叢集中的協調節點接收並解析請求;
協調節點向每個遠端叢集傳送搜尋分片API請求;
每個遠端叢集將其響應傳送回協調節點,此響應包含有關將在其上執行跨叢集搜尋請求的索引和分片的資訊;
協調節點向每個分片傳送搜尋請求,包括其自己叢集中的分片,每個分片獨立執行搜尋請求;
每個分片將其搜尋結果傳送回協調節點;
從每個叢集收集結果後,協調節點在跨叢集搜尋響應中返回最終結果。
更詳細的說明參考:https://www.elastic.co/guide/en/elasticsearch/reference/7.6/modules-cross-cluster-search.html#ccs-min-roundtrips
最小化網路傳輸會減少與遠端叢集之間的網路往返次數,這減少了網路延遲對搜尋速度的影響,同時各個遠端叢集的協調節點會預先將自己叢集的資料聚合一次。即便如此,Query叢集協調節點壓力還會比較大,因為要聚合所有叢集返回的資料。
我們根據最小化網路傳輸流程圖,分析如下聚合時協調節點的壓力:
coordinator node2和coordinator node3接收各自資料節點的1w條資料(shard數 * shard_size)並全量返回給coordinator node1,最終在coordinator node1上會有2w條資料,取前10條(size)返回給客戶端。當我們查詢的叢集、索引或者索引的shard更多時,coordinator node1的壓力會越來越大。測試過程中總會出現OOM的情況。
基於此考慮,我們修改部分原始碼,增加了coordinator_size引數,在第3步資料叢集將搜尋結果發回coordinator node1時只返回TOP N(前coordinator_size)。對於一個叢集,透過shard_size來平衡精度與效能;對於整個跨叢集方案,透過coordinator_size來平衡精度與效能。
不使用最小化網路傳輸由於資料不會經過coordinatornode2和coordinator node3,所以不支援這樣的修改。
在6.7.2版本,跨叢集搜尋只支援不使用最小化網路傳輸的方式。
在7.6.2版本,預設使用最小化網路傳輸的方式進行跨叢集搜尋,可以在請求中新增ccs_minimize_roundtrips:false引數來選擇不使用最小化網路傳輸。
4. 拆分索引
業務上儲存的是日誌資料,只有增加,沒有變更,按時間累積,天然對索引的拆分友好支援,並且如果按天拆分索引有以下好處:
方便資料刪除,超過儲存週期的資料直接使用定時指令碼在夜間刪除索引即可;
提升搜尋聚合的效率,業務上對資料的搜尋必須要攜帶時間範圍的引數,根據該時間引數轉化為具體的索引這樣搜尋的shard就會比較少;
方便後期修改shard個數和mapping,雖然shard個數和mapping一般不修改,但也會遇到特殊情況,如果需要修改,我們只需要修改template,之後新索引都會應用最新的shard設定和mapping設定,等業務滾動資料儲存週期天數後所有資料就都會應用最新規則。
5. 副本數量
越多的副本數量會增加搜尋的併發數,但是同時也會影響寫入索引的效率,佔用磁碟空間。可以根據資料安全性來設定副本的數量,一般一個副本是足夠的,同時可以考慮在索引建立時擁有更多的副本,當資料超過一定時間而變得不那麼重要後,透過API減少副本個數。
二、ES叢集配置經驗
1. 記憶體和CPU
(1)記憶體分配
Lucene能很好利用檔案系統的快取,它是透過系統核心管理的。如果沒有足夠的檔案系統快取空間,效能會受到影響。此外,專用於堆的記憶體越多意味著其他所有使用doc values 的欄位記憶體越少。參考以下原則:
當機器記憶體小於64G時,遵循通用的原則,50% 給 ES,50% 留給 lucene。
當機器記憶體大於64G時,遵循以下原則:
如果主要的使用場景是全文檢索,那麼建議給ES Heap分配4~32G的記憶體即可;其它記憶體留給作業系統,供lucene使用(segments cache),以提供更快的查詢效能;
如果主要的使用場景是聚合或排序,並且大多數是numerics,dates,geo_points以及非分詞的字串,建議分配給ES Heap 4~32G的記憶體即可,其餘部分留給作業系統來快取doc values;
如果使用場景是基於分詞字串的聚合或排序,意味著需要fielddata,這時需要更多的heap size,建議機器上執行多ES例項,每個例項保持不超過50%的ES heap設定。
記憶體配置不要超過32G,如果堆大小小於32GB,JVM可以利用指標壓縮,這可以大大降低記憶體的使用:每個指標4位元組而不是8位元組。這裡32G可能因為某些因素的影響有些誤差,最好配置到31G。
記憶體最小值(Xms)與最大值(Xmx)的大小配置相等,防止程式在執行時改變堆記憶體大小,這是一個很耗系統資源的過程。
配置jvm.options
-Xms31g -Xmx31g
(2)GC設定
保持GC的現有設定,預設設定為:Concurrent-Mark and Sweep(CMS),別換成 G1 GC,因為目前G1還有很多BUG。
(3)禁止swap
禁止swap,一旦允許記憶體與磁碟的交換,會引起致命的效能問題,可以透過在 elasticsearch.yml 中配置以下引數以保持JVM鎖定記憶體,保證ES的效能。
bootstrap.memory_lock: true
(4)核心數
processors配置引數的值決定了節點allocated_processors的引數值,而ES很多執行緒池的大小都是基於allocated_processors的值來計算的。
修改elasticsearch.yml
elasticsearch.yml
node.processors: 56
在以下情況可以考慮調整該引數:
在一臺伺服器部署多個ES例項,此時調整引數為處理器實際核心數一半;
錯誤地檢測處理器的數量,此時調整引數進行修正;
實際處理器核心數大於32,ES預設處理器核心數最大限制為32個,如果物理機的處理器核心數超過了32個,為了更充分利用CPU,可以調整引數為實際處理器核心數。如果可以選擇CPU,更多的核心數比更快的CPU更有意義。
2. 寫入
(1)增加Refresh時間間隔
ES寫入資料時先寫入memory buffer中,memory buffer會週期性(index.refresh_interval預設1s)或者寫滿後做refresh操作,將內容寫入到一個新的segment中。此時資料可以被搜尋,這就是為什麼ES提供的是近實時的搜尋。如果系統對資料延遲要求不高的話,透過延長refresh時間間隔(比如index.refresh_interval設定為30s),可以有效地提高索引速度,同時減少segment個數降低segment合併壓力。
修改索引的settings:
PUT /my_index/_settings { "index" : { "refresh_interval" : "30s" } }
在匯入大量資料的時候可以暫時設定index.refresh_interval: -1和index.number_of_replicas:0來提高效能,資料匯入完成後還原設定。
(2)修改index_buffer_size的設定
上一條說memory buffer寫滿時也會觸發refresh操作,為了減少refresh操作,我們同時也要配合增加memory buffer的大小。這是一個全域性靜態配置,會應用於一個節點上所有的分片上。
修改elasticsearch.yml:
# 接受百分比或位元組大小值。它預設為10%,這意味著10%分配給節點的總堆中的將用作所有分片共享的索引緩衝區大小。 indices.memory.index_buffer_size: 10% # 如果index_buffer_size指定為百分比,則此設定可用於指定絕對最小值。預設為48mb。 indices.memory.min_index_buffer_size: 48mb # 如果index_buffer_size指定為百分比,則此設定可用於指定絕對最大值。預設為無界。 indices.memory.max_index_buffer_size: 10240mb
(3)修改translog相關的設定
refresh操作後,資料寫入segment檔案中,此時segment在OS Cache中,以上所有資料都儲存在記憶體裡,如果伺服器異常重啟則資料都不可恢復。所以資料在寫入memory buffer的同時,記錄當前操作到translog,每30分鐘或者當translog中的資料大小達到閾值後,會觸發一次flush操作將OS Cache中的segment落盤,同時清理translog。
translog預設在每次索引、刪除、更新或批次請求後會提交到磁碟。我們可以透過設定使translog非同步提交來提高效能:
PUT /my_index/_settings { "index" : { "translog.durability": "async", # 重新整理方式。預設request 同步, async 非同步 "translog.sync_interval": "10s" # 重新整理頻率。預設5s,不能低於100ms } }
也可以控制translog的閾值來降低flush的頻率:
PUT /my_index/_settings { "index" : { "translog.flush_threshold_size": "1024mb" # translog閾值。預設512mb。如果達到則會強制flush,否則需要等待30分鐘 } }
3. 分配
(1)延遲分配配置
當叢集中某個節點離開叢集時:
master節點會將此節點上的主分片對應的副本分片提升為主分片;
在其他節點上重建因節點下線而丟失的分片;
重建完成後很可能還會觸發叢集資料平衡;
如果節點又重新加入叢集,叢集資料自動平衡,將一些分片遷移到此節點。
節點很可能因為網路原因或硬體原因短暫離開叢集,過幾分鐘又重新加入叢集,觸發上述操作會導致叢集有比較大的開銷,是完全沒有必要的。當設定了延時分配為5分鐘時,節點下線時,只會執行上述第1步操作,此時的叢集處於yellow狀態,在5分鐘內下線的節點重新加入叢集則叢集直接恢復green。避免了很多分片的遷移。透過API修改延時分配時間,值為0則表示會立即分配。
cluster.routing.allocation.balance.shard:預設0.45f,定義分配在該節點的分片數的因子閾值=因子*(當前節點的分片數-叢集的總分片數/節點數,即每個節點的平均分片數);
cluster.routing.allocation.balance.index:預設0.55f,定義分配在該節點某個索引的分片數的因子,閾值=因子*(當前節點的某個索引的分片數-索引的總分片數/節點數,即每個節點某個索引的平均分片數);
cluster.routing.allocation.balance.threshold:預設1.0f,超出這個閾值就會重新分配分片。
根據配置可以算出,當某一節點超過每個節點平局分片數2.2(1/0.45)個分片時會觸發rebalance。
當某個節點my_index的分片數超過每個節點my_index的平均分片數1.8(1/0.55)個分片時會觸發rebalance。
4. Mapping
(1)欄位型別配置
不需要被分詞的欄位應使用not_analyzed;
不需要被搜尋的欄位設定index:false;
不需要聚合的欄位設定doc_value:false;
僅用於精確匹配而不進行範圍查詢的數值欄位使用keyword型別的效率更高。numeric型別從lucene6.0開始,使用了一種名為block KD tree的儲存結構。這種結構比較適用於範圍查詢,在精確匹配方面沒有倒排索引的效能好。
(2)使用自動生成的_id
避免自定義_id,建議用ES的預設ID生成策略,ES在寫入對id判斷是否存在時對自動生成的id有最佳化。同時避免使用_id欄位進行排序或聚合,如果有需求建議將該_id欄位的內容複製到自定義已啟用doc_values 的欄位中。
(3)禁用_source
_source儲存了原始的document內容,如果沒有獲取原始文件資料的需求,可透過設定includes、excludes屬性來定義放入_source的欄位。
"mappings":{ "_source": { "excludes": [ "content" ] } }
案例:在我們的方案中,考慮在架構上,原始資料儲存在分散式檔案系統。所以在ES中可以不儲存content欄位(其他欄位仍然儲存),只為content欄位建立倒排索引用於全文檢索,而實際內容從分散式檔案系統中獲取。
收益:
降低ES中儲存;
提高查詢效能(OS cache中能裝更多的Segment);
shard的merge、恢復和遷移成本降低。
限制:
此欄位不能高亮;
update、update_by_query、reindex APIs不能使用。
下面是我們根據業務資料特點測試不儲存content欄位對儲存空間和查詢的影響。
(1)測試不儲存content欄位對磁碟儲存的影響
資料分佈:
可以看出,不儲存content欄位可以加快搜尋,對聚合影響不大。
總的來說,需要根據業務場景考慮益弊,比如是否對資料進行更新、reindex、高亮,或者說透過其他方式實現對資料的更新、reindex、高亮的成本如何。
三、ES叢集設計經驗
1. 批次提交
bulk批次寫入的效能比你一條一條寫入的效能要好很多,並不是bulk size越大越好,而是根據你的叢集等環境具體要測試出來的,因為越大的bulk size會導致記憶體壓力過大,最好不要超過幾十m。
2. 多執行緒寫入
單執行緒傳送bulk請求是無法最大化ES叢集寫入的吞吐量的。如果要利用叢集的所有資源,就需要使用多執行緒併發將資料bulk寫入叢集中。為了更好的利用叢集的資源,這樣多執行緒併發寫入,可以減少每次底層磁碟fsync的次數和開銷。
3. Merge只讀索引
合併Segment對ES非常重要,過多的Segment會消耗檔案控制程式碼、記憶體和CPU時間,影響查詢速度。Segment的合併會消耗掉大量系統資源,儘量在請求較少的時候進行,比如在夜裡兩點ForceMerge前一天的索引。
POST my_index/_forcemerge?only_expunge_deletes=false&max_num_segments=1&flush=true
4. Filter代替Query
如果涉及評分相關業務使用Query,其他場景推薦使用Filter查詢。在做聚合查詢時,filter經常發揮更大的作用。因為沒有評分ES的處理速度就會提高,提升了整體響應時間,同時filter可以快取查詢結果,而Query則不能快取。
5. 避免深分頁
分頁搜尋:每個分片各自查詢的時候先構建from+size的優先佇列,然後將所有的文件 ID 和排序值返回給協調節點。協調節點建立size為number_of_shards *(from + size) 的優先佇列,對資料節點的返回結果進行合併,取全域性的from ~ from+size返回給客戶端。
什麼是深分頁?
協調節點需要等待所有分片返回結果,然後再全域性排序。因此會建立非常大的優先佇列。比如一個索引有10個shard,查詢請求from:9990,size:10(查詢第1000頁),那麼每個shard需要返回1w條資料,協調節點就需要對10w條資料進行排序,僅僅為了獲取10條資料而處理的大量的資料。且協調節點中的資料量會被分片的數量和頁數所放大,因而一旦使用了深分頁,協調節點會需要對大量的資料進行排序,影響查詢效能。
如何避免深分頁?
限制頁數,限制只能獲取前100頁資料。翻頁操作一般是人為觸發的,並且人的行為一般不會翻頁太多。ES自身提供了max_result_window引數來限制返回的資料量,預設為1w。每頁返回100條資料,獲取100頁以後的資料就會報錯。
使用Scroll或search_after代替分頁查詢,Scroll 和 search_after都可以用於深分頁,不支援跳頁,適合拉取大量資料,目前官方推薦使用search_after代替 scroll。
6. 硬碟
固態硬碟比機械硬碟效能好很多;
使用多盤RAID0,不要以為ES可以配置多盤寫入就和RAID0是一樣的,主要是因為一個shard對應的檔案,只會放到其中一塊磁碟上,不會跨磁碟儲存,只寫一個shard的時候其他盤是空閒的,不過RAID0中一塊盤出現問題會導致整個RAID0的資料丟失。
7. 列舉空間大的欄位聚合方案
(1)根據欄位路由到固定shard
這樣在聚合時每個shard的bucket少,並且精度幾乎不損失,但是會造成資料傾斜。如果欄位資料比較平均可以選用,但是我們業務場景不適用。
(2)調整欄位的儲存型別
在欄位型別配置裡介紹了精確匹配時keyword比數值型別效率高,我們測試了相同資料keyword和long的聚合效能。
叢集建立4個索引(4天資料),每個索引120個shard,每個shard大小為30G,總資料量為:3.5T。
其資料分佈為1k的佔比50%、10k的佔比30%、100k的佔比20%。
結束語
實時資料分析和文件搜尋是ES的常用場景之一,結合客戶資料特點,百分點大資料技術團隊對ES進行了最佳化和一定的改造,並將這些能力沉澱到了我們的大資料平臺上,以更好的滿足客戶的業務需求。透過調優,在生產環境中ES叢集已經穩定執行近兩年的時間了。在實際部署前,對叢集穩定性和效能進行了多次大規模測試,也模擬了多種可能發生的故障場景,正是不斷地測試,發現了一些侷限性,對版本升級,對原始碼修改,也在不斷測試中增加了更多的最佳化項來滿足需求。
文中的最佳化實踐總體上非常的通用,希望可以給大家帶來一定的參考價值。
文章的最後,就以ILM作為彩蛋吧!
ILM生命週期管理:
索引生命週期的四個階段
Hot:index正在查詢和更新,效能好的機器會設定為Hot節點來進行資料的讀寫。
Warm:index不再更新,但是仍然需要查詢,節點效能一般可以設定為Warm節點。
Cold:index不再被更新,且很少被查詢,資料仍然可以搜尋,但是能接受較慢的查詢,節點效能較差,但有大量的磁碟空間。
Delete:資料不需要了,可以刪除。
#節點屬性可以透過 elasticsearch.yml 進行配置 # node.attr.xxx: xxx,hot warm cold node.attr.data: warm
這四個階段按照Hot,Warm,Cold,Delete順序執行,上一個階段沒有執行完成是不會執行下一個階段的,對於不存在的階段,會跳過該階段進入到下一個階段。
示例:建立索引生命週期策略來管理elasticsearch_metrics-YYYY.MM.dd日誌資料。
策略如下:
在index建立後立即進入hot階段:當index建立超過1天或者文件數超過3000w或者主分片大小超過50g後,生成新index;
舊index進入到warm階段,segment數量merge為1,index遷移至屬性data為warm的節點;
warm階段完成後,進入delete階段,index rollover時間超過30天后,將index 刪除。
(4)後續資料讀寫使用指定的別名elasticsearch_metrics
Actions
各階段支援的actions參考:Index lifecycle actions選擇對應ES版本。(https://www.elastic.co/guide/en/elasticsearch/reference/7.6/ilm-actions.html)
不同版本各個階段支援的action有變化,因此建議手動測試一下,因為7.6版本官方文件說明在hot階段如果存在rollover則可指定forceMerge,但實際測試7.6所有版本都不支援,7.7.0之後才可以這樣設定。
參考資料
[1] https://www.elastic.co/guide/en/elasticsearch/reference/7.6/index.html
[2] https://cloud.tencent.com/developer/article/1661414
[3] 《elastic stack實戰手冊》