天天看點

ES的性能調優技巧

問題導讀:

1、調優配置檔案如何設定?

2、系統層面應該如何進行調優?

3、從ES使用方式角度如何進行調優?

一、配置檔案調優

elasticsearch.yml

1、記憶體鎖定

bootstrap.memory_lock:true允許JVM鎖住記憶體,禁止作業系統交換出去。

2、zen.discovery

Elasticsearch預設被配置為使用單點傳播發現,以防止節點無意中加入叢集。多點傳播發現應該永遠不被使用在生産環境了,否則你得到的結果就是一個節點意外的加入到了你的生産環境,僅僅是因為他們收到了一個錯誤的多點傳播信号。

ES是一個P2P類型的分布式系統,使用gossip協定,叢集的任意請求都可以發送到叢集的任一節點,然後ES内部會找到需要轉發的節點,并且與之進行通信。

在ES1.x的版本,ES預設是開啟多點傳播,啟動ES之後,可以快速将區域網路内叢集名稱,預設端口的相同執行個體加入到一個大的叢集,後續再ES2.x之後,都調整成了單點傳播,避免安全問題和網絡風暴。

單點傳播 discovery.zen.ping.unicast.hosts,建議寫入叢集内所有的節點及端口,如果新執行個體加入叢集,新執行個體隻需要寫入目前叢集的執行個體,即可自動加入到目前叢集,之後再處理原執行個體的配置即可,新執行個體加入叢集,不需要重新開機原有執行個體;

節點zen相關配置:discovery.zen.ping_timeout:判斷master選舉過程中,發現其他node存活的逾時設定,主要影響選舉的耗時,參數僅在加入或者選舉 master 主節點的時候才起作用discovery.zen.join_timeout:節點确定加入到叢集中,向主節點發送加入請求的逾時時間,預設為3sdiscovery.zen.minimum_master_nodes:參與master選舉的最小節點數,當叢集能夠被選為master的節點數量小于最小數量時,叢集将無法正常選舉。

3、故障檢測(fault detection)

兩種情況下會進行故障檢測:

  •     第一種是由master向叢集的所有其他節點發起ping,驗證節點是否處于活動狀态;
  •     第二種是:叢集每個節點向master發起ping,判斷master是否存活,是否需要發起選舉。故障檢測需要配置以下設定使用 形如:discovery.zen.fd.ping_interval節點被ping的頻率,預設為1s。discovery.zen.fd.ping_timeout 等待ping響應的時間,預設為 30s,運作的叢集中,master 檢測所有節點,以及節點檢測 master 是否正常。discovery.zen.fd.ping_retries ping失敗/逾時多少導緻節點被視為失敗,預設為3。

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

4、隊列數量

不建議盲目加大ES的隊列數量,如果是偶發的因為資料突增,導緻隊列阻塞,加大隊列size可以使用記憶體來緩存資料;如果是持續性的資料阻塞在隊列,加大隊列size除了加大記憶體占用,并不能有效提高資料寫入速率,反而可能加大ES當機時候,在記憶體中可能丢失的上資料量。

哪些情況下,加大隊列size呢?GET /_cat/thread_pool,觀察api中傳回的queue和rejected,如果确實存在隊列拒絕或者是持續的queue,可以酌情調整隊列size。

https://www.elastic.co/guide/en/elasticsearch/reference/6.x/modules-threadpool.html

5、記憶體使用

設定indices的記憶體熔斷相關參數,根據實際情況進行調整,防止寫入或查詢壓力過高導緻OOM:

  •     indices.breaker.total.limit:50%,叢集級别的斷路器,預設為jvm堆的70%;
  •     indices.breaker.request.limit:10%,單個request的斷路器限制,預設為jvm堆的60%;
  •     indices.breaker.fielddata.limit:10%,fielddata breaker限制,預設為jvm堆的60%。

https://www.elastic.co/guide/en/elasticsearch/reference/6.x/circuit-breaker.html

根據實際情況調整查詢占用cache,避免查詢cache占用過多的jvm記憶體,參數為靜态的,需要在每個資料節點配置。indices.queries.cache.size: 5%,控制過濾器緩存的記憶體大小,預設為10%。接受百分比值,5%或者精确值,例如512mb。

https://www.elastic.co/guide/en/elasticsearch/reference/6.x/query-cache.html

6、建立shard

如果叢集規模較大,可以阻止建立shard時掃描叢集内全部shard的中繼資料,提升shard配置設定速度。

cluster.routing.allocation.disk.include_relocations: false,預設為true。

https://www.elastic.co/guide/en/elasticsearch/reference/6.x/disk-allocator.html

二、系統層面調優

1、jdk版本

目前根據官方建議,選擇比對的jdk版本。

2、jdk記憶體配置

首先,-Xms和-Xmx設定為相同的值,避免在運作過程中再進行記憶體配置設定,同時,如果系統記憶體小于64G,建議設定略小于機器記憶體的一半,剩餘留給系統使用。

同時,jvm heap建議不要超過32G(不同jdk版本具體的值會略有不同),否則jvm會因為記憶體指針壓縮導緻記憶體浪費,詳見:

https://www.elastic.co/guide/cn/elasticsearch/guide/current/heap-sizing.html

3、交換分區

關閉交換分區,防止記憶體發生交換導緻性能下降(部分情況下,甯死勿慢) swapoff -a

4、檔案句柄

Lucene 使用了 大量的 檔案。同時,Elasticsearch 在節點和 HTTP 用戶端之間進行通信也使用了大量的套接字,所有這一切都需要足夠的檔案描述符,預設情況下,linux預設運作單個程序打開1024個檔案句柄,這顯然是不夠的,故需要加大檔案句柄數 ulimit -n 65536。

https://www.elastic.co/guide/en/elasticsearch/reference/6.5/setting-system-settings.html

5、mmap

Elasticsearch 對各種檔案混合使用了 NioFs( 注:非阻塞檔案系統)和 MMapFs ( 注:記憶體映射檔案系統)。請確定你配置的最大映射數量,以便有足夠的虛拟記憶體可用于 mmapped 檔案。

這可以暫時設定:sysctl -w vm.max_map_count=262144 或者你可以在 /etc/sysctl.conf 通過修改 vm.max_map_count 永久設定它。

https://www.elastic.co/guide/cn/elasticsearch/guide/current/_file_descriptors_and_mmap.html

6、磁盤

如果你正在使用 SSDs,確定你的系統 I/O 排程程式是配置正确的。當你向硬碟寫資料,I/O 排程程式決定何時把資料實際發送到硬碟。大多數預設 nix 發行版下的排程程式都叫做 cfq(完全公平隊列)。但它是為旋轉媒體優化的:機械硬碟的固有特性意味着它寫入資料到基于實體布局的硬碟會更高效。這對 SSD 來說是低效的,盡管這裡沒有涉及到機械硬碟。

但是,deadline 或者 noop 應該被使用。deadline 排程程式基于寫入等待時間進行優化, noop 隻是一個簡單的 FIFO 隊列。echo noop > /sys/block/sd/queue/scheduler。

7、磁盤挂載

mount -o noatime,data=writeback,barrier=0,nobh /dev/sd* /esdata* 其中,noatime,禁止記錄通路時間戳;data=writeback,不記錄journal;barrier=0,因為關閉了journal,是以同步關閉barrier;nobh,關閉buffer_head,防止核心影響資料IO。

8、磁盤其他注意事項

使用 RAID 0。條帶化 RAID 會提高磁盤I/O,代價顯然就是當一塊硬碟故障時整個就故障了,不要使用鏡像或者奇偶校驗 RAID 因為副本已經提供了這個功能。

另外,使用多塊硬碟,并允許 Elasticsearch 通過多個 path.data 目錄配置把資料條帶化配置設定到它們上面。不要使用遠端挂載的存儲,比如 NFS 或者 SMB/CIFS。這個引入的延遲對性能來說完全是背道而馳的。

三、Elasticsearch使用方式調優

當Elasticsearch本身的配置沒有明顯的問題之後,發現ES使用還是非常慢,這個時候,就需要我們去定位ES本身的問題了,首先祭出定位問題的第一個指令:

1、hot_threads

GET /_nodes/hot_threads&interval=30s

抓取30s的節點上占用資源的熱線程,并通過排查占用資源最多的TOP線程來判斷對應的資源消耗是否正常。一般情況下,bulk,search類的線程占用資源都可能是業務造成的,但是如果是merge線程占用了大量的資源,就應該考慮是不是建立index或者刷磁盤間隔太小,批量寫入size太小造成的。

https://www.elastic.co/guide/en/elasticsearch/reference/6.x/cluster-nodes-hot-threads.html

2、pending_tasks

GET /_cluster/pending_tasks

有一些任務隻能由主節點去處理,比如建立一個新的索引或者在叢集中移動分片,由于一個叢集中隻能有一個主節點,是以隻有這一master節點可以處理叢集級别的中繼資料變動。

在99.9999%的時間裡,這不會有什麼問題,中繼資料變動的隊列基本上保持為零。在一些罕見的叢集裡,中繼資料變動的次數比主節點能處理的還快,這會導緻等待中的操作會累積成隊列。

這個時候可以通過pending_tasks api分析目前什麼操作阻塞了ES的隊列,比如,叢集異常時,會有大量的shard在recovery,如果叢集在大量建立新字段,會出現大量的put_mappings的操作,是以正常情況下,需要禁用動态mapping。

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

3、字段存儲

目前es主要有doc_values,fielddata,storefield三種類型,大部分情況下,并不需要三種類型都存儲,可根據實際場景進行調整:

  •     目前用得最多的就是doc_values,列存儲,對于不需要進行分詞的字段,都可以開啟doc_values來進行存儲(且隻保留keyword字段),節約記憶體,當然,開啟doc_values會對查詢性能有一定的影響,但是,這個性能損耗是比較小的,而且是值得的;
  •     fielddata建構和管理 100% 在記憶體中,常駐于 JVM 記憶體堆,是以可用于快速查詢,但是這也意味着它本質上是不可擴充的,有很多邊緣情況下要提防,如果對于字段沒有分析需求,可以關閉fielddata;
  •     storefield主要用于_source字段,預設情況下,資料在寫入es的時候,es會将doc資料存儲為_source字段,查詢時可以通過_source字段快速擷取doc的原始結構,如果沒有update,reindex等需求,可以将_source字段disable;
  •     _all,ES在6.x以前的版本,預設将寫入的字段拼接成一個大的字元串,并對該字段進行分詞,用于支援整個doc的全文檢索,在知道doc字段名稱的情況下,建議關閉掉該字段,節約存儲空間,也避免不帶字段key的全文檢索;
  •     norms:搜尋時進行評分,日志場景一般不需要評分,建議關閉。

4、tranlog

Elasticsearch 2.0之後為了保證不丢資料,每次 index、bulk、delete、update 完成的時候,一定觸發重新整理 translog 到磁盤上,才給請求傳回 200 OK。這個改變在提高資料安全性的同時當然也降低了一點性能。如果你不在意這點可能性,還是希望性能優先,可以在 index template 裡設定如下參數:

{

    "index.translog.durability": "async"

}
           

index.translog.sync_interval:

對于一些大容量的偶爾丢失幾秒資料問題也并不嚴重的叢集,使用異步的 fsync 還是比較有益的。

比如,寫入的資料被緩存到記憶體中,再每5秒執行一次 fsync ,預設為5s。小于的值100ms是不允許的。

index.translog.flush_threshold_size:

translog存儲尚未安全儲存在Lucene中的所有操作。雖然這些操作可用于讀取,但如果要關閉并且必須恢複,則需要重新編制索引。

此設定控制這些操作的最大總大小,以防止恢複時間過長。達到設定的最大size後,将發生重新整理,生成新的Lucene送出點,預設為512mb。

5、refresh_interval

執行重新整理操作的頻率,這會使索引的最近更改對搜尋可見,預設為1s,可以設定-1為禁用重新整理,對于寫入速率要求較高的場景,可以适當的加大對應的時長,減小磁盤io和segment的生成。

6、禁止動态mapping

動态mapping的壞處:

  •     造成叢集中繼資料一直變更,導緻叢集不穩定;
  •     可能造成資料類型與實際類型不一緻;
  •     對于一些異常字段或者是掃描類的字段,也會頻繁的修改mapping,導緻業務不可控。

動态mapping配置的可選值及含義如下:

  •     true:支援動态擴充,新增資料有新的字段屬性時,自動添加對于的mapping,資料寫入成功;
  •     false:不支援動态擴充,新增資料有新的字段屬性時,直接忽略,資料寫入成功 ;
  •     strict:不支援動态擴充,新增資料有新的字段時,報錯,資料寫入失敗。

7、批量寫入

批量請求顯然會大大提升寫入速率,且這個速率是可以量化的,官方建議每次批量的資料實體位元組數5-15MB是一個比較不錯的起點,注意這裡說的是實體位元組數大小。

文檔計數對批量大小來說不是一個好名額。

比如說,如果你每次批量索引 1000 個文檔,記住下面的事實:1000 個 1 KB 大小的文檔加起來是 1 MB 大。1000 個 100 KB 大小的文檔加起來是 100 MB 大。這可是完完全全不一樣的批量大小了。

批量請求需要在協調節點上加載進記憶體,是以批量請求的實體大小比文檔計數重要得多。從 5–15 MB 開始測試批量請求大小,緩慢增加這個數字,直到你看不到性能提升為止。

然後開始增加你的批量寫入的并發度(多線程等等辦法)。用iostat 、 top 和 ps 等工具監控你的節點,觀察資源什麼時候達到瓶頸。

如果你開始收到 EsRejectedExecutionException ,你的叢集沒辦法再繼續了:至少有一種資源到瓶頸了。或者減少并發數,或者提供更多的受限資源(比如從機械磁盤換成 SSD),或者添加更多節點。

8、索引和shard

ES的索引,shard都會有對應的中繼資料,且因為ES的中繼資料都是儲存在master節點,且中繼資料的更新是要hang住叢集向所有節點同步的。

當ES的建立字段或者建立索引的時候,都會要擷取叢集中繼資料,并對中繼資料進行變更及同步,此時會影響叢集的響應,是以需要關注叢集的index和shard數量。

建議如下:

  •     使用shrink和rollover api,相對生成合适的資料shard數;
  •     根據資料量級及對應的性能需求,選擇建立index的名稱,形如:按月生成索引:test-YYYYMM,按天生成索引:test-YYYYMMDD;
  •     控制單個shard的size,正常情況下,日志場景,建議單個shard不大于50GB,線上業務場景,建議單個shard不超過20GB。

9、segment merge

段合并的計算量龐大, 而且還要吃掉大量磁盤 I/O。合并在背景定期操作,因為他們可能要很長時間才能完成,尤其是比較大的段。

這個通常來說都沒問題,因為大規模段合并的機率是很小的。如果發現merge占用了大量的資源,可以設定:index.merge.scheduler.max_thread_count:1

特别是機械磁盤在并發 I/O 支援方面比較差,是以我們需要降低每個索引并發通路磁盤的線程數。這個設定允許 max_thread_count + 2 個線程同時進行磁盤操作,也就是設定為 1 允許三個線程。

對于 SSD,你可以忽略這個設定,預設是 Math.min(3, Runtime.getRuntime().availableProcessors() / 2) ,對 SSD 來說運作的很好。

業務低峰期通過force_merge強制合并segment,降低segment的數量,減小記憶體消耗;關閉冷索引,業務需要的時候再進行開啟,如果一直不使用的索引,可以定期删除,或者備份到hadoop叢集。

10、二級自動生成_id

當寫入端使用特定的id将資料寫入ES時,ES會去檢查對應的index下是否存在相同的id,這個操作會随着文檔數量的增加而消耗越來越大,是以如果業務上沒有強需求,建議使用ES自動生成的id,加快寫入速率。

11、routing

對于資料量較大的業務查詢場景,ES側一般會建立多個shard,并将shard配置設定到叢集中的多個執行個體來分攤壓力,正常情況下,一個查詢會周遊查詢所有的shard,然後将查詢到的結果進行merge之後,再傳回給查詢端。

此時,寫入的時候設定routing,可以避免每次查詢都周遊全量shard,而是查詢的時候也指定對應的routingkey,這種情況下,ES會隻去查詢對應的shard,可以大幅度降低合并資料和排程全量shard的開銷。

12、使用alias

生産提供服務的索引,切記使用别名提供服務,而不是直接暴露索引名稱,避免後續因為業務變更或者索引資料需要reindex等情況造成業務中斷。

13、避免寬表

在索引中定義太多字段是一種可能導緻映射爆炸的情況,這可能導緻記憶體不足錯誤和難以恢複的情況,這個問題可能比預期更常見,index.mapping.total_fields.limit ,預設值是1000。

14、避免稀疏索引

因為索引稀疏之後,對應的相鄰文檔id的delta值會很大,lucene基于文檔id做delta編碼壓縮導緻壓縮率降低,進而導緻索引檔案增大。

同時,ES的keyword,數組類型采用doc_values結構,每個文檔都會占用一定的空間,即使字段是空值,是以稀疏索引會造成磁盤size增大,導緻查詢和寫入效率降低。

繼續閱讀