值得擁有的 ES 讀場景、寫場景性能優(yōu)化指南
本文轉(zhuǎn)載自微信公眾號「小姐姐味道」,作者小姐姐養(yǎng)的狗 。轉(zhuǎn)載本文請聯(lián)系小姐姐味道公眾號。
ES作為NoSQL數(shù)據(jù)庫里非常重要的一員,使用越來越廣泛。雖然它因為索引延遲的原因,數(shù)據(jù)在時效性上有一些缺陷,但其大容量、分布式的優(yōu)秀設(shè)計,使得它在時效性要求并不是特別高的類實時搜索領(lǐng)域,能夠大展身手。
根據(jù)使用場景和用途,ES可以分為寫入和讀取兩種典型的應(yīng)用方式。比如ELKB,我們就需要額外關(guān)注它的寫優(yōu)化;再比如從MySQL中同步數(shù)據(jù)到ES的寬表,我們就需要額外關(guān)注它的讀優(yōu)化。
廢話不多說,我們直接show一下優(yōu)化方法。如果你對ES的一些概念還不是很清楚,建議收藏本文慢慢看。
1.寫入優(yōu)化
日志屬于寫多讀少的業(yè)務(wù)場景,對寫入速度要求很高。拿我們其中一個集群來說,單集群日志量達(dá)到百TB,每秒鐘日志寫入量達(dá)到10W條。
數(shù)據(jù)寫入,主要有三個動作:flush、refresh和merge。通過調(diào)整它們的行為,即可在性能和數(shù)據(jù)可靠性之間進(jìn)行權(quán)衡。
1.1 translog異步化
首先,ES需要寫一份translog,它類似于MySQL中的redolog,為的是避免在斷電的時候數(shù)據(jù)丟失。ES默認(rèn)每次請求都進(jìn)行一次flush,但對于日志來說,這沒有必要,可以將這個過程改為異步的,刷盤間隔為60秒。參數(shù)如下:
- curl-H"Content-Type: application/json"-XPUT'http://localhost:9200/_all/_settings?preserve_existing=true'-d'{
- "index.translog.durability" : "async",
- "index.translog.flush_threshold_size" : "512mb",
- "index.translog.sync_interval" : "60s"
- }'
這可以說是最重要的一步優(yōu)化了,對性能的影響最大,但在極端情況下會有丟失部分?jǐn)?shù)據(jù)的可能。對于日志系統(tǒng)來說,是可以忍受的。
1.2 增加refresh間隔
除了寫translog,ES還會將數(shù)據(jù)寫入到一個緩沖區(qū)中。但是注意了!此時,緩沖區(qū)的內(nèi)容是無法被搜索到的,它還需要寫入到segment里面才可以,也就是刷新到lucence索引里面。這就是refresh動作,默認(rèn)1秒。也就是你寫入的數(shù)據(jù),大概率1秒之后才會被搜索到。
這也是為什么ES不是實時搜索系統(tǒng)的原因,它從數(shù)據(jù)寫入到數(shù)據(jù)讀出,一般是有一個合并過程的,有一定的時間差。
通過index.refresh_interval可以修改這個刷新間隔。
對于日志系統(tǒng)來說,當(dāng)然要把它調(diào)大一點啦。xjjdog這里調(diào)整到了120s,減少了這些落到segment的頻率,I/O的壓力自然會小,寫入速度自然會快。
- curl-H"Content-Type: application/json"-XPUT'http://localhost:9200/_all/_settings?preserve_existing=true'-d'{
- "index.refresh_interval" : "120s"
- }'
1.3 merge
merge其實是lucene的機制,它主要是合并小的segment塊,生成更大的segment,來提高檢索的速度。
原因就是refresh過程會生成一大堆小segment文件,數(shù)據(jù)刪除也會產(chǎn)生空間碎片。所以merge,通俗來講就像是碎片整理進(jìn)程。像postgresql等,也有vaccum進(jìn)程在干同樣的事。
顯而易見,這種整理操作,既浪費I/O,又浪費CPU。
如果你的系統(tǒng)merge非常頻繁,那么調(diào)整merge的塊大小和頻率,是一個比較好的方法。
2.讀取優(yōu)化
2.1 指定路由
如果你向ES里寫數(shù)據(jù),那么它會為你設(shè)置一個離散的隱藏ID,落到哪個分片,是不一定的。如果你根據(jù)一個查詢條件查詢數(shù)據(jù),你設(shè)置了6個shards的話,它要查詢6次才行。如果能夠在路由的時候就知道數(shù)據(jù)在哪個分片上,查詢速度自然會上升,這就要求我們在構(gòu)造數(shù)據(jù)的時候,人工指定路由規(guī)則。它的實際運行規(guī)則如下:
- shard = hash(routing) % number_of_primary_shards
比如,一個查詢會變成這樣。
- GET my-index-000001/_search
- {
- "query": {
- "terms": {
- "_routing": [ "user1" ]
- }
- }
- }
當(dāng)然,如果你的查詢維度較多,又對數(shù)據(jù)的查詢速度有非常高的有求,根據(jù)routing存放多份數(shù)據(jù)是一個比較好的選擇。
2.2 rollover冷熱分離
rollover根據(jù)索引大小,文檔數(shù)或使用期限自動過渡到新索引。當(dāng)rollover觸發(fā)后,將創(chuàng)建新索引,寫別名將更新為指向新索引,所有后續(xù)更新都將寫入新索引,比如indexname-000001.這種模式。
從rollover這個名字可以看出來,它和Java的log日志有一定的相似之處,比如Log4j的RollingFileAppender。
當(dāng)索引變的非常大,通常是幾十GB,那它的查詢效率將變的非常的低,索引重建的成本也較大。實際上,很多索引的數(shù)據(jù)在時間維度上有較為明顯的規(guī)律,一些冷數(shù)據(jù)將很少被用到。這個時候,建立滾動索引將是一個比較好的辦法。
滾動索引一般可以與索引模板結(jié)合使用,實現(xiàn)按一定條件自動創(chuàng)建索引,ES的官方文檔有具體的_rollover建立方法。
2.3 使用BoolQuery替代TermQuery
Bool查詢現(xiàn)在包括四種子句,must、filter、should和must_not。Bool查詢是true、false對比,而TermQuery是精確的字符串比對,所以如果需求相似,BoolQuery自然會快于TermQuery。
2.4 將大查詢拆成分段查詢
有些業(yè)務(wù)的查詢比較復(fù)雜,我們不得不拼接一張非常大的寬表放在ES中,這有兩個比較明顯的問題。
- 寬表的數(shù)據(jù)往往需要從其他數(shù)據(jù)源中回查拼接而成,數(shù)據(jù)更新時對源庫或者ES本身都有較大的壓力
- 業(yè)務(wù)的查詢JSON需要書寫的非常復(fù)雜,查詢效率未知,一次查詢鎖定的內(nèi)存過高,無法進(jìn)行深入優(yōu)化
其實,寬表不論在RDBMS中還是ES中,都會與復(fù)雜的查詢語句有關(guān),其鎖定時間都較長,業(yè)務(wù)也不夠靈活。
應(yīng)對這種場景的策略,通常將復(fù)雜的數(shù)據(jù)查詢,轉(zhuǎn)移到業(yè)務(wù)代碼的拼接上來。比如,將一段非常冗長的單條查詢,拆分成循環(huán)遍歷的100條小查詢。所有的數(shù)據(jù)庫都對較小的查詢請求有較好的響應(yīng),其整體性能整體上將優(yōu)于復(fù)雜的單條查詢。
這對我們的ES索引建模能力和編碼能力提出了挑戰(zhàn)。畢竟,在ES層面,互不相關(guān)的幾個索引,將作為整體為其他服務(wù)提供所謂的數(shù)據(jù)中臺接口。
2.5 增加第一次索引的速度
很多業(yè)務(wù)的索引數(shù)據(jù)往往來自于MySQL等傳統(tǒng)數(shù)據(jù)庫,第一次索引往往是全量索引,后面才是增量索引。必要的時候,也會進(jìn)行索引的重建,大量的數(shù)據(jù)灌入造成了ES的索引速度建立緩慢。
為了緩解這種情況,建議在創(chuàng)建索引的時候,把副本數(shù)量設(shè)置成1,即沒有從副本。等所有數(shù)據(jù)索引完畢,再將副本數(shù)量增加到正常水平。
這樣,數(shù)據(jù)能夠快速索引,副本會在后臺慢慢復(fù)制。
3.通用優(yōu)化
當(dāng)然,我們還可以針對ES做一些通用的優(yōu)化。比如,使用監(jiān)控接口或者trace工具,發(fā)現(xiàn)線程池有明顯的瓶頸,則需要調(diào)整線程池的大小。
具體的優(yōu)化項如下。
3.1 線程池優(yōu)化
新版本對線程池的配置進(jìn)行了優(yōu)化,不需要配置復(fù)雜的search、bulk、index線程池。有需要配置下面幾個就行了:thread_pool.get.size, thread_pool.write.size, thread_pool.listener.size, thread_pool.analyze.size。具體可觀測_cat/thread_pool接口暴露的數(shù)據(jù)進(jìn)行調(diào)整。
3.2 物理冷熱分離
上面的rollover接口,我們可以實現(xiàn)索引滾動。但是如何將冷數(shù)據(jù)存放在比較慢但是便宜的節(jié)點上?如何將某些索引移動過去?
ES支持給節(jié)點打標(biāo)簽,具體方式是在elasticsearch.yml文件中增加一些屬性。比如:
- //熱節(jié)點
- node.attr.temperature: hot
- //冷節(jié)點
- node.attr.temperature: cold
節(jié)點有了冷熱屬性后,接下來就是指定數(shù)據(jù)的冷熱屬性,來設(shè)置和調(diào)整數(shù)據(jù)分布。ES提供了index shard filtering功能來實現(xiàn)索引的遷移。
首先,可以對索引也設(shè)置冷熱屬性。
- PUT hot_data_index/_settings
- {
- "index.routing.allocation.require.temperature": "hot"
- }
這些索引將自動轉(zhuǎn)移到冷設(shè)備上。我們可以寫一些定時任務(wù),通過_cat接口的數(shù)據(jù),自動的完成這個轉(zhuǎn)移過程。
3.2 多磁盤分散I/O
其實,可以通過配置多塊磁盤的方式,來分散I/O的壓力,但容易會造成數(shù)據(jù)熱點集中在單塊磁盤上。
ES支持在一臺機器上配置多塊磁盤,所以在存儲規(guī)模上有更大的伸縮性。在配置文件中,配置path.data屬性,即可掛載多塊磁盤。
- path.data : /data1, /data2, /data3
值得注意的是,如果你是在擴容,那么就需要配合reroute接口進(jìn)行索引的重新分配。
3.3 減少單條記錄的大小
Lucene的索引建立過程,非常耗費CPU,可以減少倒排索引的數(shù)量來減少CPU的損耗。第一個優(yōu)化就是減少字段的數(shù)量;第二個優(yōu)化就是減少索引字段的數(shù)量。具體的操作,是將不需要搜索的字段,index屬性設(shè)置為not_analyzed或者no。至于_source和_all,在實際調(diào)試中效果不大,不再贅述。
ES的使用越來越廣泛,從ELKB到APM,從NoSQL到搜索引擎,ES在企業(yè)中的地位也越來越重要。本文通過分析ES寫入和讀取場景的優(yōu)化,力求從原理到實踐層面,助你為ES加速。希望你在使用ES時能夠更加得心應(yīng)手。
通常,一個ES集群對配置的要求是較高的,尤其是APM等場景,甚至?xí)嫉絇aaS平臺的1/3資源甚至更多。ES提供了較多的配置選項,我們可以根據(jù)應(yīng)用場景,調(diào)整ES的表現(xiàn),使其更好的為我們服務(wù)。
作者簡介:小姐姐味道 (xjjdog),一個不允許程序員走彎路的公眾號。聚焦基礎(chǔ)架構(gòu)和Linux。十年架構(gòu),日百億流量,與你探討高并發(fā)世界,給你不一樣的味道。