elasticsearch的數據寫入流程及優化


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做任務定時優化索引 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM