1. 程式人生 > >小白學ES 18 - (原理) ES內部如何寫入document - 優化寫入索引的流程

小白學ES 18 - (原理) ES內部如何寫入document - 優化寫入索引的流程

文章目錄

1 Lucene操作document的流程

Lucene將index資料分為segment(段)進行儲存和管理.

Lucene中倒排索引一旦被建立就是不可變的, 要新增或修改文件, 就需要重建整個索引, 這就對一個index所能包含的資料量, 或index可以被更新的頻率造成了很大的限制.

—— 為了在保留不變性的前提下實現倒排索引的更新, Lucene引入了一個新思路: 使用更多的索引.

—— 增加新的補充索引來反映最新的修改, 而不是直接重寫整個倒排索引. 每一個倒排索引都會被查詢到 —— 從最早的開始 —— 查詢完之後再對結果進行合併.


1.1 新增document的流程

① 將資料寫入buffer(記憶體緩衝區);

② 執行commit操作: buffer空間被佔滿, 其中的資料將作為新的 index segment 被commit到檔案系統的cache(快取)中;

③ cache中的index segment通過fsync強制flush到系統的磁碟上;

④ 寫入磁碟的所有segment將被記錄到commit point(提交點)中, 並寫入磁碟;

④ 新的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 流程的改進

  • 現有流程的問題:

    插入的新文件必須等待fsync將segment強制寫入磁碟後, 才可以提供搜尋.

    —— fsync操作的代價很大, 使得搜尋不夠實時.

  • 改進寫入流程:

    ① 將資料寫入buffer(記憶體緩衝區);

    ② 不等buffer空間被佔滿, 而是每隔一定時間(預設1s), 其中的資料就作為新的index segment被commit到檔案系統的cache(快取)中;

    ③ index segment 一旦被寫入cache(快取), 就立即開啟該segment供搜尋使用;

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

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

優化的地方: 過程②和過程③ —— segment進入作業系統的快取中就可以提供搜尋, 這個寫入和開啟新segment的輕量過程被稱為refresh.

2.2 設定refresh的間隔

Elasticsearch中, 每個Shard每秒都會自動refresh一次, 所以ES是近實時的, 資料插入到可以被搜尋的間隔預設是1秒.

  • 手動refresh —— 測試時使用, 正式生產中請減少使用:

    # 重新整理所有索引:
    POST _refresh
    # 重新整理某一個索引: 
    POST employee/_refresh
    
  • 手動設定refresh間隔 —— 若要優化索引速度, 而不注重實時性, 可以降低重新整理頻率:

    # 建立索引時設定, 間隔1分鐘: 
    PUT employee
    {
        "settings": {
            "refresh_interval": "1m"
        }
    }
    # 在已有索引中設定, 間隔10: 
    PUT employee/_settings
    {
        "refresh_interval": "10s"
    }
    
  • 當你在生產環境中建立一個大的新索引時, 可以先關閉自動重新整理, 要開始使用該索引時再調整回來:

    # 關閉自動重新整理: 
    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, 供搜尋使用;

© 清空當前緩衝區buffer;

(d) 將translog檔案中的所有segment通過fsync強制刷到磁碟上;

(e) 將此次寫入磁碟的所有segment記錄到commit point中, 並寫入磁碟;

(f) 刪除當前translog, 建立新的translog接收下一波建立請求.

  • 擴充套件:

    translog也可以被用來提供實時CRUD. 當通過id查詢、更新、刪除一個文件時, 在從相應的segment中檢索之前先檢查translog中的最新變化 —— ES總是能夠實時地獲取到文件的最新版本.

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過程中, 被標記為deleted的document也會被徹底物理刪除.

4.2 merge操作的流程

① 選擇一些有相似大小的segment, merge成一個大的segment;
② 將新的segment重新整理到磁碟上;
③ 更新commit檔案: 寫一個新的commit point, 包括了新的segment, 並刪除舊的segment;

④ 開啟新的segment, 完成搜尋請求的轉移;

⑤ 刪除舊的小segment.

4.3 優化merge的配置項

segment的歸併是一個非常消耗系統CPU和磁碟IO資源的任務, 所以ES對歸併執行緒提供了限速機制, 確保這個任務不會過分影響到其他任務.

  • 歸併執行緒的速度限制:

    限速配置 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": {}
    }
    
  • 歸併執行緒的數目:

    推薦設定為CPU核心數的一半, 如果磁碟效能較差, 可以適當降低配置, 避免發生磁碟IO堵塞:

    PUT employee/_settings
    {
        "index.merge.scheduler.max_thread_count" : 8
    }
    
  • 其他策略:

    # 優先歸併小於此值的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, 節約資源, 還能提高搜尋效率.


參考資料

http://blog.51cto.com/tchuairen/1861603

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

版權宣告

作者: ma_shoufeng(馬瘦風)

出處: CSDN 馬瘦風的部落格

您的支援是對博主的極大鼓勵, 感謝您的閱讀.

本文版權歸博主所有, 歡迎轉載, 但未經博主同意必須保留此段宣告, 且在文章頁面明顯位置給出原文連結, 否則博主保留追究相關人員法律責任的權利.