ES 18 - (底層原理) Elasticsearch寫入索引數據的過程 以及優化寫入過程

1 Lucene操做document的流程

Lucene將index數據分爲segment(段)進行存儲和管理.html

Lucene中, 倒排索引一旦被建立就不可改變, 要添加或修改文檔, 就須要重建整個倒排索引, 這就對一個index所能包含的數據量, 或index能夠被更新的頻率形成了很大的限制.java

爲了在保留不變性的前提下實現倒排索引的更新, Lucene引入了一個新思路: 使用更多的索引, 也就是經過增長新的補充索引來反映最新的修改, 而不是直接重寫整個倒排索引.json

—— 這樣就能確保, 從最先的版本開始, 每個倒排索引都會被查詢到, 查詢完以後再對結果進行合併.緩存

1.1 添加document的流程

① 將數據寫入buffer(內存緩衝區);安全

② 執行commit操做: buffer空間被佔滿, 其中的數據將做爲新的 index segment 被commit到文件系統的cache(緩存)中;bash

③ cache中的index segment經過fsync強制flush到系統的磁盤上;服務器

④ 寫入磁盤的全部segment將被記錄到commit point(提交點)中, 並寫入磁盤;curl

④ 新的index segment被打開, 以備外部檢索使用;異步

⑤ 清空當前buffer緩衝區, 等待接收新的文檔.

說明:

(a) fsync是一個Unix系統調用函數, 用來將內存緩衝區buffer中的數據存儲到文件系統. 這裏做了優化, 是指將文件緩存cache中的全部segment刷新到磁盤的操做.

(b) 每一個Shard都有一個提交點(commit point), 其中保存了當前Shard成功寫入磁盤的全部segment.

1.2 刪除document的流程

① 提交刪除操做, 先查詢要刪除的文檔所屬的segment;

② commit point中包含一個.del文件, 記錄哪些segment中的哪些document被標記爲deleted了;

③ 當.del文件中存儲的文檔足夠多時, ES將執行物理刪除操做, 完全清除這些文檔.

  • 在刪除過程當中進行搜索操做:

    依次查詢全部的segment, 取得結果後, 再根據.del文件, 過濾掉標記爲deleted的文檔, 而後返回搜索結果. —— 也就是被標記爲delete的文檔, 依然能夠被查詢到.

  • 在刪除過程當中進行更新操做:

    將舊文檔標記爲deleted, 而後將新的文檔寫入新的index segment中. 執行查詢請求時, 可能會匹配到舊版本的文檔, 但因爲.del文件的存在, 不恰當的文檔將被過濾掉.


2 優化寫入流程 - 實現近實時搜索

2.1 流程的改進思路

(1) 現有流程的問題:

插入的新文檔必須等待fsync操做將segment強制寫入磁盤後, 才能夠提供搜索.而 fsync操做的代價很大, 使得搜索不夠實時.

(2) 改進寫入流程:

① 將數據寫入buffer(內存緩衝區);

② 不等buffer空間被佔滿, 而是每隔必定時間(默認1s), 其中的數據就做爲新的index segment被commit到文件系統的cache(緩存)中;

③ index segment 一旦被寫入cache(緩存), 就當即打開該segment供搜索使用;

④ 清空當前buffer緩衝區, 等待接收新的文檔.

—— 這裏移除了fsync操做, 便於後續流程的優化.

優化的地方: 過程②和過程③:

segment進入操做系統的緩存中就能夠提供搜索, 這個寫入和打開新segment的輕量過程被稱爲refresh.

2.2 設置refresh的間隔

Elasticsearch中, 每一個Shard每秒都會自動refresh一次, 因此ES是近實時的, 數據插入到能夠被搜索的間隔默認是1秒.

(1) 手動refresh —— 測試時使用, 正式生產中請減小使用:

# 刷新全部索引:
POST _refresh
# 刷新某一個索引: 
POST employee/_refresh

(2) 手動設置refresh間隔 —— 若要優化索引速度, 而不注重實時性, 能夠下降刷新頻率:

# 建立索引時設置, 間隔1分鐘: 
PUT employee
{
    "settings": {
        "refresh_interval": "1m"
    }
}
# 在已有索引中設置, 間隔10秒: 
PUT employee/_settings
{
    "refresh_interval": "10s"
}

(3) 當你在生產環境中創建一個大的新索引時, 能夠先關閉自動刷新, 要開始使用該索引時再改回來:

# 關閉自動刷新: 
PUT employee/_settings
{
    "refresh_interval": -1 
} 
# 開啓每秒刷新: 
PUT employee/_settings
{
    "refresh_interval": "1s"
}


3 優化寫入流程 - 實現持久化變動

Elasticsearch經過事務日誌(translog)來防止數據的丟失 —— durability持久化.

3.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接收下一波建立請求.

擴展: translog也能夠被用來提供實時CRUD.

當經過id查詢、更新、刪除一個文檔時, 從segment中檢索以前, 先檢查translog中的最新變化 —— ES老是可以實時地獲取到文檔的最新版本.

共計:3599 個字

3.2 基於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.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 employee/_settings
{
    "index.translog.durability": "async",
    "index.translog.sync_interval": "5s"
}


4 優化寫入流程 - 實現海量segment文件的歸併

4.1 存在的問題

由上述近實時性搜索的描述, 可知ES默認每秒都會產生一個新的segment文件, 而每次搜索時都要遍歷全部的segment, 這很是影響搜索性能.

爲解決這一問題, ES會對這些零散的segment進行merge(歸併)操做, 儘可能讓索引中只保有少許的、體積較大的segment文件.

這個過程由獨立的merge線程負責, 不會影響新segment的產生.

同時, 在merge段文件(segment)的過程當中, 被標記爲deleted的document也會被完全物理刪除.

4.2 merge操做的流程

① 選擇一些有類似大小的segment, merge成一個大的segment;
② 將新的segment刷新到磁盤上;
③ 更新commit文件: 寫一個新的commit point, 包括了新的segment, 並刪除舊的segment;
④ 打開新的segment, 完成搜索請求的轉移;
⑤ 刪除舊的小segment.

4.3 優化merge的配置項

segment的歸併是一個很是消耗系統CPU和磁盤IO資源的任務, 因此ES對歸併線程提供了限速機制, 確保這個任務不會過度影響到其餘任務.

(1) 歸併線程的速度限制:

限速配置 indices.store.throttle.max_bytes_per_sec的默認值是20MB, 這對寫入量較大、磁盤轉速較高的服務器來講明顯太低.

對ELK Stack應用, 建議將其調大到100MB或更高. 能夠經過API設置, 也能夠寫在配置文件中:

PUT _cluster/settings
{
    "persistent" : {
        "indices.store.throttle.max_bytes_per_sec" : "100mb"
    }
}
// 響應結果以下: 
{
    "acknowledged": true,
    "persistent": {
        "indices": {
            "store": {
                "throttle": {
                    "max_bytes_per_sec": "100mb"
                }
            }
        }
    },
    "transient": {}
}

(2) 歸併線程的數目:

推薦設置爲CPU核心數的一半, 若是磁盤性能較差, 能夠適當下降配置, 避免發生磁盤IO堵塞:

PUT employee/_settings
{
    "index.merge.scheduler.max_thread_count" : 8
}

(3) 其餘策略:

# 優先歸併小於此值的segment, 默認是2MB:
index.merge.policy.floor_segment

# 一次最多歸併多少個segment, 默認是10個: 
index.merge.policy.max_merge_at_once

# 一次直接歸併多少個segment, 默認是30個
index.merge.policy.max_merge_at_once_explicit

# 大於此值的segment不參與歸併, 默認是5GB. optimize操做不受影響
index.merge.policy.max_merged_segment

4.4 optimize接口的使用

segment的默認大小是5GB, 在很是龐大的索引中, 仍然會存在不少segment, 這對文件句柄、內存等資源都是很大的浪費.

但因爲歸併任務很是消耗資源, 因此通常不會選擇加大 index.merge.policy.max_merged_segment 配置, 而是在負載較低的時間段, 經過optimize接口強制歸併segment:

# 強制將segment歸併爲1個大的segment: 
POST employee/_optimize?max_num_segments=1

# 在終端中的操做方法: 
curl -XPOST http://ip:5601/employee/_optimize?max_num_segments=1

optimize線程不會受到任何資源上的限制, 因此不建議對還在寫入數據的熱索引(動態索引)執行這個操做.

實戰建議: 對一些不多發生變化的老索引, 如日誌信息, 能夠將每一個Shard下的segment合併爲一個單獨的segment, 節約資源, 還能提升搜索效率.

參考資料

Elasticsearch 基礎理論 & 配置調優

https://www.elastic.co/guide/cn/elasticsearch/guide/current/making-text-searchable.html

https://www.elastic.co/guide/cn/elasticsearch/guide/current/near-real-time.html

https://www.elastic.co/guide/cn/elasticsearch/guide/current/translog.html

https://www.elastic.co/guide/cn/elasticsearch/guide/current/merge-process.html

版權聲明

做者: 馬瘦風

出處: 博客園 馬瘦風的博客

感謝閱讀, 若是文章有幫助或啓發到你, 點個[好文要頂👆] 或 [推薦👍] 吧😜

本文版權歸博主全部, 歡迎轉載, 但[必須在文章頁面明顯位置給出原文連接], 不然博主保留追究相關人員法律責任的權利.

相關文章
相關標籤/搜索