elasticsearch document的索引過程分析

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

 

1、預備知識

1.一、索引不可變

看到這篇文章相信你們都知道es是倒排索引,不瞭解也不要緊,在個人另外一篇博文中詳細分析了es的倒排索引機制。在es的索引過程當中爲了知足一下特色,落盤的es索引是不可變的。java

1 不須要鎖。若是歷來不須要更新一個索引,就沒必要擔憂多個程序同時嘗試修改。 
2 一旦索引被讀入文件系統的緩存(譯者:在內存),它就一直在那兒,由於不會改變。只要文件系統緩存有足夠的空間,大部分的讀會直接訪問內存而不是磁盤。這有助於性能提高。
3 在索引的聲明週期內,全部的其餘緩存均可用。它們不須要在每次數據變化了都重建,使文本能夠被搜索由於數據不會變。 寫入單個大的倒排索引,能夠壓縮數據,較少磁盤IO和須要緩存索引的內存大小。 

固然,不可變的索引有它的缺點,首先是它不可變!你不能改變它。若是想要搜索一個新文 檔,必須重建整個索引。這不只嚴重限制了一個索引所能裝下的數據,還有一個索引能夠被更新的頻次。因此es引入了動態索引。數組

 

1.二、動態索引

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

 

 看到這裏若是對分段仍是有點迷惑,不要緊,假如你熟悉java語言,ArrayList這個集合咱們都知道是一個動態數組,他的底層數據結構其實就是數組,咱們都知道數組是不可變的,ArrayList是動過擴容實現的動態數組。在這裏咱們就能夠將commit point理解成ArrayList,segment就是一個個小的數組。而後將其組合成ArrayList。假如你知道Java1.8的ConcurrentHashMap的分段鎖相信你理解這個分段就很容易了。安全

 

1.三、幾個容易混淆的概念

在es中「索引」是分片(shard)的集合,在lucene中「索引」從宏觀上來講就是es中的一個分片,從微觀上來講就是segment的集合。性能優化

「document的索引過程」這句話中的這個「索引」,咱們能夠理解成es爲document簡歷索引的過程。數據結構

 

2、document索引過程

 

文檔被索引的過程如上面所示,大體能夠分爲 內存緩衝區buffer、translog、filesystem cache、系統磁盤這幾個部分,接下來咱們梳理一下這個過程。elasticsearch

階段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.一、近實時化搜索(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.二、合併段——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.三、容災與可靠存儲

沒用 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、 內存緩衝區被清空
四、 重複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-五、 現有的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

 

 

3、更新和刪除

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

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

 

 

 

  參考文獻:

  《elasticsearch-權威指南》

 

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

  原創不易,轉載請註明原文地址:http://www.javashuo.com/article/p-wnpbnayv-cm.html

相關文章
相關標籤/搜索