Elasticsearch 性能優(yōu)化詳解
硬件配置優(yōu)化
升級(jí)硬件設(shè)備配置一直都是提高服務(wù)能力最快速有效的手段,在系統(tǒng)層面能夠影響應(yīng)用性能的一般包括三個(gè)因素:CPU、內(nèi)存和 IO,可以從這三方面進(jìn)行 ES 的性能優(yōu)化工作。
CPU 配置
一般說(shuō)來(lái),CPU 繁忙的原因有以下幾個(gè):
- 線(xiàn)程中有無(wú)限空循環(huán)、無(wú)阻塞、正則匹配或者單純的計(jì)算;
- 發(fā)生了頻繁的 GC;
- 多線(xiàn)程的上下文切換;
大多數(shù) Elasticsearch 部署往往對(duì) CPU 要求不高。因此,相對(duì)其它資源,具體配置多少個(gè)(CPU)不是那么關(guān)鍵。你應(yīng)該選擇具有多個(gè)內(nèi)核的現(xiàn)代處理器,常見(jiàn)的集群使用 2 到 8 個(gè)核的機(jī)器。如果你要在更快的 CPUs 和更多的核數(shù)之間選擇,選擇更多的核數(shù)更好。多個(gè)內(nèi)核提供的額外并發(fā)遠(yuǎn)勝過(guò)稍微快一點(diǎn)點(diǎn)的時(shí)鐘頻率。
內(nèi)存配置
如果有一種資源是最先被耗盡的,它可能是內(nèi)存。排序和聚合都很耗內(nèi)存,所以有足夠的堆空間來(lái)應(yīng)付它們是很重要的。即使堆空間是比較小的時(shí)候,也能為操作系統(tǒng)文件緩存提供額外的內(nèi)存。因?yàn)?Lucene 使用的許多數(shù)據(jù)結(jié)構(gòu)是基于磁盤(pán)的格式,Elasticsearch 利用操作系統(tǒng)緩存能產(chǎn)生很大效果。
64 GB 內(nèi)存的機(jī)器是非常理想的,但是 32 GB 和 16 GB 機(jī)器也是很常見(jiàn)的。少于8 GB 會(huì)適得其反(你最終需要很多很多的小機(jī)器),大于 64 GB 的機(jī)器也會(huì)有問(wèn)題。
由于 ES 構(gòu)建基于 lucene,而 lucene 設(shè)計(jì)強(qiáng)大之處在于 lucene 能夠很好的利用操作系統(tǒng)內(nèi)存來(lái)緩存索引數(shù)據(jù),以提供快速的查詢(xún)性能。lucene 的索引文件 segements 是存儲(chǔ)在單文件中的,并且不可變,對(duì)于 OS 來(lái)說(shuō),能夠很友好地將索引文件保持在 cache 中,以便快速訪(fǎng)問(wèn);因此,我們很有必要將一半的物理內(nèi)存留給 lucene;另一半的物理內(nèi)存留給 ES(JVM heap)。
內(nèi)存分配
當(dāng)機(jī)器內(nèi)存小于 64G 時(shí),遵循通用的原則,50% 給 ES,50% 留給 lucene。
當(dāng)機(jī)器內(nèi)存大于 64G 時(shí),遵循以下原則:
- 如果主要的使用場(chǎng)景是全文檢索,那么建議給 ES Heap 分配 4~32G 的內(nèi)存即可;其它內(nèi)存留給操作系統(tǒng),供 lucene 使用(segments cache),以提供更快的查詢(xún)性能。
- 如果主要的使用場(chǎng)景是聚合或排序,并且大多數(shù)是 numerics,dates,geo_points 以及 not_analyzed 的字符類(lèi)型,建議分配給 ES Heap 分配 4~32G 的內(nèi)存即可,其它內(nèi)存留給操作系統(tǒng),供 lucene 使用,提供快速的基于文檔的聚類(lèi)、排序性能。
- 如果使用場(chǎng)景是聚合或排序,并且都是基于 analyzed 字符數(shù)據(jù),這時(shí)需要更多的 heap size,建議機(jī)器上運(yùn)行多 ES 實(shí)例,每個(gè)實(shí)例保持不超過(guò) 50% 的 ES heap 設(shè)置(但不超過(guò) 32 G,堆內(nèi)存設(shè)置 32 G 以下時(shí),JVM 使用對(duì)象指標(biāo)壓縮技巧節(jié)省空間),50% 以上留給 lucene。
禁止 swap
禁止 swap,一旦允許內(nèi)存與磁盤(pán)的交換,會(huì)引起致命的性能問(wèn)題。可以通過(guò)在 elasticsearch.yml 中 bootstrap.memory_lock: true,以保持 JVM 鎖定內(nèi)存,保證 ES 的性能。
GC 設(shè)置
老的版本中官方文檔 中推薦默認(rèn)設(shè)置為:Concurrent-Mark and Sweep(CMS),給的理由是當(dāng)時(shí)G1 還有很多 BUG。
原因是:已知JDK 8附帶的HotSpot JVM的早期版本存在一些問(wèn)題,當(dāng)啟用G1GC收集器時(shí),這些問(wèn)題可能導(dǎo)致索引損壞。受影響的版本早于JDK 8u40隨附的HotSpot版本。來(lái)源于官方說(shuō)明
實(shí)際上如果你使用的JDK8較高版本,或者JDK9+,我推薦你使用G1 GC;因?yàn)槲覀兡壳暗捻?xiàng)目使用的就是G1 GC,運(yùn)行效果良好,對(duì)Heap大對(duì)象優(yōu)化尤為明顯。修改jvm.options文件,將下面幾行:
-XX:+UseConcMarkSweepGC
-XX:CMSInitiatingOccupancyFraction=75
-XX:+UseCMSInitiatingOccupancyOnly
更改為
-XX:+UseG1GC
-XX:MaxGCPauseMillis=50
其中 -XX:MaxGCPauseMillis是控制預(yù)期的最高GC時(shí)長(zhǎng),默認(rèn)值為200ms,如果線(xiàn)上業(yè)務(wù)特性對(duì)于GC停頓非常敏感,可以適當(dāng)設(shè)置低一些。但是 這個(gè)值如果設(shè)置過(guò)小,可能會(huì)帶來(lái)比較高的cpu消耗。
G1對(duì)于集群正常運(yùn)作的情況下減輕G1停頓對(duì)服務(wù)時(shí)延的影響還是很有效的,但是如果是你描述的GC導(dǎo)致集群卡死,那么很有可能換G1也無(wú)法根本上解決問(wèn)題。通常都是集群的數(shù)據(jù)模型或者Query需要優(yōu)化。
磁盤(pán)
硬盤(pán)對(duì)所有的集群都很重要,對(duì)大量寫(xiě)入的集群更是加倍重要(例如那些存儲(chǔ)日志數(shù)據(jù)的)。硬盤(pán)是服務(wù)器上最慢的子系統(tǒng),這意味著那些寫(xiě)入量很大的集群很容易讓硬盤(pán)飽和,使得它成為集群的瓶頸。
在經(jīng)濟(jì)壓力能承受的范圍下,盡量使用固態(tài)硬盤(pán)(SSD)。固態(tài)硬盤(pán)相比于任何旋轉(zhuǎn)介質(zhì)(機(jī)械硬盤(pán),磁帶等),無(wú)論隨機(jī)寫(xiě)還是順序?qū)懀紩?huì)對(duì) IO 有較大的提升。
- 如果你正在使用 SSDs,確保你的系統(tǒng) I/O 調(diào)度程序是配置正確的。當(dāng)你向硬盤(pán)寫(xiě)數(shù)據(jù),I/O 調(diào)度程序決定何時(shí)把數(shù)據(jù)實(shí)際發(fā)送到硬盤(pán)。大多數(shù)默認(rèn) *nix 發(fā)行版下的調(diào)度程序都叫做 cfq(完全公平隊(duì)列)。
- 調(diào)度程序分配時(shí)間片到每個(gè)進(jìn)程。并且優(yōu)化這些到硬盤(pán)的眾多隊(duì)列的傳遞。但它是為旋轉(zhuǎn)介質(zhì)優(yōu)化的:機(jī)械硬盤(pán)的固有特性意味著它寫(xiě)入數(shù)據(jù)到基于物理布局的硬盤(pán)會(huì)更高效。
- 這對(duì) SSD 來(lái)說(shuō)是低效的,盡管這里沒(méi)有涉及到機(jī)械硬盤(pán)。但是,deadline 或者 noop 應(yīng)該被使用。deadline 調(diào)度程序基于寫(xiě)入等待時(shí)間進(jìn)行優(yōu)化,noop 只是一個(gè)簡(jiǎn)單的 FIFO 隊(duì)列。
這個(gè)簡(jiǎn)單的更改可以帶來(lái)顯著的影響。僅僅是使用正確的調(diào)度程序,我們看到了 500 倍的寫(xiě)入能力提升。
如果你使用旋轉(zhuǎn)介質(zhì)(如機(jī)械硬盤(pán)),嘗試獲取盡可能快的硬盤(pán)(高性能服務(wù)器硬盤(pán),15k RPM 驅(qū)動(dòng)器)。
使用 RAID0 是提高硬盤(pán)速度的有效途徑,對(duì)機(jī)械硬盤(pán)和 SSD 來(lái)說(shuō)都是如此。沒(méi)有必要使用鏡像或其它 RAID 變體,因?yàn)?Elasticsearch 在自身層面通過(guò)副本,已經(jīng)提供了備份的功能,所以不需要利用磁盤(pán)的備份功能,同時(shí)如果使用磁盤(pán)備份功能的話(huà),對(duì)寫(xiě)入速度有較大的影響。
最后,避免使用網(wǎng)絡(luò)附加存儲(chǔ)(NAS)。人們常聲稱(chēng)他們的 NAS 解決方案比本地驅(qū)動(dòng)器更快更可靠。除卻這些聲稱(chēng),我們從沒(méi)看到 NAS 能配得上它的大肆宣傳。NAS 常常很慢,顯露出更大的延時(shí)和更寬的平均延時(shí)方差,而且它是單點(diǎn)故障的。
索引優(yōu)化設(shè)置
索引優(yōu)化主要是在 Elasticsearch 的插入層面優(yōu)化,Elasticsearch 本身索引速度其實(shí)還是蠻快的,具體數(shù)據(jù),我們可以參考官方的 benchmark 數(shù)據(jù)。我們可以根據(jù)不同的需求,針對(duì)索引優(yōu)化。
批量提交
當(dāng)有大量數(shù)據(jù)提交的時(shí)候,建議采用批量提交(Bulk 操作);此外使用 bulk 請(qǐng)求時(shí),每個(gè)請(qǐng)求不超過(guò)幾十M,因?yàn)樘髸?huì)導(dǎo)致內(nèi)存使用過(guò)大。
比如在做 ELK 過(guò)程中,Logstash indexer 提交數(shù)據(jù)到 Elasticsearch 中,batch size 就可以作為一個(gè)優(yōu)化功能點(diǎn)。但是優(yōu)化 size 大小需要根據(jù)文檔大小和服務(wù)器性能而定。
像 Logstash 中提交文檔大小超過(guò) 20MB,Logstash 會(huì)將一個(gè)批量請(qǐng)求切分為多個(gè)批量請(qǐng)求。
如果在提交過(guò)程中,遇到 EsRejectedExecutionException 異常的話(huà),則說(shuō)明集群的索引性能已經(jīng)達(dá)到極限了。這種情況,要么提高服務(wù)器集群的資源,要么根據(jù)業(yè)務(wù)規(guī)則,減少數(shù)據(jù)收集速度,比如只收集 Warn、Error 級(jí)別以上的日志。
增加 Refresh 時(shí)間間隔
為了提高索引性能,Elasticsearch 在寫(xiě)入數(shù)據(jù)的時(shí)候,采用延遲寫(xiě)入的策略,即數(shù)據(jù)先寫(xiě)到內(nèi)存中,當(dāng)超過(guò)默認(rèn)1秒(index.refresh_interval)會(huì)進(jìn)行一次寫(xiě)入操作,就是將內(nèi)存中 segment 數(shù)據(jù)刷新到磁盤(pán)中,此時(shí)我們才能將數(shù)據(jù)搜索出來(lái),所以這就是為什么 Elasticsearch 提供的是近實(shí)時(shí)搜索功能,而不是實(shí)時(shí)搜索功能。
如果我們的系統(tǒng)對(duì)數(shù)據(jù)延遲要求不高的話(huà),我們可以通過(guò)延長(zhǎng) refresh 時(shí)間間隔,可以有效地減少 segment 合并壓力,提高索引速度。比如在做全鏈路跟蹤的過(guò)程中,我們就將 index.refresh_interval 設(shè)置為30s,減少 refresh 次數(shù)。再如,在進(jìn)行全量索引時(shí),可以將 refresh 次數(shù)臨時(shí)關(guān)閉,即 index.refresh_interval 設(shè)置為-1,數(shù)據(jù)導(dǎo)入成功后再打開(kāi)到正常模式,比如30s。
在加載大量數(shù)據(jù)時(shí)候可以暫時(shí)不用 refresh 和 repliccas,index.refresh_interval 設(shè)置為-1,index.number_of_replicas 設(shè)置為0。
相關(guān)原理,請(qǐng)參考[原理:ES原理之索引文檔流程詳解]
修改 index_buffer_size 的設(shè)置
索引緩沖的設(shè)置可以控制多少內(nèi)存分配給索引進(jìn)程。這是一個(gè)全局配置,會(huì)應(yīng)用于一個(gè)節(jié)點(diǎn)上所有不同的分片上。
indices.memory.index_buffer_size: 10%
indices.memory.min_index_buffer_size: 48mb
indices.memory.index_buffer_size 接受一個(gè)百分比或者一個(gè)表示字節(jié)大小的值。默認(rèn)是10%,意味著分配給節(jié)點(diǎn)的總內(nèi)存的10%用來(lái)做索引緩沖的大小。這個(gè)數(shù)值被分到不同的分片(shards)上。如果設(shè)置的是百分比,還可以設(shè)置 min_index_buffer_size (默認(rèn) 48mb)和 max_index_buffer_size(默認(rèn)沒(méi)有上限)。
修改 translog 相關(guān)的設(shè)置
一是控制數(shù)據(jù)從內(nèi)存到硬盤(pán)的操作頻率,以減少硬盤(pán) IO。可將 sync_interval 的時(shí)間設(shè)置大一些。默認(rèn)為5s。
index.translog.sync_interval: 5s
也可以控制 tranlog 數(shù)據(jù)塊的大小,達(dá)到 threshold 大小時(shí),才會(huì) flush 到 lucene 索引文件。默認(rèn)為512m。
index.translog.flush_threshold_size: 512mb
translog我們?cè)赱原理:ES原理之索引文檔流程詳解]也有介紹。
注意 _id 字段的使用
_id 字段的使用,應(yīng)盡可能避免自定義 _id,以避免針對(duì) ID 的版本管理;建議使用 ES 的默認(rèn) ID 生成策略或使用數(shù)字類(lèi)型 ID 做為主鍵。
注意 _all 字段及 _source 字段的使用
_all 字段及 _source 字段的使用,應(yīng)該注意場(chǎng)景和需要,_all 字段包含了所有的索引字段,方便做全文檢索,如果無(wú)此需求,可以禁用;_source 存儲(chǔ)了原始的 document 內(nèi)容,如果沒(méi)有獲取原始文檔數(shù)據(jù)的需求,可通過(guò)設(shè)置 includes、excludes 屬性來(lái)定義放入 _source 的字段。
合理的配置使用 index 屬性
合理的配置使用 index 屬性,analyzed 和 not_analyzed,根據(jù)業(yè)務(wù)需求來(lái)控制字段是否分詞或不分詞。只有 groupby 需求的字段,配置時(shí)就設(shè)置成 not_analyzed,以提高查詢(xún)或聚類(lèi)的效率。
減少副本數(shù)量
Elasticsearch 默認(rèn)副本數(shù)量為3個(gè),雖然這樣會(huì)提高集群的可用性,增加搜索的并發(fā)數(shù),但是同時(shí)也會(huì)影響寫(xiě)入索引的效率。
在索引過(guò)程中,需要把更新的文檔發(fā)到副本節(jié)點(diǎn)上,等副本節(jié)點(diǎn)生效后在進(jìn)行返回結(jié)束。使用 Elasticsearch 做業(yè)務(wù)搜索的時(shí)候,建議副本數(shù)目還是設(shè)置為3個(gè),但是像內(nèi)部 ELK 日志系統(tǒng)、分布式跟蹤系統(tǒng)中,完全可以將副本數(shù)目設(shè)置為1個(gè)。
查詢(xún)方面優(yōu)化
Elasticsearch 作為業(yè)務(wù)搜索的近實(shí)時(shí)查詢(xún)時(shí),查詢(xún)效率的優(yōu)化顯得尤為重要。
路由優(yōu)化
當(dāng)我們查詢(xún)文檔的時(shí)候,Elasticsearch 如何知道一個(gè)文檔應(yīng)該存放到哪個(gè)分片中呢?它其實(shí)是通過(guò)下面這個(gè)公式來(lái)計(jì)算出來(lái)的。
shard = hash(routing) % number_of_primary_shards
routing 默認(rèn)值是文檔的 id,也可以采用自定義值,比如用戶(hù) ID。
不帶 routing 查詢(xún)
在查詢(xún)的時(shí)候因?yàn)椴恢酪樵?xún)的數(shù)據(jù)具體在哪個(gè)分片上,所以整個(gè)過(guò)程分為2個(gè)步驟:
- 分發(fā):請(qǐng)求到達(dá)協(xié)調(diào)節(jié)點(diǎn)后,協(xié)調(diào)節(jié)點(diǎn)將查詢(xún)請(qǐng)求分發(fā)到每個(gè)分片上。
- 聚合:協(xié)調(diào)節(jié)點(diǎn)搜集到每個(gè)分片上查詢(xún)結(jié)果,再將查詢(xún)的結(jié)果進(jìn)行排序,之后給用戶(hù)返回結(jié)果。
帶 routing 查詢(xún)
查詢(xún)的時(shí)候,可以直接根據(jù) routing 信息定位到某個(gè)分配查詢(xún),不需要查詢(xún)所有的分配,經(jīng)過(guò)協(xié)調(diào)節(jié)點(diǎn)排序。
向上面自定義的用戶(hù)查詢(xún),如果 routing 設(shè)置為 userid 的話(huà),就可以直接查詢(xún)出數(shù)據(jù)來(lái),效率提升很多。
Filter VS Query
盡可能使用過(guò)濾器上下文(Filter)替代查詢(xún)上下文(Query)
- Query:此文檔與此查詢(xún)子句的匹配程度如何?
- Filter:此文檔和查詢(xún)子句匹配嗎?
Elasticsearch 針對(duì) Filter 查詢(xún)只需要回答「是」或者「否」,不需要像 Query 查詢(xún)一樣計(jì)算相關(guān)性分?jǐn)?shù),同時(shí)Filter結(jié)果可以緩存。
深度翻頁(yè)
在使用 Elasticsearch 過(guò)程中,應(yīng)盡量避免大翻頁(yè)的出現(xiàn)。
正常翻頁(yè)查詢(xún)都是從 from 開(kāi)始 size 條數(shù)據(jù),這樣就需要在每個(gè)分片中查詢(xún)打分排名在前面的 from+size 條數(shù)據(jù)。協(xié)同節(jié)點(diǎn)收集每個(gè)分配的前 from+size 條數(shù)據(jù)。協(xié)同節(jié)點(diǎn)一共會(huì)受到 N*(from+size) 條數(shù)據(jù),然后進(jìn)行排序,再將其中 from 到 from+size 條數(shù)據(jù)返回出去。如果 from 或者 size 很大的話(huà),導(dǎo)致參加排序的數(shù)量會(huì)同步擴(kuò)大很多,最終會(huì)導(dǎo)致 CPU 資源消耗增大。
可以通過(guò)使用 Elasticsearch scroll 和 scroll-scan 高效滾動(dòng)的方式來(lái)解決這樣的問(wèn)題。
也可以結(jié)合實(shí)際業(yè)務(wù)特點(diǎn),文檔 id 大小如果和文檔創(chuàng)建時(shí)間是一致有序的,可以以文檔 id 作為分頁(yè)的偏移量,并將其作為分頁(yè)查詢(xún)的一個(gè)條件。
腳本(script)合理使用
我們知道腳本使用主要有 3 種形式,內(nèi)聯(lián)動(dòng)態(tài)編譯方式、_script 索引庫(kù)中存儲(chǔ)和文件腳本存儲(chǔ)的形式;一般腳本的使用場(chǎng)景是粗排,盡量用第二種方式先將腳本存儲(chǔ)在 _script 索引庫(kù)中,起到提前編譯,然后通過(guò)引用腳本 id,并結(jié)合 params 參數(shù)使用,即可以達(dá)到模型(邏輯)和數(shù)據(jù)進(jìn)行了分離,同時(shí)又便于腳本模塊的擴(kuò)展與維護(hù)。
Cache的設(shè)置及使用
- QueryCache: ES查詢(xún)的時(shí)候,使用filter查詢(xún)會(huì)使用query cache, 如果業(yè)務(wù)場(chǎng)景中的過(guò)濾查詢(xún)比較多,建議將querycache設(shè)置大一些,以提高查詢(xún)速度。
indices.queries.cache.size:10%(默認(rèn)),可設(shè)置成百分比,也可設(shè)置成具體值,如256mb。
當(dāng)然也可以禁用查詢(xún)緩存(默認(rèn)是開(kāi)啟), 通過(guò)index.queries.cache.enabled:false設(shè)置。
- FieldDataCache: 在聚類(lèi)或排序時(shí),field data cache會(huì)使用頻繁,因此,設(shè)置字段數(shù)據(jù)緩存的大小,在聚類(lèi)或排序場(chǎng)景較多的情形下很有必要,可通過(guò)indices.fielddata.cache.size:30% 或具體值10GB來(lái)設(shè)置。但是如果場(chǎng)景或數(shù)據(jù)變更比較頻繁,設(shè)置cache并不是好的做法,因?yàn)榫彺婕虞d的開(kāi)銷(xiāo)也是特別大的。
- ShardRequestCache: 查詢(xún)請(qǐng)求發(fā)起后,每個(gè)分片會(huì)將結(jié)果返回給協(xié)調(diào)節(jié)點(diǎn)(Coordinating Node), 由協(xié)調(diào)節(jié)點(diǎn)將結(jié)果整合。如果有需求,可以設(shè)置開(kāi)啟; 通過(guò)設(shè)置index.requests.cache.enable: true來(lái)開(kāi)啟。不過(guò),shard request cache只緩存hits.total, aggregations, suggestions類(lèi)型的數(shù)據(jù),并不會(huì)緩存hits的內(nèi)容。也可以通過(guò)設(shè)置indices.requests.cache.size: 1%(默認(rèn))來(lái)控制緩存空間大小。
更多查詢(xún)優(yōu)化經(jīng)驗(yàn)
- query_string 或 multi_match的查詢(xún)字段越多, 查詢(xún)?cè)铰?梢栽趍apping階段,利用copy_to屬性將多字段的值索引到一個(gè)新字段,multi_match時(shí),用新的字段查詢(xún)。
- 日期字段的查詢(xún), 尤其是用now 的查詢(xún)實(shí)際上是不存在緩存的,因此, 可以從業(yè)務(wù)的角度來(lái)考慮是否一定要用now, 畢竟利用query cache 是能夠大大提高查詢(xún)效率的。
- 查詢(xún)結(jié)果集的大小不能隨意設(shè)置成大得離譜的值, 如query.setSize不能設(shè)置成 Integer.MAX_VALUE, 因?yàn)镋S內(nèi)部需要建立一個(gè)數(shù)據(jù)結(jié)構(gòu)來(lái)放指定大小的結(jié)果集數(shù)據(jù)。
- 避免層級(jí)過(guò)深的聚合查詢(xún), 層級(jí)過(guò)深的aggregation , 會(huì)導(dǎo)致內(nèi)存、CPU消耗,建議在服務(wù)層通過(guò)程序來(lái)組裝業(yè)務(wù),也可以通過(guò)pipeline的方式來(lái)優(yōu)化。
- 復(fù)用預(yù)索引數(shù)據(jù)方式來(lái)提高AGG性能:
如通過(guò) terms aggregations 替代 range aggregations, 如要根據(jù)年齡來(lái)分組,分組目標(biāo)是: 少年(14歲以下) 青年(14-28) 中年(29-50) 老年(51以上), 可以在索引的時(shí)候設(shè)置一個(gè)age_group字段,預(yù)先將數(shù)據(jù)進(jìn)行分類(lèi)。從而不用按age來(lái)做range aggregations, 通過(guò)age_group字段就可以了。
通過(guò)開(kāi)啟慢查詢(xún)配置定位慢查詢(xún)
不論是數(shù)據(jù)庫(kù)還是搜索引擎,對(duì)于問(wèn)題的排查,開(kāi)啟慢查詢(xún)?nèi)罩臼鞘直匾模珽S 開(kāi)啟慢查詢(xún)的方式有多種,但是最常用的是調(diào)用模板 API 進(jìn)行全局設(shè)置:
PUT /_template/{TEMPLATE_NAME}
{
"template":"{INDEX_PATTERN}",
"settings" : {
"index.indexing.slowlog.level": "INFO",
"index.indexing.slowlog.threshold.index.warn": "10s",
"index.indexing.slowlog.threshold.index.info": "5s",
"index.indexing.slowlog.threshold.index.debug": "2s",
"index.indexing.slowlog.threshold.index.trace": "500ms",
"index.indexing.slowlog.source": "1000",
"index.search.slowlog.level": "INFO",
"index.search.slowlog.threshold.query.warn": "10s",
"index.search.slowlog.threshold.query.info": "5s",
"index.search.slowlog.threshold.query.debug": "2s",
"index.search.slowlog.threshold.query.trace": "500ms",
"index.search.slowlog.threshold.fetch.warn": "1s",
"index.search.slowlog.threshold.fetch.info": "800ms",
"index.search.slowlog.threshold.fetch.debug": "500ms",
"index.search.slowlog.threshold.fetch.trace": "200ms"
},
"version" : 1
}
PUT {INDEX_PAATERN}/_settings
{
"index.indexing.slowlog.level": "INFO",
"index.indexing.slowlog.threshold.index.warn": "10s",
"index.indexing.slowlog.threshold.index.info": "5s",
"index.indexing.slowlog.threshold.index.debug": "2s",
"index.indexing.slowlog.threshold.index.trace": "500ms",
"index.indexing.slowlog.source": "1000",
"index.search.slowlog.level": "INFO",
"index.search.slowlog.threshold.query.warn": "10s",
"index.search.slowlog.threshold.query.info": "5s",
"index.search.slowlog.threshold.query.debug": "2s",
"index.search.slowlog.threshold.query.trace": "500ms",
"index.search.slowlog.threshold.fetch.warn": "1s",
"index.search.slowlog.threshold.fetch.info": "800ms",
"index.search.slowlog.threshold.fetch.debug": "500ms",
"index.search.slowlog.threshold.fetch.trace": "200ms"
}
這樣,在日志目錄下的慢查詢(xún)?nèi)罩揪蜁?huì)有輸出記錄必要的信息了。
{CLUSTER_NAME}_index_indexing_slowlog.log
{CLUSTER_NAME}_index_search_slowlog.log
數(shù)據(jù)結(jié)構(gòu)優(yōu)化
基于 Elasticsearch 的使用場(chǎng)景,文檔數(shù)據(jù)結(jié)構(gòu)盡量和使用場(chǎng)景進(jìn)行結(jié)合,去掉沒(méi)用及不合理的數(shù)據(jù)。
盡量減少不需要的字段
如果 Elasticsearch 用于業(yè)務(wù)搜索服務(wù),一些不需要用于搜索的字段最好不存到 ES 中,這樣即節(jié)省空間,同時(shí)在相同的數(shù)據(jù)量下,也能提高搜索性能。
避免使用動(dòng)態(tài)值作字段,動(dòng)態(tài)遞增的 mapping,會(huì)導(dǎo)致集群崩潰;同樣,也需要控制字段的數(shù)量,業(yè)務(wù)中不使用的字段,就不要索引。控制索引的字段數(shù)量、mapping 深度、索引字段的類(lèi)型,對(duì)于 ES 的性能優(yōu)化是重中之重。
以下是 ES 關(guān)于字段數(shù)、mapping 深度的一些默認(rèn)設(shè)置:
index.mapping.nested_objects.limit: 10000
index.mapping.total_fields.limit: 1000
index.mapping.depth.limit: 20
Nested Object vs Parent/Child
盡量避免使用 nested 或 parent/child 的字段,能不用就不用;nested query 慢,parent/child query 更慢,比 nested query 慢上百倍;因此能在 mapping 設(shè)計(jì)階段搞定的(大寬表設(shè)計(jì)或采用比較 smart 的數(shù)據(jù)結(jié)構(gòu)),就不要用父子關(guān)系的 mapping。
如果一定要使用 nested fields,保證 nested fields 字段不能過(guò)多,目前 ES 默認(rèn)限制是 50。因?yàn)獒槍?duì) 1 個(gè) document,每一個(gè) nested field,都會(huì)生成一個(gè)獨(dú)立的 document,這將使 doc 數(shù)量劇增,影響查詢(xún)效率,尤其是 JOIN 的效率。
index.mapping.nested_fields.limit: 50
對(duì)比 | Nested Object | Parent/Child |
優(yōu)點(diǎn) | 文檔存儲(chǔ)在一起,因此讀取性高 | 父子文檔可以獨(dú)立更新,互不影響 |
缺點(diǎn) | 更新父文檔或子文檔時(shí)需要更新整個(gè)文檔 | 為了維護(hù) join 關(guān)系,需要占用部分內(nèi)存,讀取性能較差 |
場(chǎng)景 | 子文檔偶爾更新,查詢(xún)頻繁 | 子文檔更新頻繁 |
選擇靜態(tài)映射,非必需時(shí),禁止動(dòng)態(tài)映射
盡量避免使用動(dòng)態(tài)映射,這樣有可能會(huì)導(dǎo)致集群崩潰,此外,動(dòng)態(tài)映射有可能會(huì)帶來(lái)不可控制的數(shù)據(jù)類(lèi)型,進(jìn)而有可能導(dǎo)致在查詢(xún)端出現(xiàn)相關(guān)異常,影響業(yè)務(wù)。
此外,Elasticsearch 作為搜索引擎時(shí),主要承載 query 的匹配和排序的功能,那數(shù)據(jù)的存儲(chǔ)類(lèi)型基于這兩種功能的用途分為兩類(lèi),一是需要匹配的字段,用來(lái)建立倒排索引對(duì) query 匹配用,另一類(lèi)字段是用做粗排用到的特征字段,如 ctr、點(diǎn)擊數(shù)、評(píng)論數(shù)等等。
document 模型設(shè)計(jì)
對(duì)于 MySQL,我們經(jīng)常有一些復(fù)雜的關(guān)聯(lián)查詢(xún)。在 es 里該怎么玩兒,es 里面的復(fù)雜的關(guān)聯(lián)查詢(xún)盡量別用,一旦用了性能一般都不太好。
最好是先在 Java 系統(tǒng)里就完成關(guān)聯(lián),將關(guān)聯(lián)好的數(shù)據(jù)直接寫(xiě)入 es 中。搜索的時(shí)候,就不需要利用 es 的搜索語(yǔ)法來(lái)完成 join 之類(lèi)的關(guān)聯(lián)搜索了。
document 模型設(shè)計(jì)是非常重要的,很多操作,不要在搜索的時(shí)候才想去執(zhí)行各種復(fù)雜的亂七八糟的操作。es 能支持的操作就那么多,不要考慮用 es 做一些它不好操作的事情。如果真的有那種操作,盡量在 document 模型設(shè)計(jì)的時(shí)候,寫(xiě)入的時(shí)候就完成。另外對(duì)于一些太復(fù)雜的操作,比如 join/nested/parent-child 搜索都要盡量避免,性能都很差的。
集群架構(gòu)設(shè)計(jì)
合理的部署 Elasticsearch 有助于提高服務(wù)的整體可用性。
主節(jié)點(diǎn)、數(shù)據(jù)節(jié)點(diǎn)和協(xié)調(diào)節(jié)點(diǎn)分離
Elasticsearch 集群在架構(gòu)拓樸時(shí),采用主節(jié)點(diǎn)、數(shù)據(jù)節(jié)點(diǎn)和負(fù)載均衡節(jié)點(diǎn)分離的架構(gòu),在 5.x 版本以后,又可將數(shù)據(jù)節(jié)點(diǎn)再細(xì)分為“Hot-Warm”的架構(gòu)模式。
Elasticsearch 的配置文件中有 2 個(gè)參數(shù),node.master 和 node.data。這兩個(gè)參數(shù)搭配使用時(shí),能夠幫助提供服務(wù)器性能。
主(master)節(jié)點(diǎn)
配置 node.master:true 和 node.data:false,該 node 服務(wù)器只作為一個(gè)主節(jié)點(diǎn),但不存儲(chǔ)任何索引數(shù)據(jù)。我們推薦每個(gè)集群運(yùn)行3 個(gè)專(zhuān)用的 master 節(jié)點(diǎn)來(lái)提供最好的彈性。使用時(shí),你還需要將 discovery.zen.minimum_master_nodes setting 參數(shù)設(shè)置為 2,以免出現(xiàn)腦裂(split-brain)的情況。用 3 個(gè)專(zhuān)用的 master 節(jié)點(diǎn),專(zhuān)門(mén)負(fù)責(zé)處理集群的管理以及加強(qiáng)狀態(tài)的整體穩(wěn)定性。因?yàn)檫@ 3 個(gè) master 節(jié)點(diǎn)不包含數(shù)據(jù)也不會(huì)實(shí)際參與搜索以及索引操作,在 JVM 上它們不用做相同的事,例如繁重的索引或者耗時(shí),資源耗費(fèi)很大的搜索。因此不太可能會(huì)因?yàn)槔厥斩鴮?dǎo)致停頓。因此,master 節(jié)點(diǎn)的 CPU,內(nèi)存以及磁盤(pán)配置可以比 data 節(jié)點(diǎn)少很多的。
數(shù)據(jù)(data)節(jié)點(diǎn)
配置 node.master:false 和 node.data:true,該 node 服務(wù)器只作為一個(gè)數(shù)據(jù)節(jié)點(diǎn),只用于存儲(chǔ)索引數(shù)據(jù),使該 node 服務(wù)器功能單一,只用于數(shù)據(jù)存儲(chǔ)和數(shù)據(jù)查詢(xún),降低其資源消耗率。
在 Elasticsearch 5.x 版本之后,data 節(jié)點(diǎn)又可再細(xì)分為“Hot-Warm”架構(gòu),即分為熱節(jié)點(diǎn)(hot node)和暖節(jié)點(diǎn)(warm node)。
hot 節(jié)點(diǎn):
hot 節(jié)點(diǎn)主要是索引節(jié)點(diǎn)(寫(xiě)節(jié)點(diǎn)),同時(shí)會(huì)保存近期的一些頻繁被查詢(xún)的索引。由于進(jìn)行索引非常耗費(fèi) CPU 和 IO,即屬于 IO 和 CPU 密集型操作,建議使用 SSD 的磁盤(pán)類(lèi)型,保持良好的寫(xiě)性能;我們推薦部署最小化的 3 個(gè) hot 節(jié)點(diǎn)來(lái)保證高可用性。根據(jù)近期需要收集以及查詢(xún)的數(shù)據(jù)量,可以增加服務(wù)器數(shù)量來(lái)獲得想要的性能。
將節(jié)點(diǎn)設(shè)置為 hot 類(lèi)型需要 elasticsearch.yml 如下配置:
node.attr.box_type: hot
如果是針對(duì)指定的 index 操作,可以通過(guò) settings 設(shè)置 index.routing.allocation.require.box_type: hot 將索引寫(xiě)入 hot 節(jié)點(diǎn)。
warm 節(jié)點(diǎn):
這種類(lèi)型的節(jié)點(diǎn)是為了處理大量的,而且不經(jīng)常訪(fǎng)問(wèn)的只讀索引而設(shè)計(jì)的。由于這些索引是只讀的,warm 節(jié)點(diǎn)傾向于掛載大量磁盤(pán)(普通磁盤(pán))來(lái)替代 SSD。內(nèi)存、CPU 的配置跟 hot 節(jié)點(diǎn)保持一致即可;節(jié)點(diǎn)數(shù)量一般也是大于等于 3 個(gè)。
將節(jié)點(diǎn)設(shè)置為 warm 類(lèi)型需要 elasticsearch.yml 如下配置:
node.attr.box_type: warm
同時(shí),也可以在 elasticsearch.yml 中設(shè)置 index.codec:best_compression 保證 warm 節(jié)點(diǎn)的壓縮配置。
當(dāng)索引不再被頻繁查詢(xún)時(shí),可通過(guò) index.routing.allocation.require.box_type:warm,將索引標(biāo)記為 warm,從而保證索引不寫(xiě)入 hot 節(jié)點(diǎn),以便將 SSD 磁盤(pán)資源用在刀刃上。一旦設(shè)置這個(gè)屬性,ES 會(huì)自動(dòng)將索引合并到 warm 節(jié)點(diǎn)。
協(xié)調(diào)(coordinating)節(jié)點(diǎn)
協(xié)調(diào)節(jié)點(diǎn)用于做分布式里的協(xié)調(diào),將各分片或節(jié)點(diǎn)返回的數(shù)據(jù)整合后返回。該節(jié)點(diǎn)不會(huì)被選作主節(jié)點(diǎn),也不會(huì)存儲(chǔ)任何索引數(shù)據(jù)。該服務(wù)器主要用于查詢(xún)負(fù)載均衡。在查詢(xún)的時(shí)候,通常會(huì)涉及到從多個(gè) node 服務(wù)器上查詢(xún)數(shù)據(jù),并將請(qǐng)求分發(fā)到多個(gè)指定的 node 服務(wù)器,并對(duì)各個(gè) node 服務(wù)器返回的結(jié)果進(jìn)行一個(gè)匯總處理,最終返回給客戶(hù)端。在 ES 集群中,所有的節(jié)點(diǎn)都有可能是協(xié)調(diào)節(jié)點(diǎn),但是,可以通過(guò)設(shè)置 node.master、node.data、node.ingest 都為 false 來(lái)設(shè)置專(zhuān)門(mén)的協(xié)調(diào)節(jié)點(diǎn)。需要較好的 CPU 和較高的內(nèi)存。
- node.master:false和node.data:true,該node服務(wù)器只作為一個(gè)數(shù)據(jù)節(jié)點(diǎn),只用于存儲(chǔ)索引數(shù)據(jù),使該node服務(wù)器功能單一,只用于數(shù)據(jù)存儲(chǔ)和數(shù)據(jù)查詢(xún),降低其資源消耗率。
- node.master:true和node.data:false,該node服務(wù)器只作為一個(gè)主節(jié)點(diǎn),但不存儲(chǔ)任何索引數(shù)據(jù),該node服務(wù)器將使用自身空閑的資源,來(lái)協(xié)調(diào)各種創(chuàng)建索引請(qǐng)求或者查詢(xún)請(qǐng)求,并將這些請(qǐng)求合理分發(fā)到相關(guān)的node服務(wù)器上。
- node.master:false和node.data:false,該node服務(wù)器即不會(huì)被選作主節(jié)點(diǎn),也不會(huì)存儲(chǔ)任何索引數(shù)據(jù)。該服務(wù)器主要用于查詢(xún)負(fù)載均衡。在查詢(xún)的時(shí)候,通常會(huì)涉及到從多個(gè)node服務(wù)器上查詢(xún)數(shù)據(jù),并將請(qǐng)求分發(fā)到多個(gè)指定的node服務(wù)器,并對(duì)各個(gè)node服務(wù)器返回的結(jié)果進(jìn)行一個(gè)匯總處理,最終返回給客戶(hù)端。
關(guān)閉 data 節(jié)點(diǎn)服務(wù)器中的 http 功能
針對(duì) Elasticsearch 集群中的所有數(shù)據(jù)節(jié)點(diǎn),不用開(kāi)啟 http 服務(wù)。將其中的配置參數(shù)這樣設(shè)置,http.enabled:false,同時(shí)也不要安裝 head, bigdesk, marvel 等監(jiān)控插件,這樣保證 data 節(jié)點(diǎn)服務(wù)器只需處理創(chuàng)建/更新/刪除/查詢(xún)索引數(shù)據(jù)等操作。
http 功能可以在非數(shù)據(jù)節(jié)點(diǎn)服務(wù)器上開(kāi)啟,上述相關(guān)的監(jiān)控插件也安裝到這些服務(wù)器上,用于監(jiān)控 Elasticsearch 集群狀態(tài)等數(shù)據(jù)信息。這樣做一來(lái)出于數(shù)據(jù)安全考慮,二來(lái)出于服務(wù)性能考慮。
一臺(tái)服務(wù)器上最好只部署一個(gè) node
一臺(tái)物理服務(wù)器上可以啟動(dòng)多個(gè) node 服務(wù)器節(jié)點(diǎn)(通過(guò)設(shè)置不同的啟動(dòng) port),但一臺(tái)服務(wù)器上的 CPU、內(nèi)存、硬盤(pán)等資源畢竟有限,從服務(wù)器性能考慮,不建議一臺(tái)服務(wù)器上啟動(dòng)多個(gè) node 節(jié)點(diǎn)。
集群分片設(shè)置
ES 一旦創(chuàng)建好索引后,就無(wú)法調(diào)整分片的設(shè)置,而在 ES 中,一個(gè)分片實(shí)際上對(duì)應(yīng)一個(gè) lucene 索引,而 lucene 索引的讀寫(xiě)會(huì)占用很多的系統(tǒng)資源,因此,分片數(shù)不能設(shè)置過(guò)大;所以,在創(chuàng)建索引時(shí),合理配置分片數(shù)是非常重要的。一般來(lái)說(shuō),我們遵循一些原則:
控制每個(gè)分片占用的硬盤(pán)容量不超過(guò) ES 的最大 JVM 的堆空間設(shè)置(一般設(shè)置不超過(guò) 32 G,參考上面的 JVM 內(nèi)存設(shè)置原則),因此,如果索引的總?cè)萘吭?500 G 左右,那分片大小在 16 個(gè)左右即可;當(dāng)然,最好同時(shí)考慮原則 2。考慮一下 node 數(shù)量,一般一個(gè)節(jié)點(diǎn)有時(shí)候就是一臺(tái)物理機(jī),如果分片數(shù)過(guò)多,大大超過(guò)了節(jié)點(diǎn)數(shù),很可能會(huì)導(dǎo)致一個(gè)節(jié)點(diǎn)上存在多個(gè)分片,一旦該節(jié)點(diǎn)故障,即使保持了 1 個(gè)以上的副本,同樣有可能會(huì)導(dǎo)致數(shù)據(jù)丟失,集群無(wú)法恢復(fù)。所以,一般都設(shè)置分片數(shù)不超過(guò)節(jié)點(diǎn)數(shù)的 3 倍。