1. 程式人生 > >elasticsearch document的索引過程分析

elasticsearch document的索引過程分析

elasticsearch專欄:https://www.cnblogs.com/hello-shf/category/1550315.html

 

一、預備知識

1.1、索引不可變

看到這篇文章相信大家都知道es是倒排索引,不瞭解也沒關係,在我的另一篇博文中詳細分析了es的倒排索引機制。在es的索引過程中為了滿足一下特點,落盤的es索引是不可變的。

1 不需要鎖。如果從來不需要更新一個索引,就不必擔心多個程式同時嘗試修改。 
2 一旦索引被讀入檔案系統的快取(譯者:在記憶體),它就一直在那兒,因為不會改變。只要檔案系統快取有足夠的空間,大部分的讀會直接訪問記憶體而不是磁碟。這有助於效能提升。
3 在索引的宣告週期內,所有的其他快取都可用。它們不需要在每次資料變化了都重建,使文字可以被搜尋因為資料不會變。 寫入單個大的倒排索引,可以壓縮資料,較少磁碟IO和需要快取索引的記憶體大小。 

當然,不可變的索引有它的缺點,首先是它不可變!你不能改變它。如果想要搜尋一個新文 檔,必須重建整個索引。這不僅嚴重限制了一個索引所能裝下的資料,還有一個索引可以被更新的頻次。所以es引入了動態索引。

 

1.2、動態索引

下一個需要解決的問題是如何在保持不可變好處的同時更新倒排索引。答案是,使用多個索引。不是重寫整個倒排索引,而是增加額外的索引反映最近的變化。每個倒排索引都可以按順序查詢,從最老的開始,最後把結果聚合。 Elasticsearch底層依賴的Lucene,引入了 per-segment search 的概念。一個段(segment)是有完整功能的倒排索引,但是現在Lucene中的索引指的是段的集合,再加上提交點(commit point,包括所有段的檔案),如圖1所示。新的文件,在被寫入磁碟的段之前,首先寫入記憶體區的索引快取。然後再通過fsync將快取中的段重新整理到磁碟上,該段將被開啟即段落盤之後開始能被檢索。

 

 看到這裡如果對分段還是有點迷惑,沒關係,假如你熟悉java語言,ArrayList這個集合我們都知道是一個動態陣列,他的底層資料結構其實就是陣列,我們都知道陣列是不可變的,ArrayList是動過擴容實現的動態陣列。在這裡我們就可以將commit point理解成ArrayList,segment就是一個個小的陣列。然後將其組合成ArrayList。假如你知道Java1.8的ConcurrentHashMap的分段鎖相信你理解這個分段就很容易了。

 

1.3、幾個容易混淆的概念

在es中“索引”是分片(shard)的集合,在lucene中“索引”從巨集觀上來說就是es中的一個分片,從微觀上來說就是segment的集合。

“document的索引過程”這句話中的這個“索引”,我們可以理解成es為document簡歷索引的過程。

 

二、document索引過程

 

文件被索引的過程如上面所示,大致可以分為 記憶體緩衝區buffer、translog、filesystem cache、系統磁碟這幾個部分,接下來我們梳理一下這個過程。

階段1:

這個階段很簡單,一個document文件第一步會同時被寫進記憶體緩衝區buffer和translog。

階段2:

refresh:記憶體緩衝區的documents每隔一秒會被refresh(重新整理)到filesystem cache中的一個新的segment中,segment就是索引的最小單位,此時segment將會被開啟供檢索。也就是說一旦文件被重新整理到檔案系統快取中,其就能被檢索使用了。這也是es近實時性(NRT)的關鍵。後面會詳細介紹。

階段3:

merge:每秒都會有新的segment生成,這將意味著用不了多久segment的數量就會爆炸,每個段都將十分消耗檔案控制代碼、記憶體、和cpu資源。這將是系統無法忍受的,所以這時,我們急需將零散的segment進行合併。ES通過後臺合併段解決這個問題。小段被合併成大段,再合併成更大的段。然後將新的segment開啟供搜尋,舊的segment刪除。

階段4:

flush:經過階段3合併後新生成的更大的segment將會被flush到系統磁碟上。這樣整個過程就完成了。但是這裡留一個包袱就是flush的時機。在後面介紹translog的時候會介紹。

不要著急,接下來我們將以上步驟拆分開來詳細分析一下。

 

2.1、近實時化搜尋(NRT)

在早起的lucene中,只有當segement被寫入到磁碟,該segment才會被開啟供搜尋,和我們上面所說的當doc被重新整理到filesystem cache中生成新的segment就將會被開啟。

因為 per-segment search 機制,索引和搜尋一個文件之間是有延遲的。新的文件會在幾分鐘內可以搜尋,但是這依然不夠快。磁碟是瓶頸。提交一個新的段到磁碟需要 fsync 操作,確保段被物理地寫入磁碟,即時電源失效也不會丟失資料。但是 fsync 是昂貴的,它不能在每個文件被索引的時就觸發。

所以需要一種更輕量級的方式使新的文件可以被搜尋,這意味這移除 fsync 。

位於Elasticsearch和磁碟間的是檔案系統快取。如前所說,在記憶體索引快取中的文件被寫入新的段,但是新的段首先寫入檔案系統快取,這代價很低,之後會被同步到磁碟,這個代價很大。但是一旦一個檔案被快取,它也可以被開啟和讀取,就像其他檔案一樣。

在es中每隔一秒寫入記憶體緩衝區的文件就會被重新整理到filesystem cache中的新的segment,也就意味著可以被搜尋了。這就是ES的NRT——近實時性搜尋。

簡單介紹一下refresh API

如果你遇到過你新增了doc,但是沒檢索到,很可能是因為還未自動進行refresh,這是你可以嘗試手動重新整理

POST /student/_refresh

 

效能優化

在這裡我們需要知道一點refresh過程是很消耗效能的。如果你的系統對實時性要求不高,可以通過API控制refresh的時間間隔,但是如果你的新系統很要求實時性,那你就忍受它吧。

如果你對系統的實時性要求很低,我們可以調整refresh的時間間隔,調大一點將會在一定程度上提升系統的效能。

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

 

2.2、合併段——merge

通過每秒自動重新整理建立新的段,用不了多久段的數量就爆炸了。有太多的段是一個問題。每個段消費檔案控制代碼,記憶體,cpu資源。更重要的是,每次搜尋請求都需要依次檢查每個段。段越多,查詢越慢。

ES通過後臺合併段解決這個問題。小段被合併成大段,再合併成更大的段。

這是舊的文件從檔案系統刪除的時候。舊的段不會再複製到更大的新段中。 這個過程你不必做什麼。當你在索引和搜尋時ES會自動處理。

我們再來總結一下段合併的過程。

1 選擇一些有相似大小的segment,merge成一個大的segment
2 將新的segment flush到磁碟上去
3 寫一個新的commit point,包括了新的segment,並且排除舊的那些segment
4 將新的segment開啟供搜尋
5 將舊的segment刪除
optimize API與效能優化

optimize API 最好描述為強制合併段API。它強制分片合併段以達到指定 max_num_segments 引數。這是為了減少段的數量(通常為1)達到提高搜尋效能的目的。

在特定的環境下, optimize API 是有用的。典型的場景是記錄日誌,這中情況下日誌是按照每天,周,月存入索引。舊的索引一般是隻可讀的,它們是不可能修改的。 這種情況下,把每個索引的段降至1是有效的。搜尋過程就會用到更少的資源,效能更好:
POST /logstash-2019-10-01/_optimize?max_num_segments=1

 一般場景下儘量不要手動執行,讓它自動預設執行就可以了

 

2.3、容災與可靠儲存

沒用 fsync 同步檔案系統快取到磁碟,我們不能確保電源失效,甚至正常退出應用後,資料的安全。為了ES的可靠性,需要確保變更持久化到磁碟。

我們說過一次全提交同步段到磁碟,寫提交點,這會列出所有的已知的段。在重啟,或重新 開啟索引時,ES使用這次提交點決定哪些段屬於當前的分片。

當我們通過每秒的重新整理獲得近實時的搜尋,我們依然需要定時地執行全提交確保能從失敗中 恢復。但是提交之間的文件怎麼辦?我們也不想丟失它們。

上面doc索引流程的階段1,doc分別被寫入到記憶體緩衝區和translog,然後每秒都將會把記憶體緩衝區的docs重新整理到filesystem cache中的新segment,然後眾多segment會進行不斷的壓縮,小段被合併成大段,再合併成更大的段。每次refresh操作後,記憶體緩衝區的docs被重新整理到filesystem cache中的segemnt中,但是tanslog仍然在持續的增大增多。當translog大到一定程度,將會發生一個commit操作也就是全量提交。

詳細過程如下:

1、 doc寫入記憶體緩衝區和translog日誌檔案
2、 每隔一秒鐘,記憶體緩衝區中的docs被寫入到filesystem cache中新的segment,此時segment被開啟並供檢索使用
3、 記憶體緩衝區被清空
4、 重複1~3,新的segment不斷新增,記憶體緩衝區不斷被清空,而translog中的資料不斷累加
5、 當translog長度達到一定程度的時候,commit操作發生
  5-1、 記憶體緩衝區中的docs被寫入到filesystem cache中新的segment,開啟供檢索使用
  5-2、 記憶體緩衝區被清空
  5-3、 一個commit ponit被寫入磁碟,標明瞭所有的index segment
  5-4、 filesystem cache中的所有index segment file快取資料,被fsync強行刷到磁碟上
  5-5、 現有的translog被清空,建立一個新的translog

 

其實到這裡我們發現fsync還是沒有被捨棄的,但是我們通過動態索引和translog技術減少了其使用頻率,並實現了近實時搜尋。其次通過以上步驟我們發現flush操作包括filesystem cache中的segment通過fsync重新整理到硬碟以及translog的清空兩個過程。es預設每30分鐘進行一次flush操作,但是當translog大到一定程度時也會進行flush操作。

對應過程圖如下

 

 

5-5步驟不難發現只有記憶體緩衝區中的docs全部重新整理到filesystem cache中並fsync到硬碟,translog才會被清除,這樣就保證了資料不會丟失,因為只要translog存在,我們就能根據translog進行資料的恢復。

簡單介紹一下flush API

手動flush如下所示,但是並不建議使用。但是當要重啟或關閉一個索引,flush該索引是很有用的。當ES嘗試恢復或者重新開啟一個索引時,它必須重放所有事務日誌中的操作,所以日誌越小,恢復速度越快。

POST /student/_flush

 

 

三、更新和刪除

前面我們說過es的索引是不可變的,那麼更新和刪除是如何進行的呢?

段是不可變的,所以文件既不能從舊的段中移除,舊的段也不能更新以反映文件最新的版本。相反,每一個提交點包括一個.del檔案,包含了段上已經被刪除的文件。當一個文件被刪除,它實際上只是在.del檔案中被標記為刪除,依然可以匹配查詢,但是最終返回之前會被從結果中刪除。 文件的更新操作是類似的:當一個文件被更新,舊版本的文件被標記為刪除,新版本的文件在新的段中索引。也許該文件的不同版本都會匹配一個查詢,但是更老版本會從結果中刪除。

 

 

 

  參考文獻:

  《elasticsearch-權威指南》

 

  如有錯誤的地方還請留言指正。

  原創不易,轉載請註明原文地址:https://www.cnblogs.com/hello-shf/p/11553317.html