1. 程式人生 > >Elasticsearch 之 commit point | Segment | refresh | flush 索引分片內部原理

Elasticsearch 之 commit point | Segment | refresh | flush 索引分片內部原理

轉載自: http://www.6aiq.com/article/1539308290695

基本概念

Segments in Lucene

眾所周知,Elasticsearch 儲存的基本單元是shard, ES中一個Index 可能分為多個shard, 事實上每個shard 都是一個Lucence 的Index,並且每個Lucence Index 由多個Segment組成, 每個Segment事實上是一些倒排索引的集合, 每次建立一個新的Document, 都會歸屬於一個新的Segment, 而不會去修改原來的Segment; 且每次的文件刪除操作,會僅僅標記Segment中該文件為刪除
狀態, 而不會真正的立馬物理刪除, 所以說ES的index 可以理解為一個抽象的概念。 就像下圖所示:

Commits in Lucene

為了資料安全, 每次的索引變更都最好要立刻刷盤, 所以Commit操作意味著將Segment合併,並寫入磁碟。保證記憶體資料儘量不丟。刷盤是很重的IO操作, 所以為了機器效能和近實時搜尋, 並不會刷盤那麼及時。

Translog

新文件被索引意味著文件會被首先寫入記憶體buffer和translog檔案。每個shard都對應一個translog檔案
9cdea21e54c94f84b942b7a111f48ec1.png

Refresh in Elasticsearch

在elasticsearch中, _refresh 操作預設每秒執行一次, 意味著將記憶體buffer的資料寫入到一個新的Segment中,這個時候索引變成了可被檢索的。
0ce7a70dede942f7a465b528fa9ead99.png

Flush in Elasticsearch

Flush 操作意味著將記憶體buffer的資料全都寫入新的Segments中, 並將記憶體中所有的Segments全部刷盤, 並且清空translog日誌的過程。
30e1d93490a94ed6a8a52b7193c726a8.png

近實時搜尋

提交(Commiting)一個新的段到磁碟需要一個 fsync 來確保段被物理性地寫入磁碟,這樣在斷電的時候就不會丟失資料。 但是 fsync 操作代價很大; 如果每次索引一個文件都去執行一次的話會造成很大的效能問題。

我們需要的是一個更輕量的方式來使一個文件可被搜尋,這意味著 fsync 要從整個過程中被移除。

在Elasticsearch和磁碟之間是檔案系統快取。

像之前描述的一樣, 在記憶體索引緩衝區( 圖 19 “在記憶體緩衝區中包含了新文件的 Lucene 索引” )中的文件會被寫入到一個新的段中( 圖 20 “緩衝區的內容已經被寫入一個可被搜尋的段中,但還沒有進行提交” )。 但是這裡新段會被先寫入到檔案系統快取–這一步代價會比較低,稍後再被重新整理到磁碟–這一步代價比較高。不過只要檔案已經在快取中, 就可以像其它檔案一樣被開啟和讀取了。

圖 19. 在記憶體緩衝區中包含了新文件的 Lucene 索引

Lucene 允許新段被寫入和開啟–使其包含的文件在未進行一次完整提交時便對搜尋可見。 這種方式比進行一次提交代價要小得多,並且在不影響效能的前提下可以被頻繁地執行。

圖 20. 緩衝區的內容已經被寫入一個可被搜尋的段中,但還沒有進行提交

refresh APA

在 Elasticsearch 中,寫入和開啟一個新段的輕量的過程叫做 refresh 預設情況下每個分片會每秒自動重新整理一次。這就是為什麼我們說 Elasticsearch 是 實時搜尋: 文件的變化並不是立即對搜尋可見,但會在一秒之內變為可見。

這些行為可能會對新使用者造成困惑: 他們索引了一個文件然後嘗試搜尋它,但卻沒有搜到。這個問題的解決辦法是用 refresh API 執行一次手動重新整理:

1. POST /_refresh
2. POST /blogs/_refresh

  1. 重新整理(Refresh)所有的索引。
  2. 只重新整理(Refresh) blogs 索引

並不是所有的情況都需要每秒重新整理。可能你正在使用 Elasticsearch 索引大量的日誌檔案, 你可能想優化索引速度而不是近實時搜尋, 可以通過設定 refresh_interval , 降低每個索引的重新整理頻率

PUT /my_logs {  "settings":  {  "refresh_interval":  "30s"  }

refresh_interval 可以在既存索引上進行動態更新。 在生產環境中,當你正在建立一個大的新索引時,可以先關閉自動重新整理,待開始使用該索引時,再把它們調回來:

PUT /my_logs/_settings {  "refresh_interval":  -1  }
PUT /my_logs/_settings {  "refresh_interval":  "1s"  }

持久化變更

如果沒有用 fsync 把資料從檔案系統快取刷(flush)到硬碟,我們不能保證資料在斷電甚至是程式正常退出之後依然存在。為了保證 Elasticsearch 的可靠性,需要確保資料變化被持久化到磁碟。

動態更新索引,我們說一次完整的提交會將段刷到磁碟,並寫入一個包含所有段列表的提交點。Elasticsearch 在啟動或重新開啟一個索引的過程中使用這個提交點來判斷哪些段隸屬於當前分片。

即使通過每秒重新整理(refresh)實現了近實時搜尋,我們仍然需要經常進行完整提交來確保能從失敗中恢復。但在兩次提交之間發生變化的文件怎麼辦?我們也不希望丟失掉這些資料。

Elasticsearch 增加了一個 translog ,或者叫事務日誌,在每一次對 Elasticsearch 進行操作時均進行了日誌記錄。通過 translog ,整個流程看起來是下面這樣:

  1. 一個文件被索引之後,就會被新增到記憶體緩衝區,並且 追加到了 translog ,正如 圖 21 “新的文件被新增到記憶體緩衝區並且被追加到了事務日誌” 描述的一樣。
**圖 21. 新的文件被新增到記憶體緩衝區並且被追加到了事務日誌**



![](http://img.6aiq.com/e/4b2e04139759408fa51376bdcb03e49e.png)
  1. 重新整理(refresh)使分片處於 圖 22 “重新整理(refresh)完成後, 快取被清空但是事務日誌不會” 描述的狀態,分片每秒被重新整理(refresh)一次:

    • 這些在記憶體緩衝區的文件被寫入到一個新的段中,且沒有進行 fsync 操作。
    • 這個段被開啟,使其可被搜尋。
    • 記憶體緩衝區被清空。

    圖 22. 重新整理(refresh)完成後, 快取被清空但是事務日誌不會

  2. 這個程序繼續工作,更多的文件被新增到記憶體緩衝區和追加到事務日誌(見 圖 23 “事務日誌不斷積累文件” )。

    圖 23. 事務日誌不斷積累文件

  3. 每隔一段時間–例如 translog 變得越來越大–索引被重新整理(flush);一個新的 translog 被建立,並且一個全量提交被執行(見 圖 24 “在重新整理(flush)之後,段被全量提交,並且事務日誌被清空”):

    • 所有在記憶體緩衝區的文件都被寫入一個新的段。
    • 緩衝區被清空。
    • 一個提交點被寫入硬碟。
    • 檔案系統快取通過 fsync 被重新整理(flush)。
    • 老的 translog 被刪除。

translog 提供所有還沒有被刷到磁碟的操作的一個持久化紀錄。當 Elasticsearch 啟動的時候, 它會從磁碟中使用最後一個提交點去恢復已知的段,並且會重放 translog 中所有在最後一次提交後發生的變更操作。

translog 也被用來提供實時 CRUD 。當你試著通過ID查詢、更新、刪除一個文件,它會在嘗試從相應的段中檢索之前, 首先檢查 translog 任何最近的變更。這意味著它總是能夠實時地獲取到文件的最新版本。
圖 24. 在重新整理(flush)之後,段被全量提交,並且事務日誌被清空

flush API

這個執行一個提交併且截斷 translog 的行為在 Elasticsearch 被稱作一次 flush分片每30分鐘被自動重新整理(flush),或者在 translog 太大的時候也會重新整理。請檢視 translog 文件 來設定,它可以用來 控制這些閾值:

flush API 可以 被用來執行一個手工的重新整理(flush):

POST /blogs/_flush
POST /_flush?wait_for_ongoin
  1. 重新整理(flush) blogs 索引。
  2. 重新整理(flush)所有的索引並且並且等待所有重新整理在返回前完成。
    你很少需要自己手動執行一個的 flush 操作;通常情況下,自動重新整理就足夠了。

這就是說,在重啟節點或關閉索引之前執行 flush 有益於你的索引。當 Elasticsearch 嘗試恢復或重新開啟一個索引, 它需要重放 translog 中所有的操作,所以如果日誌越短,恢復越快。

Translog 有多安全?
translog 的目的是保證操作不會丟失。這引出了這個問題: Translog 有多安全?
在檔案被 fsync 到磁碟前,被寫入的檔案在重啟之後就會丟失。預設 translog 是每 5 秒被 fsync 重新整理到硬碟, 或者在每次寫請求完成之後執行(e.g. index, delete, update, bulk)。這個過程在主分片和複製分片都會發生。最終, 基本上,這意味著在整個請求被 fsync 到主分片和複製分片的translog之前,你的客戶端不會得到一個 200 OK 響應。
在每次請求後都執行一個 fsync 會帶來一些效能損失,儘管實踐表明這種損失相對較小(特別是bulk匯入,它在一次請求中平攤了大量文件的開銷)。

但是對於一些大容量的偶爾丟失幾秒資料問題也並不嚴重的叢集,使用非同步的 fsync 還是比較有益的。比如,寫入的資料被快取到記憶體中,再每5秒執行一次 fsync

這個行為可以通過設定 durability 引數為 async 來啟用:

PUT /my_index/_settings { 
"index.translog.durability":  "async", 
"index.translog.sync_interval":  "5s" 
}

這個選項可以針對索引單獨設定,並且可以動態進行修改。如果你決定使用非同步 translog 的話,你需要 保證 在發生crash時,丟失掉 sync_interval 時間段的資料也無所謂。請在決定前知曉這個特性。

如果你不確定這個行為的後果,最好是使用預設的引數( "index.translog.durability": "request" )來避免資料丟失。

段合併

由於自動重新整理流程每秒會建立一個新的段 ,這樣會導致短時間內的段數量暴增。而段數目太多會帶來較大的麻煩。 每一個段都會消耗檔案控制代碼、記憶體和cpu執行週期。更重要的是,每個搜尋請求都必須輪流檢查每個段;所以段越多,搜尋也就越慢。

Elasticsearch通過在後臺進行段合併來解決這個問題。小的段被合併到大的段,然後這些大的段再被合併到更大的段。

段合併的時候會將那些舊的已刪除文件 從檔案系統中清除。 被刪除的文件(或被更新文件的舊版本)不會被拷貝到新的大段中。

啟動段合併不需要你做任何事。進行索引和搜尋時會自動進行。這個流程像在 圖 25 “兩個提交了的段和一個未提交的段正在被合併到一個更大的段” 中提到的一樣工作:

1、 當索引的時候,重新整理(refresh)操作會建立新的段並將段開啟以供搜尋使用。

2、 合併程序選擇一小部分大小相似的段,並且在後臺將它們合併到更大的段中。這並不會中斷索引和搜尋。

圖 25. 兩個提交了的段和一個未提交的段正在被合併到一個更大的段

3、 圖 26 “一旦合併結束,老的段被刪除” 說明合併完成時的活動:

  • 新的段被重新整理(flush)到了磁碟。 ** 寫入一個包含新段且排除舊的和較小的段的新提交點。
  • 新的段被開啟用來搜尋。
  • 老的段被刪除。

圖 26. 一旦合併結束,老的段被刪除

合併大的段需要消耗大量的I/O和CPU資源,如果任其發展會影響搜尋效能。Elasticsearch在預設情況下會對合並流程進行資源限制,所以搜尋仍然 有足夠的資源很好地執行。

optimize API

optimize API大可看做是 強制合併 API。它會將一個分片強制合併到 max_num_segments 引數指定大小的段數目。 這樣做的意圖是減少段的數量(通常減少到一個),來提升搜尋效能。
在特定情況下,使用 optimize API 頗有益處。例如在日誌這種用例下,每天、每週、每月的日誌被儲存在一個索引中。 老的索引實質上是隻讀的;它們也並不太可能會發生變化。

在這種情況下,使用optimize優化老的索引,將每一個分片合併為一個單獨的段就很有用了;這樣既可以節省資源,也可以使搜尋更加快速:

POST /logstash-2014-10/_optimize?max_num_segments=1