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的內存模型,具體每塊的作用,不在這裏闡述。
新生代和老年代分配的內存比例給多大?
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索引的讀寫會佔用很多的系統資源,因此,分片數不能設置過大;所以,在創建索引時,合理配置分片數是非常重要的。一般來說,我們遵循一些原則:
控制每個分片佔用的硬盤容量不超過ES的最大JVM的堆空間設置(一般設置不超過32G,參加上文的JVM設置原則),因此,如果索引的總容量在500G左右,那分片大小在16個左右即可;當然,最好同時考慮原則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
_id字段的使用,應儘可能避免自定義_id, 以避免針對ID的版本管理;建議使用ES的默認ID生成策略或使用數字類型ID做爲主鍵。
_all字段及_source字段的使用,應該注意場景和需要,_all字段包含了所有的索引字段,方便做全文檢索,如果無此需求,可以禁用;_source存儲了原始的document內容,如果沒有獲取原始文檔數據的需求,可通過設置includes、excludes 屬性來定義放入_source的字段。
合理的配置使用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交流、探討、