Elasticsearch使用優化之拙見

Elasticsearch常常作爲日誌存儲和分析的工具,在企業級應用中常常使用。Elasticsearch提供強大的搜索、分析功能,已經是後端技術棧不可缺少的一部分。

在維護ElastciSearch集羣的時候,對Elasticsearch進行了一些調優和分析,現整理成文,純屬拙見,如果有不合理之處,歡迎指出探討。我所使用的Elasticsearch版本爲5.x。

Elasticsearch有大量的查詢數據和插入數據的請求,需要大量文件句柄,centos系統默認的1024個文件句柄。如果文件句柄用完了,這就意味着操作系統會拒絕連接,意味着數據可能丟失,這是災難性的後果,

不能被接受。登陸Elasticsearch的啓動用戶,用一下命令查看:

ulimit -a

查看結果:

core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 127673
max locked memory (kbytes, -l) unlimited
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 2056474
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited

上面的文件句柄(open files)的個數爲1024,在ElasticSearch大量請求的情況下,這個句柄數量是不夠的,可以改成655360。

臨時修改可以通過執行以下命令,即可立即生效,但是機器重啓後又會失效:

ulimit -n 655360

永久生效,修改/etc/security/limits.conf,需要重啓機器生效:

u_es - nofile 655360

上述配置中u_es爲啓動ElasticSearch的用戶,設置了該用戶的ElasticSearch的文件句柄爲655360、

JVM參數優化

Elasticsearch是運行在JVM上的,對其做JVM參數調優至關重要。最常見的調優是Java內存的分配。下面是JVM的內存模型,具體每塊的作用,不在這裏闡述。

Elasticsearch使用優化之拙見


新生代和老年代分配的內存比例給多大?

Jvm內存分爲新生代和老年代。

  • 新生代(或者伊甸園)

  • 新實例化的對象分配的空間。新生代空間通常都非常小,一般在 100 MB–500 MB。新生代也包含兩個 倖存 空間。

  • 老年代

  • 較老的對象存儲的空間。這些對象預計將長期留存並持續上很長一段時間。老生代通常比新生代大很多。

新生代、老生代的垃圾回收都有一個階段會“stop the world”。在這段時間裏,JVM 停止了程序運行,以便對對象進行可達性分析,收集死亡對象。在這個時間停止階段,一切都不會發生。請求不被服務,ping 不被迴應,分片不被分配。整個世界都真的停止了。

對於新生代,這不是什麼大問題;那麼小的空間意味着 GC 會很快執行完。但是老生代大很多,而這裏面一個慢 GC 可能就意味着 1 秒乃至 15 秒的暫停——對於服務器軟件來說這是不可接受的。

那一般我們給新生代和老年代分配多大的內存呢?他們的比例是多少呢?

一般來說,老年代和新生代的內存比例爲2:1是比較合適的。比如給堆內存分配3G,則新生代分配1G,其餘都給老年代。在ElasticSearce的配置文件jvm.options文件配置:

-Xms3g //配置堆初始化大小
-Xmx3g //配置堆的最大內存
-Xmn1g //配置新生代內存。

該分配多大的內存給Elasticesearch?

在使用Elasticesearch的時候,我們對裝Elasticesearch的機器進行了升級,從最小的8G內存升級到了16G內存,然後到目前的32G內存。一臺機器裝一個Elasticesearch節點,我們應該怎麼分配機器的內存呢?

官方給出瞭解決方案,把一半(少於)的內存分配給Luence,另外的內存分配給ElasticSearch.

內存對於 Elasticsearch 來說絕對是重要的,它可以被許多內存數據結構使用來提供更快的操作。但是說到這裏, 還有另外一個內存消耗大戶 非堆內存 (off-heap):Lucene。

Lucene 被設計爲可以利用操作系統底層機制來緩存內存數據結構。Lucene 的段是分別存儲到單個文件中的。因爲段是不可變的,這些文件也都不會變化,這是對緩存友好的,同時操作系統也會把這些段文件緩存起來,以便更快的訪問。

Lucene 的性能取決於和操作系統的相互作用。如果你把所有的內存都分配給 Elasticsearch 的堆內存,那將不會有剩餘的內存交給 Lucene。這將嚴重地影響全文檢索的性能。

標準的建議是把 50% 的可用內存作爲 Elasticsearch 的堆內存,保留剩下的 50%。當然它也不會被浪費,Lucene 會很樂意利用起餘下的內存。

我們實際的解決辦法是將機器的一半分給Elasticesearch的堆,棧內存、方法區、常量池、非堆內存佔用另外一半。

分配給堆最大內存應該小於 32766 mb(~31.99 gb)

JVM 在內存小於 32 GB 的時候會採用一個內存對象指針壓縮技術。

對於 32 位的系統,意味着堆內存大小最大爲 4 GB。對於 64 位的系統, 可以使用更大的內存,但是 64 位的指針意味着更大的浪費,因爲你的指針本身大了。更糟糕的是, 更大的指針在主內存和各級緩存(例如 LLC,L1 等)之間移動數據的時候,會佔用更多的帶寬。

Java 使用一個叫作 內存指針壓縮(compressed oops)的技術來解決這個問題。它的指針不再表示對象在內存中的精確位置,而是表示 偏移量 。這意味着 32 位的指針可以引用 40 億個 對象 , 而不是 40 億個字節。最終, 也就是說堆內存增長到 32 GB 的物理內存,也可以用 32 位的指針表示。

一旦你越過那個神奇的 ~32 GB 的邊界,指針就會切回普通對象的指針。每個對象的指針都變長了,就會使用更多的 CPU 內存帶寬,也就是說你實際上失去了更多的內存。事實上,當內存到達 40–50 GB 的時候,有效內存才相當於使用內存對象指針壓縮技術時候的 32 GB 內存。

這段描述的意思就是說:即便你有足夠的內存,也儘量不要 超過 32 GB。因爲它浪費了內存,降低了 CPU 的性能,還要讓 GC 應對大內存。

關掉swap

內存交換 到磁盤對服務器性能來說是 致命 的。

如果內存交換到磁盤上,一個 100 微秒的操作可能變成 10 毫秒。再想想那麼多 10 微秒的操作時延累加起來。不難看出 swapping 對於性能是多麼可怕。

用以下命令關掉swap:

sudo swapoff -a


不要碰以下的配置

所有的調整就是爲了優化,但是這些調整,你真的不需要理會它。因爲它們經常會被亂用,從而造成系統的不穩定或者糟糕的性能,甚至兩者都有可能。

線程池配置

許多人 喜歡 調整線程池。無論什麼原因,人們都對增加線程數無法抵抗。索引太多了?增加線程!搜索太多了?增加線程!節點空閒率低於 95%?增加線程!

Elasticsearch 默認的線程設置已經是很合理的了。對於所有的線程池(除了 搜索 ),線程個數是根據 CPU 核心數設置的。如果你有 8 個核,你可以同時運行的只有 8 個線程,只分配 8 個線程給任何特定的線程池是有道理的。

搜索線程池設置的大一點,配置爲 int(( 核心數 * 3 )/ 2 )+ 1 。

垃圾回收器

Elasticsearch 默認的垃圾回收器( GC )是 CMS。這個垃圾回收器可以和應用並行處理,以便它可以最小化停頓。然而,它有兩個 stop-the-world 階段,處理大內存也有點吃力。

儘管有這些缺點,它還是目前對於像 Elasticsearch 這樣低延遲需求軟件的最佳垃圾回收器。官方建議使用 CMS。

合理設置最小主節點

minimum_master_nodes 設置及其重要,爲了防止集羣腦裂,這個參數應該設置爲法定個數就是 ( master 候選節點個數 / 2) + 1。

分片均勻,磁盤優化,剔除掉高負載的Master競選?

筆者在實際生產環境中遇到了有一個節點的負載是其他節點的幾倍,從虛擬機監控上看,所有的節點的qps是差不多的。機器的配置是一樣的,爲什麼負載會有如此大的差距?

  • 首先,我們懷疑數據分配不均勻,我們排查了下,沒有這種現象。

  • 然後,我們監控到了高負載的節點磁盤IO非常的高,經常達到100%,我們懷疑是那個虛擬機磁盤性能不行。但是我們當時沒有更好的磁盤。

  • 我們找到了一個適中的解決辦法是將這臺高負載的節點剔除Master競選,即將elasticsearch.yml文件中的node.master改爲false然後重啓,負載下降了一些。

數據存儲天數的優化

存儲天數的優化,這個需要根據實際的業務來,下面是刪除過期數據的腳本,該腳本來源於https://stackoverflow.com/questions/33430055/removing-old-indices-in-elasticsearch#answer-39746705 ;

#!/bin/bash
searchIndex=logstash-monitor
elastic_url=logging.core.k94.kvk.nl
elastic_port=9200
date2stamp () {
 date --utc --date "$1" +%s
}
dateDiff (){
 case $1 in
 -s) sec=1; shift;;
 -m) sec=60; shift;;
 -h) sec=3600; shift;;
 -d) sec=86400; shift;;
 *) sec=86400;;
 esac
 dte1=$(date2stamp $1)
 dte2=$(date2stamp $2)
 diffSec=$((dte2-dte1))
 if ((diffSec < 0)); then abs=-1; else abs=1; fi
 echo $((diffSec/sec*abs))
}
for index in $(curl -s "${elastic_url}:${elastic_port}/_cat/indices?v" | grep -E " ${searchIndex}-20[0-9][0-9]\.[0-1][0-9]\.[0-3][0-9]" | awk '{ print $3 }'); do
 date=$(echo ${index: -10} | sed 's/\./-/g')
 cond=$(date +%Y-%m-%d)
 diff=$(dateDiff -d $date $cond)
 echo -n "${index} (${diff})"
 if [ $diff -gt 1 ]; then
 echo " / DELETE"
 # curl -XDELETE "${elastic_url}:${elastic_port}/${index}?pretty"
 else
 echo ""
 fi
done

然後使用crontab每天定時執行一次這個腳本。

集羣分片設置

ES一旦創建好索引後,就無法調整分片的設置,而在ES中,一個分片實際上對應一個lucene 索引,而lucene索引的讀寫會佔用很多的系統資源,因此,分片數不能設置過大;所以,在創建索引時,合理配置分片數是非常重要的。一般來說,我們遵循一些原則:

  1. 控制每個分片佔用的硬盤容量不超過ES的最大JVM的堆空間設置(一般設置不超過32G,參加上文的JVM設置原則),因此,如果索引的總容量在500G左右,那分片大小在16個左右即可;當然,最好同時考慮原則2。

  2. 考慮一下node數量,一般一個節點有時候就是一臺物理機,如果分片數過多,大大超過了節點數,很可能會導致一個節點上存在多個分片,一旦該節點故障,即使保持了1個以上的副本,同樣有可能會導致數據丟失,集羣無法恢復。所以, 一般都設置分片數不超過節點數的3倍。


索引優化

1.修改index_buffer_size 的設置,可以設置成百分數,也可設置成具體的大小,大小可根據集羣的規模做不同的設置測試。

indices.memory.index_buffer_size:10%(默認)
indices.memory.min_index_buffer_size:48mb(默認)
indices.memory.max_index_buffer_size
  1. _id字段的使用,應儘可能避免自定義_id, 以避免針對ID的版本管理;建議使用ES的默認ID生成策略或使用數字類型ID做爲主鍵。

  2. _all字段及_source字段的使用,應該注意場景和需要,_all字段包含了所有的索引字段,方便做全文檢索,如果無此需求,可以禁用;_source存儲了原始的document內容,如果沒有獲取原始文檔數據的需求,可通過設置includes、excludes 屬性來定義放入_source的字段。

  3. 合理的配置使用index屬性,analyzed 和not_analyzed,根據業務需求來控制字段是否分詞或不分詞。只有 groupby需求的字段,配置時就設置成not_analyzed, 以提高查詢或聚類的效率。

查詢優化

  • 查詢優化,調整filter過濾順序


如果把過濾效果不明顯的條件放在了前面,導致查詢出大量不需要的數據,導致查詢變慢。

把過濾效果明顯的條件提前,按照過濾效果把過濾條件排序

  • 索引時間精度優化


研究Filter的工作原理可以看出,它每次工作都是遍歷整個索引的,所以時間粒度越大,對比越快,搜索時間越短,在不影響功能的情況下,時間精度越低越好,有時甚至犧牲一點精度也值得,當然最好的情況是根本不作時間限制。

es重新刷索引,增加冗餘的時間字段,精確到天。帶有時間範圍的查詢使用該字段進行查詢

  • 查詢Fetch Source優化


業務查詢語句獲取的數據集比較大,並且從source中獲取了非必須的字段,導致查詢較慢。

舉例:只需要從es中查詢id這一個字段,卻把所有字段查詢了出來

  • 預索引數據


利用索引查詢數據是最優的方式。例如,如果所有的文檔都有 price 字段,並且大多數查詢都在一個固定的範圍列表中運行範圍聚合,那麼可以通過將 index 預索引到 index 和使用 terms 聚合來更快地實現聚合。

例如,像下面這樣:

PUT index/type/1
{
 "designation": "spoon",
 "price": 13
}

像這樣的查詢:

GET index/_search
{
 "aggs": {
 "price_ranges": {
 "range": {
 "field": "price",
 "ranges": [
 { "to": 10 },
 { "from": 10, "to": 100 },
 { "from": 100 }
 ]
 }
 }
 }
}

文檔在索引的時候要使用 price_range ,應該被映射爲關鍵詞:

PUT index
{
 "mappings": {
 "type": {
 "properties": {
 "price_range": {
 "type": "keyword"
 }
 }
 }
 }
}
PUT index/type/1
{
 "designation": "spoon",
 "price": 13,
 "price_range": "10-100"
}

然後這個請求就直接聚合新字段,而不是在 price 字段運行範圍查詢:

GET index/_search
{
 "aggs": {
 "price_ranges": {
 "terms": {
 "field": "price_range"
 }
 }
 }
}

總結

總的來說,ElasticSearch的優化,優化可以從以下方面的考慮:

  • 硬件的優化:機器分配,機器配置,機器內存,機器CPU,機器網絡,機器磁盤性能

  • 操作系統設置優化:文件句柄優化、swap關閉

  • ElasticSearch合理分配節點,合理分配參加競選Master的節點

  • ElasticSearch的存儲的優化,副本數量、索引數量、分片數量

  • ElasticSearch的使用優化,索引的優化,查詢的優化

 最後,分享一份面試寶典《Java核心知識點整理.pdf》,覆蓋了JVM、鎖、高併發、反射、Spring原理、微服務、Zookeeper、數據庫、數據結構等等

有需要的朋友可以加入Java架構技術交流Q羣328993819交流、探討、


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章