Author: 袁野
Date: 2018.01.05
Version: 1.0
注意事項(xiàng):
- 本文檔所述為通用情況,不可作為特定業(yè)務(wù)參照;
- 本文檔所述適用于 ELK 棧中的 ElasticSearch 優(yōu)化;
- 本文檔所述基于 elastic 公司的 5.2.1 版本;
- 本文檔只描述最佳實(shí)踐,不包含所需變更步驟;
- 本文檔針對 CentOS 7.2,其他 Unix 發(fā)行版不在討論范圍內(nèi);
硬件選擇
目前公司的物理機(jī)機(jī)型在CPU和內(nèi)存方面都滿足需求,建議使用SSD機(jī)型。原因在于,可以快速把 Lucene 的索引文件加載入內(nèi)存(這在宕機(jī)恢復(fù)的情況下尤為明顯),減少 IO 負(fù)載和 IO wait以便CPU不總是在等待IO中斷。建議使用多裸盤而非raid,因?yàn)?ElasticSearch 本身就支持多目錄,raid 要么犧牲空間要么犧牲可用性。
系統(tǒng)配置
ElasticSearch 理論上必須單獨(dú)部署,并且會獨(dú)占幾乎所有系統(tǒng)資源,因此需要對系統(tǒng)進(jìn)行配置,以保證運(yùn)行 ElasticSearch 的用戶可以使用足夠多的資源。生產(chǎn)集群需要調(diào)整的配置如下:
- 設(shè)置 JVM 堆大小;
- 關(guān)閉 swap;
- 增加文件描述符;
- 保證足夠的虛存;
- 保證足夠的線程;
- 暫時不建議使用G1GC;
設(shè)置 JVM 堆大小
ElasticSearch 需要有足夠的 JVM 堆支撐索引數(shù)據(jù)的加載,對于公司的機(jī)型來說,因?yàn)槎际谴笥?128GB 的,所以推薦的配置是 32GB(如果 JVM 以不等的初始和最大堆大小啟動,則在系統(tǒng)使用過程中可能會因?yàn)?JVM 堆的大小調(diào)整而容易中斷。 為了避免這些調(diào)整大小的暫停,最好使用初始堆大小等于最大堆大小的 JVM 來啟動),預(yù)留足夠的 IO Cache 給 Lucene(官方建議超過一半的內(nèi)存需要預(yù)留)。
設(shè)置方法(需要重啟進(jìn)程):
# Step1. 修改 ${PATH_TO_ElasticSearch_HOME}/config/jvm.options 中的 Xms 和 Xmx
-Xms32g
-Xmx32g
# Step2. 重啟 elasticsearch
sudo sytemctl restart elasticsearch
關(guān)閉 swap & 禁用交換
必須要關(guān)閉 swap,因?yàn)樵谖锢韮?nèi)存不足時,如果發(fā)生 FGC,在回收虛擬內(nèi)存的時候會造成長時間的 stop-the-world,最嚴(yán)重的后果是造成集群雪崩。公司的默認(rèn)模板是關(guān)閉的,但是要巡檢一遍,避免有些機(jī)器存在問題。
設(shè)置方法:
# Step1. root 用戶臨時關(guān)閉
sudo swapoff -a
sudo sysctl vm.swappiness=0
# Step2. 修改 /etc/fstab,注釋掉 swap 這行
# Step3. 修改 /etc/sysctl.conf,添加:
vm.swappiness = 0
# Step4. 確認(rèn)是否生效
sudo sysctl vm.swappiness
也可以通過修改 yml 配置文件的方式從 ElasticSearch 層面禁止物理內(nèi)存和交換區(qū)之間交換內(nèi)存:
Linux 把它的物理 RAM 分成多個內(nèi)存塊,稱之為分頁。內(nèi)存交換(swapping)是這樣一個過程,它把內(nèi)存分頁復(fù)制到預(yù)先設(shè)定的叫做交換區(qū)的硬盤空間上,以此釋放內(nèi)存分頁。物理內(nèi)存和交換區(qū)加起來的大小就是虛擬內(nèi)存的可用額度。
內(nèi)存交換有個缺點(diǎn),跟內(nèi)存比起來硬盤非常慢。內(nèi)存的讀寫速度以納秒來計(jì)算,而硬盤是以毫秒來計(jì)算,所以訪問硬盤比訪問內(nèi)存要慢幾萬倍。交換次數(shù)越多,進(jìn)程就越慢,所以應(yīng)該不惜一切代價避免內(nèi)存交換的發(fā)生。
ElasticSearch 的 memory_lock 屬性允許 Elasticsearch 節(jié)點(diǎn)不交換內(nèi)存。(注意只有Linux/Unix系統(tǒng)可設(shè)置。)這個屬性可以在yml文件中設(shè)置。
# Step1. 修改 ${PATH_TO_ES_HOME}/config/elasticsearch.yml,添加:
bootstrap.memory_lock: true
增加文件描述符
單個用戶可用的最大進(jìn)程數(shù)量(軟限制)&單個用戶可用的最大進(jìn)程數(shù)量(硬限制),超過軟限制會有警告,但是無法超過硬限制。 ElasticSearch 會使用大量的文件句柄,如果超過限制可能會造成宕機(jī)或者數(shù)據(jù)缺失。
文件描述符是用于跟蹤打開“文件”的 Unix 結(jié)構(gòu)體。在Unix中,一切都皆文件。 例如,“文件”可以是物理文件,虛擬文件(例如/proc/loadavg)或網(wǎng)絡(luò)套接字。 ElasticSearch 需要大量的文件描述符(例如,每個 shard 由多個 segment 和其他文件組成,以及到其他節(jié)點(diǎn)的 socket 連接等)。
設(shè)置方法(假設(shè)是 admin 用戶啟動的 ElasticSearch 進(jìn)程):
# Step1. 修改 /etc/security/limits.conf,添加:
admin soft nofile 65536
admin hard nofile 65536
# Step2. 確認(rèn)是否生效
su - admin
ulimit -n
# Step3. 通過 rest 確認(rèn)是否生效
GET /_nodes/stats/process?filter_path=**.max_file_descriptors
保證足夠的虛存
單進(jìn)程最多可以占用的內(nèi)存區(qū)域,默認(rèn)為 65536。Elasticsearch 默認(rèn)會使用 mmapfs 去存儲 indices,默認(rèn)的 65536 過少,會造成 OOM 異常。
設(shè)置方法:
# Step1. root 用戶修改臨時參數(shù)
sudo sysctl -w vm.max_map_count=262144
# Step2. 修改 /etc/sysctl.conf,在文末添加:
vm.max_map_count = 262144
# Step3. 確認(rèn)是否生效
sudo sysctl vm.max_map_count
保證足夠的線程
Elasticsearch 通過將請求分成幾個階段,并交給不同的線程池執(zhí)行(Elasticsearch 中有各種不同的線程池執(zhí)行器)。 因此,Elasticsearch 需要創(chuàng)建大量線程的能力。進(jìn)程可創(chuàng)建線程的最大數(shù)量確保 Elasticsearch 進(jìn)程有權(quán)在正常使用情況下創(chuàng)建足夠的線程。 這可以通過 /etc/security/limits.conf
使用 nproc 設(shè)置來完成。
設(shè)置方法(假設(shè)是 admin 用戶啟動的 Elasticsearch 進(jìn)程):
# Step1. 修改 /etc/security/limits.d/90-nproc.conf,添加:
admin soft nproc 2048
暫時不建議使用G1GC
已知 JDK 8 附帶的 HotSpot JVM 的早期版本在啟用 G1GC 收集器時會導(dǎo)致索引損壞。受影響的版本是早于 JDK 8u40 附帶的HotSpot 的版本,出于穩(wěn)定性的考慮暫時不建議使用。
內(nèi)存優(yōu)化
ElasticSearch 自身對內(nèi)存管理進(jìn)行了大量優(yōu)化,但對于持續(xù)增長的業(yè)務(wù)仍需進(jìn)行一定程度的內(nèi)存優(yōu)化(而不是純粹的添加節(jié)點(diǎn)和擴(kuò)展物理內(nèi)存),以防止 OOM 發(fā)生。ElasticSearch 使用的 JVM 堆中主要包括以下幾類內(nèi)存使用:
- Segment Memory;
- Filter Cache;
- Field Data Cache;
- Bulk Queue;
- Indexing Buffer;
- Cluster State Buffer;
- 超大搜索聚合結(jié)果集的 fetch;
詳細(xì)資料可以參閱我寫的這篇博文。
減少 Segment Memory
- 刪除無用的歷史索引
刪除辦法,使用 rest API:
# 刪除指定某個索引
DELETE /${INDEX_NAME}
# 刪除符合 pattern 的某些索引
DELETE /${INDEX_PATTERN}
- 關(guān)閉無需實(shí)時查詢的歷史索引,文件仍然存在于磁盤,只是釋放掉內(nèi)存,需要的時候可以重新打開
關(guān)閉辦法,使用 rest API:
# 關(guān)閉指定某個索引
POST /${INDEX_NAME}/_close
# 關(guān)閉符合 pattern 的某些索引
POST /${INDEX_PATTERN}/_close
- 定期對不再更新的索引做 force merge(會占用大量 IO,建議業(yè)務(wù)低峰期觸發(fā))
force merge 辦法,使用 rest API:
# Step1. 在合并前需要對合并速度進(jìn)行合理限制,默認(rèn)是 20mb,SSD可以適當(dāng)放寬到 80mb:
PUT /_cluster/settings -d '
{
"persistent" : {
"indices.store.throttle.max_bytes_per_sec" : "20mb"
}
}'
# Step2. 強(qiáng)制合并 API,示例表示的是最終合并為一個 segment file:
# 對某個索引做合并
POST /${INDEX_NAME}/_forcemerge?max_num_segments=1
# 對某些索引做合并
POST /${INDEX_PATTERN}/_forcemerge?max_num_segments=1
Filter Cache
默認(rèn)的 10% heap 設(shè)置工作得夠好,如果實(shí)際使用中 heap 沒什么壓力的情況下,才考慮加大這個設(shè)置。
Field Data Cache
對需要排序的字段不進(jìn)行 analyzed,盡量使用 doc values(5.X版本天然支持,不需要特別設(shè)置)。對于不參與搜索的字段 ( fields ),將其 index 方法設(shè)置為 no,如果對分詞沒有需求,對參與搜索的字段,其 index 方法設(shè)置為 not_analyzed。
Bulk Queue
一般來說官方默認(rèn)的 thread pool 設(shè)置已經(jīng)能很好的工作了,建議不要隨意去調(diào)優(yōu)相關(guān)的設(shè)置,很多時候都是適得其反的效果。
Indexing Buffer
這個參數(shù)的默認(rèn)值是10% heap size。根據(jù)經(jīng)驗(yàn),這個默認(rèn)值也能夠很好的工作,應(yīng)對很大的索引吞吐量。 但有些用戶認(rèn)為這個 buffer 越大吞吐量越高,因此見過有用戶將其設(shè)置為 40% 的。到了極端的情況,寫入速度很高的時候,40%都被占用,導(dǎo)致OOM。
Cluster State Buffer
在超大規(guī)模集群的情況下,可以考慮分集群并通過 tribe node 連接做到對用戶透明,這樣可以保證每個集群里的 state 信息不會膨脹得過大。在單集群情況下,縮減 cluster state buffer 的方法就是減少 shard 數(shù)量,shard 數(shù)量的確定有以下幾條規(guī)則:
- 避免有非常大的分片,因?yàn)榇蟮姆制赡軙簭墓收现谢謴?fù)的能力產(chǎn)生負(fù)面影響。 對于多大的分片沒有固定的限制,但是分片大小為 50GB 通常被界定為適用于各種用例的限制;
- 盡可能使用基于時間的索引來管理數(shù)據(jù)。根據(jù)保留期(retention period,可以理解成有效期)將數(shù)據(jù)分組。基于時間的索引還可以輕松地隨時間改變主分片和副本分片的數(shù)量(以為要生成的下一個索引進(jìn)行更改)。這簡化了適應(yīng)不斷變化的數(shù)據(jù)量和需求;(周期性的通過刪除或者關(guān)閉歷史索引以減少分片)
- 小分片會導(dǎo)致小分段(segment),從而增加開銷。目的是保持平均分片大小在幾GB和幾十GB之間。對于具有基于時間的數(shù)據(jù)的用例,通常看到大小在 20GB 和 40GB 之間的分片;
- 由于每個分片的開銷取決于分段數(shù)和大小,通過強(qiáng)制操作迫使較小的段合并成較大的段可以減少開銷并提高查詢性能。一旦沒有更多的數(shù)據(jù)被寫入索引,這應(yīng)該是理想的。請注意,這是一個消耗資源的(昂貴的)操作,較為理想的處理時段應(yīng)該在非高峰時段執(zhí)行;(對應(yīng)使用 force meger 以減少 segment 數(shù)量的優(yōu)化,目的是降低 segment memory 占用)
- 可以在集群節(jié)點(diǎn)上保存的分片數(shù)量與可用的堆內(nèi)存大小成正比,但這在 Elasticsearch 中沒有的固定限制。 一個很好的經(jīng)驗(yàn)法則是:確保每個節(jié)點(diǎn)的分片數(shù)量保持在低于每 1GB 堆內(nèi)存對應(yīng)集群的分片在 20-25 之間。 因此,具有 32GB 堆內(nèi)存的節(jié)點(diǎn)最多可以有 600-750 個分片;
- 對于單索引的主分片數(shù),有這么 2 個公式:
節(jié)點(diǎn)數(shù) <= 主分片數(shù) *(副本數(shù) + 1)
以及(同一索引 shard 數(shù)量 * (1 + 副本數(shù))) < 3 * 數(shù)據(jù)節(jié)點(diǎn)數(shù)
,比如有 3 個節(jié)點(diǎn)全是數(shù)據(jù)節(jié)點(diǎn),1 個副本,那么主分片數(shù)大于等于 1.5,同時同一索引總分片數(shù)需要小于 4.5,因?yàn)楦北緮?shù)為 1,所以單節(jié)點(diǎn)主分片最適為 2,索引總分片數(shù)最適為 6,這樣每個節(jié)點(diǎn)的總分片為 4; - 單分片小于 20GB 的情況下,采用單分片較為合適,請求不存在網(wǎng)絡(luò)抖動的顧慮;
小結(jié):分片不超 20GB,且單節(jié)點(diǎn)總分片不超 600。比如互聯(lián)網(wǎng)區(qū)域,每天新建索引(lw-greenbay-online) 1 個分片 1 個副本,3 個月前的歷史索引都關(guān)閉,3 節(jié)點(diǎn)總共需要扛 90 * 2 = 180 個分片,每個分片大約 6 GB,可謂比較健康的狀態(tài)。
超大搜索聚合結(jié)果集的 fetch
避免用戶 fetch 超大搜索聚合結(jié)果集,確實(shí)需要大量拉取數(shù)據(jù)可以采用 scan & scroll API 來實(shí)現(xiàn)。在 ElasticSearch 上搜索數(shù)據(jù)時,默認(rèn)只會返回10條文檔,當(dāng)我們想獲取更多結(jié)果,或者只要結(jié)果中的一個區(qū)間的數(shù)據(jù)時,可以通過 size 和 from 來指定。
GET /_search?size=3&from=20
如上的查詢語句,會返回排序后的結(jié)果中第 20 到第 22 條數(shù)據(jù)。ElasticSearch 在收到這樣的一個請求之后,每一個分片都會返回一個 top22 的搜索結(jié)果,然后將這些結(jié)果匯總排序,再選出 top22 ,最后取第 20 到第 22 條數(shù)據(jù)作為結(jié)果返回。
這樣會帶來一個問題,當(dāng)我們搜索的時候,如果想取出第 10001 條數(shù)據(jù),那么就相當(dāng)于每個一分片都要對數(shù)據(jù)進(jìn)行排序,取出前 10001 條文檔,然后 ElasticSearch 再將這些結(jié)果匯總再次排序,之后取出第 10001 條數(shù)據(jù)。這樣對于 ElasticSearch 來說就會產(chǎn)生相當(dāng)大的資源和性能開銷。如果我們不要求 ElasticSearch 對結(jié)果進(jìn)行排序,那么就會消耗很少的資源,所以針對此種情況,ElasticSearch 提供了scan & scroll的搜索方式。
GET /old_index/_search?search_type=scan&scroll=1m
{
"query": { "match_all": {}},
"size": 1000
}
我們可以首先通過如上的請求發(fā)起一個搜索,但是這個請求不會返回任何文檔,它會返回一個 _scroll_id ,接下來我們再通過這個 id 來從 ElasticSearch 中讀取數(shù)據(jù):
GET /_search/scroll?scroll=1m
c2Nhbjs1OzExODpRNV9aY1VyUVM4U0NMd2pjWlJ3YWlBOzExOTpRNV9aY1VyUVM4U0 NMd2pjWlJ3YWlBOzExNjpRNV9aY1VyUVM4U0NMd2pjWlJ3YWlBOzExNzpRNV9aY1VyUVM4U0NMd2pjWlJ3YWlBOzEyMDpRNV9aY1VyUVM4U0NMd2pjWlJ3YWlBOzE7dG90YWxfaGl0czoxOw==
此時除了會返回搜索結(jié)果以外,還會再次返回一個 _scroll_id,當(dāng)我們下次繼續(xù)取數(shù)據(jù)時,需要用最新的 id。
存儲優(yōu)化
關(guān)閉不需要的功能
默認(rèn)情況下 ElasticSearch 并將會將 indexs 和 doc values 添加到大多數(shù)字段中,以便可以搜索和聚合它們。 例如,如果有一個名為 foo 的數(shù)字字段,需要運(yùn)行 histograms 但不需要 filter,則可以安全地禁用映射中此字段的索引:
PUT ${INDEX_NAME}
{
"mappings": {
"type": {
"properties": {
"foo": {
"type": "integer",
"index": false
}
}
}
}
}
text 字段在索引中存儲規(guī)范化因子以便能夠?qū)ξ臋n進(jìn)行評分。 如果只需要在 text 字段上使用 matching 功能,但不關(guān)心生成的 score,則可以命令 ElasticSearch 配置為不將規(guī)范寫入索引:
PUT ${INDEX_NAME}
{
"mappings": {
"type": {
"properties": {
"foo": {
"type": "text",
"norms": false
}
}
}
}
}
text 字段也默認(rèn)存儲索引中的頻率和位置。 頻率用于計(jì)算分?jǐn)?shù),位置用于運(yùn)行短語查詢(phrase queries)。 如果不需要運(yùn)行短語查詢,可以告訴 ElasticSearch 不要索引位置:
PUT ${INDEX_NAME}
{
"mappings": {
"type": {
"properties": {
"foo": {
"type": "text",
"index_options": "freqs"
}
}
}
}
}
此外,如果不關(guān)心計(jì)分,則可以配置 ElasticSearch 以僅索引每個 term 的匹配文檔。 這樣做仍然可以在此字段上進(jìn)行搜索(search),但是短語查詢會引發(fā)錯誤,評分將假定 term 在每個文檔中只出現(xiàn)一次。
PUT ${INDEX_NAME}
{
"mappings": {
"type": {
"properties": {
"foo": {
"type": "text",
"norms": false,
"index_options": "freqs"
}
}
}
}
}
強(qiáng)制清除已標(biāo)記刪除的數(shù)據(jù)
Elasticsearch 是建立在 Apache Lucene 基礎(chǔ)上的實(shí)時分布式搜索引擎,Lucene 為了提高搜索的實(shí)時性,采用不可再修改(immutable)方式將文檔存儲在一個個 segment 中。也就是說,一個 segment 在寫入到存儲系統(tǒng)之后,將不可以再修改。那么 Lucene 是如何從一個 segment 中刪除一個被索引的文檔呢?簡單的講,當(dāng)用戶發(fā)出命令刪除一個被索引的文檔#ABC 時,該文檔并不會被馬上從相應(yīng)的存儲它的 segment 中刪除掉,而是通過一個特殊的文件來標(biāo)記該文檔已被刪除。當(dāng)用戶再次搜索到 #ABC 時,Elasticsearch 在 segment 中仍能找到 #ABC,但由于 #ABC 文檔已經(jīng)被標(biāo)記為刪除,所以Lucene 會從發(fā)回給用戶的搜索結(jié)果中剔除 #ABC,所以給用戶感覺的是 #ABC 已經(jīng)被刪除了。
Elasticseach 會有后臺線程根據(jù) Lucene 的合并規(guī)則定期進(jìn)行 segment merging 合并操作,一般不需要用戶擔(dān)心或者采取任何行動。被刪除的文檔在 segment 合并時,才會被真正刪除掉。在此之前,它仍然會占用著 JVM heap 和操作系統(tǒng)的文件 cache 等資源。在某些情況下,我們需要強(qiáng)制 Elasticsearch 進(jìn)行 segment merging,已釋放其占用的大量系統(tǒng)資源。
POST /${INDEX_NAME}/_forcemerge?max_num_segments=1&only_expunge_deletes=true&wait_for_completion=true
POST /${INDEX_PATTERN}/_forcemerge?max_num_segments=1&only_expunge_deletes=true&wait_for_completion=true
Force Merge 命令可強(qiáng)制進(jìn)行 segment 合并,并刪除所有標(biāo)記為刪除的文檔。Segment merging 要消耗 CPU,以及大量的 I/O 資源,所以一定要在 ElasticSearch 集群處于維護(hù)窗口期間,并且有足夠的 I/O 空間的(如:SSD)的條件下進(jìn)行;否則很可能造成集群崩潰和數(shù)據(jù)丟失。
減少副本數(shù)
最直接的存儲優(yōu)化手段是調(diào)整副本數(shù),默認(rèn) ElasticSearch 是有 1 個副本的,假設(shè)對可用性要求不高,允許磁盤損壞情況下可能的數(shù)據(jù)缺失,可以把副本數(shù)調(diào)整為 0,具體操作如下:
PUT /_template/${TEMPLATE_NAME}
{
"template":"${TEMPLATE_PATTERN}",
"settings" : {
"number_of_replicas" : 0
},
"version" : 1
}
其中 ${TEMPLATE_NAME} 表示模板名稱,可以是不存在的,系統(tǒng)會新建。${TEMPLATE_PATTERN} 是用于匹配索引的表達(dá)式,比如 lw-greenbay-online-*。
與此相關(guān)的一個系統(tǒng)參數(shù)為:index.merge.scheduler.max_thread_count
,默認(rèn)值為 Math.max(1, Math.min(4, Runtime.getRuntime().availableProcessors() / 2))
,這個值在 SSD 上工作沒問題,但是 SATA 盤上還是使用 1 個線程為好,因?yàn)樘嘁瞾聿患巴瓿伞?/p>
# SATA 請?jiān)O(shè)置 merge 線程為 1
PUT /_template/${TEMPLATE_NAME}
{
"template":"${TEMPLATE_PATTERN}",
"settings" : {
"index.merge.scheduler.max_thread_count": 1
},
"version" : 1
}
請勿使用默認(rèn)的動態(tài)字符串映射
默認(rèn)的動態(tài)字符串映射會將字符串字段索引為文本(text)和關(guān)鍵字(keyword)。 如果只需要其中的一個,這樣做無疑是浪費(fèi)的。 通常情況下,一個 id 字段只需要被索引為一個 keyword,而一個 body 字段只需要被索引為一個 text 字段。
可以通過在字符串字段上配置顯式映射或設(shè)置將字符串字段映射為文本(text)或關(guān)鍵字(keyword)的動態(tài)模板來禁用此功能。
例如下面的模板,可以用來將 strings 字段映射為關(guān)鍵字:
PUT ${INDEX_NAME}
{
"mappings": {
"type": {
"dynamic_templates": [
{
"strings": {
"match_mapping_type": "string",
"mapping": {
"type": "keyword"
}
}
}
]
}
}
}
禁用 _all 字段
_all 字段是由所有字段拼接成的超級字段,如果在查詢中已知需要查詢的字段,就可以考慮禁用它。
PUT /_template/${TEMPLATE_NAME}
{
"template": "${TEMPLATE_PATTERN}",
"settings" : {...},
"mappings": {
"type_1": {
"_all": {
"enabled": false
},
"properties": {...}
}
},
"version" : 1
}
使用 best_compression
_source 字段和 stored fields 會占用大量存儲,可以考慮使用 best_compression 進(jìn)行壓縮。默認(rèn)的壓縮方式為 LZ4,但需要更高壓縮比的話,可以通過 inex.codec
進(jìn)行設(shè)置,修改為 DEFLATE,在 force merge 后生效:
# Step1. 修改壓縮算法為 best_compression
PUT /_template/${TEMPLATE_NAME}
{
"template":"${TEMPLATE_PATTERN}",
"settings" : {
"index.codec" : "best_compression"
},
"version" : 1
}
# Step2. force merge
POST /${INDEX_NAME}/_forcemerge?max_num_segments=1&wait_for_completion=true
POST /${INDEX_PATTERN}/_forcemerge?max_num_segments=1&wait_for_completion=true
使用最優(yōu)數(shù)據(jù)格式
我們?yōu)閿?shù)字?jǐn)?shù)據(jù)選擇的類型可能會對磁盤使用量產(chǎn)生重大影響。 首先,應(yīng)使用整數(shù)類型(byte,short,integer或long)來存儲整數(shù),浮點(diǎn)數(shù)應(yīng)該存儲在 scaled_float 中,或者存儲在適合用例的最小類型中:使用 float 而不是 double,使用 half_float 而不是 float。
PUT /_template/${TEMPLATE_NAME}
{
"template": "${TEMPLATE_PATTERN}",
"settings" : {...},
"mappings": {
"type_1": {
"${FIELD_NAME}": {
"type": "integer"
},
"properties": {...}
}
},
"version" : 1
}
搜索速度優(yōu)化
避免Join和Parent-Child
Join會使查詢慢數(shù)倍、 Parent-Child會使查詢慢數(shù)百倍,請?jiān)谶M(jìn)行 query 語句編寫的時候盡量避免。
映射
某些數(shù)據(jù)本身是數(shù)字,但并不意味著它應(yīng)該總是被映射為一個數(shù)字字段。 通常存儲著標(biāo)識符的字段(如ISBN)或來自另一個數(shù)據(jù)庫的數(shù)字型記錄,可能映射為 keyword 而不是 integer 或者 long 會更好些。
避免使用 Scripts
之前 Groovy 腳本曝出了很大的漏洞,總的來說是需要避免使用的。如果必須要使用,盡量用 5.X 以上版本自帶的 painless 和 expressions 引擎。
根據(jù)四舍五入的日期進(jìn)行查詢
根據(jù) timestamp 字段進(jìn)行的查詢通常不可緩存,因?yàn)槠ヅ涞姆秶冀K在變化。 但就用戶體驗(yàn)而言,以四舍五入對日期進(jìn)行轉(zhuǎn)換通常是可接受的,這樣可以有效利用系統(tǒng)緩存。
舉例說明,有以下查詢:
PUT index/type/1
{
"my_date": "2016-05-11T16:30:55.328Z"
}
GET index/_search
{
"query": {
"constant_score": {
"filter": {
"range": {
"my_date": {
"gte": "now-1h",
"lte": "now"
}
}
}
}
}
}
可以對時間范圍進(jìn)行替換:
GET index/_search
{
"query": {
"constant_score": {
"filter": {
"range": {
"my_date": {
"gte": "now-1h/m",
"lte": "now/m"
}
}
}
}
}
}
在這種情況下,我們四舍五入到分鐘,所以如果當(dāng)前時間是 16:31:29 ,范圍查詢將匹配 my_date 字段的值在 15:31:00 和16:31:59 之間的所有內(nèi)容。 如果多個用戶在同一分鐘內(nèi)運(yùn)行包含這個范圍的查詢,查詢緩存可以幫助加快速度。 用于四舍五入的時間間隔越長,查詢緩存可以提供的幫助就越多,但要注意過于積極的舍入也可能會傷害用戶體驗(yàn)。
為了能夠利用查詢緩存,建議將范圍分割成大的可緩存部分和更小的不可緩存的部分,如下所示:
GET index/_search
{
"query": {
"constant_score": {
"filter": {
"bool": {
"should": [
{
"range": {
"my_date": {
"gte": "now-1h",
"lte": "now-1h/m"
}
}
},
{
"range": {
"my_date": {
"gt": "now-1h/m",
"lt": "now/m"
}
}
},
{
"range": {
"my_date": {
"gte": "now/m",
"lte": "now"
}
}
}
]
}
}
}
}
}
然而,這種做法可能會使查詢在某些情況下運(yùn)行速度較慢,因?yàn)橛?bool 查詢引入的開銷可能會因更好地利用查詢緩存而失敗。
對只讀 indices 進(jìn)行 force merge
建議將只讀索引被合并到一個單獨(dú)的分段中。 基于時間的索引通常就是這種情況:只有當(dāng)前時間索引會寫入數(shù)據(jù),而舊索引是只讀索引。
預(yù)熱 global ordinals
全局序號(global ordinals)是用于在關(guān)鍵字(keyword)字段上運(yùn)行 terms aggregations 的數(shù)據(jù)結(jié)構(gòu)。 由于 ElasticSearch 不知道聚合使用哪些字段、哪些字段不使用,所以它們在內(nèi)存中被加載得很慢。 我們可以通過下面的 API 來告訴 ElasticSearch 通過配置映射來在 refresh 的時候加載全局序號:
PUT index
{
"mappings": {
"type": {
"properties": {
"foo": {
"type": "keyword",
"eager_global_ordinals": true
}
}
}
}
}
寫入性能優(yōu)化
之前描述了 ElasticSearch 在內(nèi)存管理方面的優(yōu)化,接下來梳理下如何對寫入性能進(jìn)行優(yōu)化,寫入性能的優(yōu)化也和 HBase 類似,無非就是增加吞吐,而增加吞吐的方法就是增大刷寫間隔、合理設(shè)置線程數(shù)量、開啟異步刷寫(允許數(shù)據(jù)丟失的情況下)。
增大刷寫間隔
通過修改主配置文件 elasticsearch.yml
或者 Rest API 都可以對 index.refresh_interval進(jìn)行修改,增大該屬性可以提升寫入吞吐。
PUT /_template/{TEMPLATE_NAME}
{
"template":"{INDEX_PATTERN}",
"settings" : {
"index.refresh_interval" : "30s"
}
}
PUT {INDEX_PAATERN}/_settings
{
"index.refresh_interval" : "30s"
}
合理設(shè)置線程數(shù)量
調(diào)整 elasticsearch.yml
,對 bulk/flush 線程池進(jìn)行調(diào)優(yōu),根據(jù)本機(jī)實(shí)際配置:
threadpool.bulk.type:fixed
threadpool.bulk.size:8 #(CPU核數(shù))
threadpool.flush.type:fixed
threadpool.flush.size:8 #(CPU核數(shù))
開啟異步刷寫
如果允許數(shù)據(jù)丟失,可以對特定 index 開啟異步刷寫:
PUT /_template/{TEMPLATE_NAME}
{
"template":"{INDEX_PATTERN}",
"settings" : {
"index.translog.durability": "async"
}
}
PUT {INDEX_PAATERN}/_settings
{
"index.translog.durability": "async"
}
審計(jì)優(yōu)化
開啟慢查詢?nèi)罩?/h3>
不論是數(shù)據(jù)庫還是搜索引擎,對于問題的排查,開啟慢查詢?nèi)罩臼鞘直匾模珽lasticSearch 開啟慢查詢的方式有多種,但是最常用的是調(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
}
對于已經(jīng)存在的 index 使用 settings API:
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"
}
這樣,在日志目錄下的慢查詢?nèi)罩揪蜁休敵鲇涗洷匾男畔⒘恕?/p>