Elasticsearch 寫入流程及優化
一、 集群分片設置:
ES一旦創建好索引后,就無法調整分片的設置,而在ES中,一個分片實際上對應一個lucene 索引,而lucene索引的讀寫會占用很多的系統資源,因此,分片數不能設置過大;所以,在創建索引時,合理配置分片數是非常重要的。一般來說,我們遵循一些原則:
1. 控制每個分片占用的硬盤容量不超過ES的最大JVM的堆空間設置(一般設置不超過32G,參加上文的JVM設置原則),因此,如果索引的總容量在500G左右,那分片大小在16個左右即可;當然,最好同時考慮原則2。
2. 考慮一下node數量,一般一個節點有時候就是一台物理機,如果分片數過多,大大超過了節點數,很可能會導致一個節點上存在多個分片,一旦該節點故障,即使保持了1個以上的副本,同樣有可能會導致數據丟失,集群無法恢復。所以, 一般都設置分片數不超過節點數的3倍。
二、 Mapping建模:
1. 盡量避免使用nested或 parent/child,能不用就不用;nested query慢, parent/child query 更慢,比nested query慢上百倍;因此能在mapping設計階段搞定的(大寬表設計或采用比較smart的數據結構),就不要用父子關系的mapping。
2. 如果一定要使用nested fields,保證nested fields字段不能過多,目前ES默認限制是50。參考:
index.mapping.nested_fields.limit :50
因為針對1個document, 每一個nested field, 都會生成一個獨立的document, 這將使Doc數量劇增,影響查詢效率,尤其是JOIN的效率。
3. 避免使用動態值作字段(key), 動態遞增的mapping,會導致集群崩潰;同樣,也需要控制字段的數量,業務中不使用的字段,就不要索引。控制索引的字段數量、mapping深度、索引字段的類型,對於ES的性能優化是重中之重。以下是ES關於字段數、mapping深度的一些默認設置:
index.mapping.nested_objects.limit :10000
index.mapping.total_fields.limit:1000
index.mapping.depth.limit: 20
三,Elasticsearch寫入索引數據的過程 以及優化寫入過程
完整elasticsearch的寫入數據流程如下:

上圖補充:將es中比較困惑的幾個概念簡單總結一下,這三種操作對理解es底層原理和優化很有幫助!
refresh
es接收數據請求時先存入內存中,默認每隔一秒會從內存buffer中將數據寫入filesystem cache,這個過程叫做refresh;
fsync
translog會每隔5秒或者在一個變更請求完成之后執行一次fsync操作,將translog從緩存刷入磁盤,這個操作比較耗時,如果對數據一致性要求不是跟高時建議將索引改為異步,如果節點宕機時會有5秒數據丟失;
flush
es默認每隔30分鍾會將filesystem cache中的數據刷入磁盤同時清空translog日志文件,這個過程叫做flush。
四, Lucene操作document的流程
Lucene將index數據分為segment(段)進行存儲和管理.
Lucene中, 倒排索引一旦被創建就不可改變, 要添加或修改文檔, 就需要重建整個倒排索引, 這就對一個index所能包含的數據量, 或index可以被更新的頻率造成了很大的限制.
為了在保留不變性的前提下實現倒排索引的更新, Lucene引入了一個新思路: 使用更多的索引, 也就是通過增加新的補充索引來反映最新的修改, 而不是直接重寫整個倒排索引.
1,添加document的流程
① 將數據寫入buffer(內存緩沖區);
② 執行commit操作: buffer空間被占滿, 其中的數據將作為新的 index segment 被commit到文件系統的cache(緩存)中;
③ cache中的index segment通過fsync強制flush到系統的磁盤上;
④ 寫入磁盤的所有segment將被記錄到commit point(提交點)中, 並寫入磁盤;
④ 新的index segment被打開, 以備外部檢索使用;
⑤ 清空當前buffer緩沖區, 等待接收新的文檔.
indexing buffer優化說明如下:
(a)
fsync是一個Unix系統調用函數, 用來將內存緩沖區buffer中的數據存儲到文件系統. 這里作了優化, 是指將文件緩存cache中的所有segment刷新到磁盤的操作.(b) 修改index_buffer_size 的設置,可以設置成百分數,也可設置成具體的大小,大小可根據集群的規模做不同的設置測試。indices.memory.index_buffer_size:10%(默認,可優化30%寫入配置文件)
(c) 每個Shard都有一個提交點(commit point), 其中保存了當前Shard成功寫入磁盤的所有segment.
2,優化寫入流程 - 實現近實時搜索
(1) 現有流程的問題:
插入的新文檔必須等待fsync操作將segment強制寫入磁盤后, 才可以提供搜索.而 fsync操作的代價很大, 使得搜索不夠實時.
(2) 改進寫入流程:
① 將數據寫入buffer(內存緩沖區);
② 不等buffer空間被占滿, 而是每隔一定時間(默認1s), 其中的數據就作為新的index segment被commit到文件系統的cache(緩存)中;
③ index segment 一旦被寫入cache(緩存), 就立即打開該segment供搜索使用;
④ 清空當前buffer緩沖區, 等待接收新的文檔.
優化的地方: 過程②和過程③:
segment進入操作系統的緩存中就可以提供搜索, 這個寫入和打開新segment的輕量過程被稱為
refresh.
優化refresh的間隔:
Elasticsearch中, 每個Shard每秒都會自動refresh一次, 所以ES是近實時的, 數據插入到可以被搜索的間隔默認是1秒
(1) 手動refresh —— 測試時使用, 正式生產中請減少使用:
# 刷新所有索引:
POST _refresh
# 刷新某一個索引:
POST index/_refresh
(2) 手動設置refresh間隔 —— 若要優化索引速度, 而不注重實時性, 可以降低刷新頻率:
# 在已有索引中設置, 間隔10秒: PUT /_all/_settings { "index":{ "refresh_interval":"120s" } }
(3) 當你在生產環境中建立一個大的新索引時, 可以先關閉自動刷新, 要開始使用該索引時再改回來:
# 關閉自動刷新: PUT /_all/_settings { "refresh_interval": -1 }
(4)調小索引副本數,通過增大refresh間隔周期,同時不設置副本來提高寫性能。
api執行方式:
PUT /_all/_settings {"number_of_replicas":"0"}
命令接口執行方式:
curl -XPUT '192.168.115.98:9200/_all/_settings' -H 'Content-Type: application/json' -d '{"index":{"refresh_interval":"1200s","number_of_replicas":"0"}}'
開啟x-pack用戶密碼后執行方式:
curl -XPUT '192.168.115.98:9200/_all/_settings' -uelastic:Ericss0n -H 'Content-Type: application/json' -d '{"index":{"refresh_interval":"120s","number_of_replicas":"0"}}'
3,優化寫入流程 - 實現持久化變更
Elasticsearch通過事務日志(translog)來防止數據的丟失 —— durability持久化.
(1)文檔持久化到磁盤的流程
① 索引數據在寫入內存buffer(緩沖區)的同時, 也寫入到translog日志文件中;
② 每隔refresh_interval的時間就執行一次refresh:
(a) 將buffer中的數據作為新的 index segment, 刷到文件系統的cache(緩存)中;
(b) index segment一旦被寫入文件cache(緩存), 就立即打開該segment供搜索使用;
③ 清空當前內存buffer(緩沖區), 等待接收新的文檔;
④ 重復①~③, translog文件中的數據不斷增加;
⑤ 每隔一定時間(默認30分鍾), 或者當translog文件達到一定大小時, 發生flush操作, 並執行一次全量提交:
(a) 將此時內存buffer(緩沖區)中的所有數據寫入一個新的segment, 並commit到文件系統的cache中;
(b) 打開這個新的segment, 供搜索使用;
(c) 清空當前的內存buffer(緩沖區);
(d) 將translog文件中的所有segment通過
fsync強制刷到磁盤上;(e) 將此次寫入磁盤的所有segment記錄到commit point中, 並寫入磁盤;
(f) 刪除當前translog, 創建新的translog接收下一波創建請求.
4,基於translog和commit point的數據恢復
(1) 關於translog的配置:
flush操作 = 將translog中的記錄刷到磁盤上 + 更新commit point信息 + 清空translog文件.
Elasticsearch默認: 每隔30分鍾就flush一次;
或者: 當translog文件的大小達到上限(默認為512MB)時主動觸發flush.
相關配置為:
# 發生多少次操作(累計多少條數據)后進行一次flush, 默認是unlimited: index.translog.flush_threshold_ops # 當translog的大小達到此預設值時, 執行一次flush操作, 默認是512MB: index.translog.flush_threshold_size # 每隔多長時間執行一次flush操作, 默認是30min: index.translog.flush_threshold_period # 檢查translog、並執行一次flush操作的間隔. 默認是5s: ES會在5-10s之間進行一次操作: index.translog.sync_interval
(2) 數據的故障恢復:
① 增刪改操作成功的標志: segment被成功刷新到Primary Shard和其對應的Replica Shard的磁盤上, 對應的操作才算成功.
② translog文件中存儲了上一次flush(即上一個commit point)到當前時間的所有數據的變更記錄. —— 即translog中存儲的是還沒有被刷到磁盤的所有最新變更記錄.
③ ES發生故障, 或重啟ES時, 將根據磁盤中的commit point去加載已經寫入磁盤的segment, 並重做translog文件中的所有操作, 從而保證數據的一致性.
(3) 異步刷新translog:
為了保證不丟失數據, 就要保護translog文件的安全:
Elasticsearch 2.0之后, 每次寫請求(如index、delete、update、bulk等)完成時, 都會觸發
fsync將translog中的segment刷到磁盤, 然后才會返回200 OK的響應;或者: 默認每隔5s就將translog中的數據通過
fsync強制刷新到磁盤.
—— 提高數據安全性的同時, 降低了一點性能.
==> 頻繁地執行fsync操作, 可能會產生阻塞導致部分操作耗時較久. 如果允許部分數據丟失, 可設置異步刷新translog來提高效率.優化如下:
PUT /_all/_settings { "index.translog.durability": "async", "index.translog.flush_threshold_size":"1024mb", "index.translog.sync_interval": "120s" }
命令接口執行方式:
curl -XPUT '192.168.115.98:9200/_all/_settings' -H 'Content-Type: application/json' -d '{"index":{"translog.durability": "async","translog.flush_threshold_size":"1024mb","translog.sync_interval": "120s"}}'
5,優化寫入流程 - 實現海量segment文件的歸並
由上述近實時性搜索的描述, 可知ES默認每秒都會產生一個新的segment文件, 而每次搜索時都要遍歷所有的segment, 這非常影響搜索性能.
為解決這一問題, ES會對這些零散的segment進行merge(歸並)操作, 盡量讓索引中只保有少量的、體積較大的segment文件.
這個過程由獨立的merge線程負責, 不會影響新segment的產生.
同時, 在merge段文件(segment)的過程中, 被標記為deleted的document也會被徹底物理刪除.
1,merge操作的流程
① 選擇一些有相似大小的segment, merge成一個大的segment;
② 將新的segment刷新到磁盤上;
③ 更新commit文件: 寫一個新的commit point, 包括了新的segment, 並刪除舊的segment;
④ 打開新的segment, 完成搜索請求的轉移;
⑤ 刪除舊的小segment.
2,優化merge的配置項
segment的歸並是一個非常消耗系統CPU和磁盤IO資源的任務, 所以ES對歸並線程提供了限速機制, 確保這個任務不會過分影響到其他任務.
segment合並;索引節點粒度配置,segment默認最小值2M,不過有時候合並會拖累寫入速率
PUT /_all/_settings { "index.merge.policy.floor_segment":"10mb" }
(1) 歸並線程的數目:
推薦設置為CPU核心數的一半, 如果磁盤性能較差, 可以適當降低配置, 避免發生磁盤IO堵塞,所以我們需要降低每個索引並發訪問磁盤的線程數。這個設置允許 max_thread_count + 2 個線程同時進行磁盤操作,也就是設置為 1 允許三個線程。
PUT /_all/_settings { "index.merge.scheduler.max_thread_count" : "1" }
(2) 其他策略:
# 優先歸並小於此值的segment, 默認是2MB:
index.merge.policy.floor_segment
# 一次最多歸並多少個segment, 默認是10個:
index.merge.policy.max_merge_at_once
#如果堆棧經常有很多merge,則可以調整配置,默認是10個,其應該大於等於index.merge.policy.max_merge_at_once。
index.merge.policy.segments_per_tier
# 一次直接歸並多少個segment, 默認是30個
index.merge.policy.max_merge_at_once_explicit
# 大於此值的segment不參與歸並, 默認是5GB. optimize操作不受影響,可以考慮適當降低此值
index.merge.policy.max_merged_segment
命令接口執行方式:
curl -XPUT '192.168.115.98:9200/_all/_settings' -H 'Content-Type: application/json' -d '{"index":{"merge.scheduler.max_thread_count":"1","merge.policy.floor_segment":"10mb","merge.policy.segments_per_tier":"20"}}'
五,Cache的設置及使用:
a) QueryCache: ES查詢的時候,使用filter查詢會使用query cache, 如果業務場景中的過濾查詢比較多,建議將querycache設置大一些,以提高查詢速度。
indices.queries.cache.size: 10%(默認),可設置成百分比,也可設置成具體值,如256mb。(可寫入配置文件)
當然也可以禁用查詢緩存(默認是開啟), 通過index.queries.cache.enabled:false設置。
b) FieldDataCache: 在聚類或排序時,field data cache會使用頻繁,因此,設置字段數據緩存的大小,在聚類或排序場景較多的情形下很有必要,可通過indices.fielddata.cache.size:30% 或具體值10GB來設置。但是如果場景或數據變更比較頻繁,設置cache並不是好的做法,因為緩存加載的開銷也是特別大的。
c) ShardRequestCache: 查詢請求發起后,每個分片會將結果返回給協調節點(Coordinating Node), 由協調節點將結果整合。
如果有需求,可以設置開啟; 通過設置index.requests.cache.enable: true來開啟。
不過,shard request cache只緩存hits.total, aggregations, suggestions類型的數據,並不會緩存hits的內容。也可以通過設置indices.requests.cache.size: 1%(默認)來控制緩存空間大小。
六,查看所有的索引設置
GET /_all/_settings
注意:以上index索引優化,ES2.0后不能直接寫入配置文件所以可以通過api調用或者用shell腳本添加crontab做任務定時優化索引
