ELasticSearch分片原理


  Elasticsearch數據存儲在分片中,然後分片分配到集羣中的節點上。當集羣擴容或縮小,Elasticsearch 將會自動在節點間遷移分片,以使集羣保持平衡,當集羣中部分節點停止服務,整個集羣不受影響。一個分片是一個最小的工作單元,它只是保存了索引中所有數據的一部分。
  默認情況下,一個索引被分配 5 個主分片(在Elasticsearch7中默認僅分配一個主分片),可以使用以下命令配置主分片和副本分片個數。

PUT /zijie 
{
    "settings": {
        "number_of_shards": 3,
        "number_of_replicas": 1
    }
}

主分片

  主分片的數量決定了索引最多能存儲多少數據,主分片過小會在索引增長較快的時候,集羣無法通過增加節點實現對這個索引的數據擴展;主分片數過大會導致單個shard容量很小,引發一個節點上過多分片,影響性能。

  在一個多分片的索引中寫入數據時,通過路由來確定具體寫入哪一個分片中,大致路由過程如下:

shard = hash(routing) % number_of_primary_shards

  routing 是一個可變值,默認是文檔的 _id ,也可以設置成一個自定義的值。routing 通過 hash 函數生成一個數字,然後這個數字再除以 number_of_primary_shards (主分片的數量)後得到餘數 。這個在 0 到 number_of_primary_shards 之間的餘數,就是所尋求的文檔所在分片的位置。

  如果主分片數量發生變化,那麼所有之前路由的值都會無效,文檔就無法被檢索,所以Elasticsearch不支持擴展主分片,除非重建索引。

副本分片

  副本分片是主分片的一個副本,可以防止硬件故障導致的數據丟失,同時可以提供讀請求,副本分片設置過多,會降低集羣整體寫入性能。

  每個主分片應該至少有一個副本分片,當主分片異常時,副本可以promote爲主節點。主分片和對應的副本分片是不會在同一個節點上的,所以副本分片數的最大值是 n -1(其中 n 爲節點數)。

  當索引創建完成的時候,主分片的數量就固定了,但是副本分片的數量可以隨時調整,根據需求擴大或者縮小規模。

PUT /zijie/_settings 
{
    "number_of_replicas": 2
}

  主分片或者副本分片都可以處理讀請求——搜索或文檔檢索,所以數據的冗餘越多,能處理的搜索吞吐量就越大。

節點分類

  • Coordinating Node-路由請求
      路由請求,所有節點默認都是Coordinating Node,通過將其他類型設置成False,使其變成Coordinating Node節點。

  • Data Node-保存數據
      節點啓動後,默認就是數據節點,可以設置成node.data: false 禁止。通過增加數據節點,可以解決數據水平擴展和解決數據單點的問題。

  • Master Node-處理請求
      處理創建、刪除索引等請求、決定分片分到那個節點,維護並更新Cluster 狀態。在每一個節點上都保存了集羣的狀態信息。但是,只有Master節點上才能修改集羣狀態的信息,並負責同步給其他節點。

一、 分片內部原理

1.1 文檔可被搜索

  Elasticsearch作爲搜索引擎必須解決的是如何使文本可被搜索。 傳統數據庫每個字段存儲單個值,但這對全文檢索並不夠。對於搜索引擎來講,文本字段中的每個單詞需要被搜索,這對數據庫意味着需要單個字段有索引多值(這裏指單詞)的能力。

  ELasticsearch 通過倒排索引實現文檔可被搜索的功能。倒排索引簡單來講就是關鍵詞到文檔的映射。

倒排索引有以下優勢:

  • 不需要鎖。因爲倒排索引不需要更新,就不需要擔心併發修改,也就不需要鎖。
  • 一旦索引被讀入內核的文件系統緩存,便會留在哪裏,由於其不變性,只要文件系統緩存中還有足夠的空間,那麼大部分讀請求會直接請求內存,而不會命中磁盤。這提供了很大的性能提升。
  • 其它緩存(query cache、request cache、fielddata cache),在索引的生命週期內始終有效。它們不需要在每次數據改變時被重建,因爲數據不會變化。
  • 寫入單個大的倒排索引,可以壓縮數據,較少磁盤 IO 和需要緩存索引的內存大小。

  倒排索引也有它的缺點:

  • 倒排索引是不可變的,不可變意味着新增和修改文檔內容,需要重建整個索引,頻繁的重建會引起大量的消耗(CPU、IO)。

1.2 動態更新索引

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

  Elasticsearch每個segment本身都是一個倒排索引, 但索引在除了表示所有段的集合外,還增加了提交點的概念(一個列出了所有已知段的文件)。

在這裏插入圖片描述
段寫入磁盤的流程如下:

1、新的文檔首先寫入內存區的索引緩存

2、緩存中的內容不時被提交:

  • 一個新的段(額外的倒排索引)寫入磁盤。

  • 新的提交點寫入磁盤,包括新段的名稱

  • 所有寫操作等待文件系統緩存同步到磁盤,確保被寫入。

3、新的段被打開,它包含的文檔可以被檢索

4、內存中的緩存被清除,等待接受新的文檔。

A Lucene index with new documents in the in-memory buffer, ready to commit
在這裏插入圖片描述

刪除和更新

  段是不可改變的,所以既不能從把文檔從舊的段中移除,也不能修改舊的段來進行反映文檔的更新。 取而代之的是,每個提交點會包含一個 .del 文件,文件中會列出這些被刪除文檔的段信息。

  當一個文檔被 “刪除” 時,它實際上只是在 .del 文件中被標記刪除。一個被標記刪除的文檔仍然可以被查詢匹配到, 但它會在最終結果被返回前從結果集中移除。

  文檔更新也是類似的操作方式:當一個文檔被更新時,舊版本文檔被標記刪除,文檔的新版本被索引到一個新的段中。 可能兩個版本的文檔都會被一個查詢匹配到,但被刪除的那個舊版本文檔在結果集返回前就已經被移除。

1.3 近實時搜索

  在動態更新機制中,新增加的文檔在沒有落到磁盤之前是不可檢索的,所以新增加的文檔是需要延遲一段時間纔可以被搜索到,磁盤會是瓶頸(fsync消耗比較大,不能在每個文檔被索引時就觸發)。

  在Elasticsearch和磁盤之間是文件系統緩存。 像之前描述的一樣, 在內存索引緩衝區中的文檔會被寫入到一個新的段中。 但是新段會被先寫入到文件系統緩存,這一步代價會比較低,稍後再被刷新到磁盤(這一步代價比較高)。不過只要文件已經在緩存中, 就可以像其它文件一樣被打開和讀取了。Elasticsearch允許新段被寫入和打開(使其包含的文檔在未進行一次完整提交時便對搜索可見)。這種方式比進行一次提交代價要小得多,並且在不影響性能的前提下可以被頻繁地執行。

A Lucene index with new documents in the in-memory buffer
在這裏插入圖片描述
refresh

  在 Elasticsearch 中,將Index Buffer寫入Segment的過程叫Refresh。 默認情況下每個分片會每秒自動刷新一次。這就是爲什麼我們說 Elasticsearch 是近實時搜索: 文檔的變化並不是立即對搜索可見,但會在一秒之內變爲可見。

  這可能會造成困惑: 因爲檢索了一個文檔然後嘗試搜索它,但卻沒有搜到。要讀取最新數據的解決辦法是用 refresh API 執行一次手動刷新:

//More Actions刷新(Refresh)所有的索引
POST /_refresh 
//只刷新(Refresh) `blogs` 索引
POST /blogs/_refresh 

  儘管刷新是比提交輕量很多的操作,它還是會有性能開銷。當測試的時候, 手動刷新很有用,但是不要在生產環境下每次索引一個文檔都去手動刷新。 相反,應用需要意識到Elasticsearch 的近實時的性質,並接受它的不足。並不是所有的情況都需要每秒刷新。可能正在使用 Elasticsearch 索引大量的日誌文件, 可能想優化索引速度而不是近實時搜索, 可以通過設置 refresh_interval , 降低每個索引的刷新頻率:

//每30秒刷新 `my_logs` 索引
PUT /my_logs
{
  "settings": {
    "refresh_interval": "30s" 
  }
}

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

//關閉自動刷新
PUT /my_logs/_settings
{ "refresh_interval": -1 } 
//每秒自動刷新
PUT /my_logs/_settings
{ "refresh_interval": "1s" } 

refresh_interval 需要一個持續時間值, 例如 1s2m。 只是1的話表示的是 1毫秒,可能會導致集羣癱瘓。

總結

  • 將Index Buffer寫入Segment的過程叫Refresh。Refresh不執行fsync操作

  • Refresh頻率:默認1秒發生一次,可通過index.refresh_interval配置。Refersh後,數據就可以被搜索到了。這也是爲什麼Elasticsearch是近實時查詢的原因

  • 如果系統有大量的數據寫入,那就會產生很多Segment

  • Index Buffer被佔滿時,會觸發Refresh,默認值是JVM的10%

爲了減少消耗,提升性能,引用了文件系統緩存但是沒有fsync同步文件到磁盤,如果出現異常情況,數據的安全性無法保證。

1.4 持久化變更

  如果沒有用 fsync 把數據從文件系統緩存刷(flush)到硬盤,那就不能保證數據在斷電甚至是程序正常退出之後依然存在。爲了保證 Elasticsearch 的可靠性,需要確保數據變化被持久化到磁盤。

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

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

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

  1. 一個文檔被索引之後,就會被添加到內存緩衝區,並且追加到了 translog 。在這裏插入圖片描述

  2. 分片刷新(refresh)完成後, 緩存被清空但是事務日誌不會,分片每秒被刷新(refresh)一次:

    • 這些在內存緩衝區的文檔被寫入到一個新的段中,且沒有進行 fsync 操作。
    • 這個段被打開,使其可被搜索。
    • 內存緩衝區被清空。

    After a refresh, the buffer is cleared but the transaction log is not

  3. 這個進程繼續工作,更多的文檔被添加到內存緩衝區和追加到事務日誌。

在這裏插入圖片描述

  1. 每隔一段時間translog 變得越來越大—索引被刷新(flush);一個新的 translog 被創建,並且一個全量提交被執行:

    • 所有在內存緩衝區的文檔都被寫入一個新的段。
    • 緩衝區被清空。
    • 一個提交點被寫入硬盤。
    • 文件系統緩存通過 fsync 被刷新(flush)。
    • 老的 translog 被刪除。

  translog 提供所有還沒有被刷到磁盤的操作的一個持久化記錄。當 Elasticsearch 啓動的時候,ES會用最近一次的提交點從硬盤中恢復一直的段,並且從translog中恢復所有操作。

  translog 也被用來提供實時 CRUD 。同時事務日誌還來提供實時的CRUD操作,當嘗試用ID進行CRUD時,它會先查日誌最新的改動,來保證獲取到文檔的最新版本。

在這裏插入圖片描述
flush

  執行一個提交併且清除 translog 的行爲在 Elasticsearch 被稱作一次flush。 分片每30分鐘被自動刷新(flush),或者在 translog 太大的時候也會刷新。通常情況下,自動刷新就足夠了。

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

//刷新(flush) `blogs` 索引
POST /blogs/_flush 
//刷新(flush)所有的索引並且並且等待所有刷新在返回前完成
POST /_flush?wait_for_ongoing 

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

總結

ES Flush & Luence Commit

  • 調用Refresh,Index Buffer清空並且Refresh
  • 調用fsync,將緩存中的Segment寫入磁盤
  • 清空Transaction Log
  • 默認30分鐘調用一次
  • Transaction Log滿(默認512M)

Translog 安全性

translog 的目的是保證操作不會丟失。這引出了這個問題: Translog 安全性

  在文件被 fsync 到磁盤前,被寫入的文件在重啓之後就會丟失。默認 translog 是每 5 秒被 fsync 刷新到硬盤, 或者在每次寫請求完成之後執行。這個過程在主分片和複製分片都會發生。這意味着在整個請求被 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" )來避免數據丟失。

總結

  • Segment寫入磁盤的過程相對耗時,藉助文件系統緩存,Refresh時,先將Segment寫入緩存以開放查詢
  • 爲了保證數據不會丟失。所以在Index文檔時,同時寫Transaction Log,高版本開始,Transaction Log默認落盤,每個分片有一個Transaction Log
  • 在ES Refresh 時,Index Buffer被清空,Transaction Log不會被清空

1.5 段合併

  由於自動刷新流程每秒會創建一個新的段 ,這樣會導致短時間內的段數量暴增。而段數目太多會帶來較大的麻煩。 每一個段都會消耗文件句柄、內存和cpu運行週期。更重要的是,每個搜索請求都必須輪流檢查每個段;所以段越多,搜索也就越慢。

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

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

  啓動段合併不需要做任何事。進行索引和搜索時會自動進行。

1、 當索引的時候,刷新(refresh)操作會創建新的段並將段打開以供搜索使用。

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

Two commited segments and one uncommited segment in the process of being merged into a bigger segment

3、合併結束,老的段被刪除說明合併完成時的活動:

  • 新的段被刷新(flush)到了磁盤。 寫入一個包含新段且排除舊的和較小的段的新提交點。
  • 新的段被打開用來搜索。
  • 老的段被刪除。

在這裏插入圖片描述

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

merge

  merge可看做是 強制合併API。它會將一個分片強制合併到 max_num_segments 參數指定大小的段數目。 這樣做的意圖是減少段的數量(通常減少到一個),來提升搜索性能。

  merge不應該被用在一個活躍的索引。後臺合併流程已經可以很好地完成工作。 optimizing 會阻礙這個進程。不要干擾它!

  在特定情況下,使用merge是有用的。例如在日誌這種用例下,每天、每週、每月的日誌被存儲在一個索引中。 老的索引實質上是隻讀的;它們也並不太可能會發生變化。

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

//合併索引中的每個分片爲一個單獨的段
POST /logstash-2014-10/_forcemerge?max_num_segments=1 

  使用 optimize API 觸發段合併的操作不會受到任何資源上的限制。這可能會消耗掉節點上全部的I/O資源, 使其沒有餘裕來處理搜索請求,從而有可能使集羣失去響應。 如果想要對索引執行 optimize,需要先使用分片分配,把索引移到一個安全的節點,再執行。

總結

  • Segment很多,需要被定期被合併

    • 減少Segment/刪除已經刪除的文檔
  • ES和Luence會自動進行Merge操作

    • POST my_index/_forcemerge

1、索引過程中,refresh會創建新的段,並打開它
2、合併過程會在後臺選擇一些小的段(包含已提交和未提交的)合併成大的段,這個過程不會中斷索引和搜索
3、合併後的段會被flush到硬盤,新的提交點會寫入新的段,排除舊的段。新的段打開提供搜索,舊段被刪除
合併段會消耗大量的IO和CPU,ES會限制合併的過程,保證有足夠的資源來進行搜索。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章