Elasticsearch基礎整理-Elasticsearch Lucene 數據寫入原理

ES基礎

數據模型

邏輯概念

ES本身是schema less的,有比較特殊的字段需要通過Mapping設置一下,每個數據點就是一行數據Document,ES數據分類通過Index這層完成的
Elassticsearch的基礎概念-數據模型,如上圖把ES數據模型概念和傳統數據庫做了對比。

  • index 對應db 庫database庫
  • type 對應db 表table表(廢棄)
  • doc 對應db 行 row
  • field 對應db 字段 column

物理存儲

首先分爲兩層,一個是ES,下面是Lucene,一個ES集羣有多個node組成的。一個ES實例會承載一些Shard,多個shard會落到不同機器上面,P1和P2是兩個不同的Shard,並且shard有主從的概念,對於ES每個Shard落地下來是一個Lucene Index。

節點(Node)

單個實例的ES主機稱爲節點,它是集羣的一個成員,可以存儲數據、參與集羣索引及搜索操作。類似於集羣,節點靠其名稱進行標識,默認爲啓動時自動生成的隨機Marvel字符名稱。用戶可以按需要自定義任何希望使用的名稱,但出於管理的目的,此名稱應該儘可能有較好的識別性。節點通過爲其配置的ES集羣名稱確定其所要加入的集羣。

分片(Shard)和副本(Replica)

ES的“分片(shard)”機制可將一個索引內部的數據分佈地存儲於多個節點,它通過將一個索引切分爲多個底層物理的Lucene索引完成索引數據的分割存儲功能,這每一個物理的Lucene索引稱爲一個分片(shard)。每個分片其內部都是一個全功能且獨立的索引,因此可由集羣中的任何主機存儲。創建索引時,用戶可指定其分片的數量,默認數量爲5個。
Shard有兩種類型:primary和replica,即主shard及副本shard。Primary shard用於文檔存儲,每個新的索引會自動創建5個Primary(最新版改成1個了) shard,當然此數量可在索引創建之前通過配置自行定義,不過,一旦創建完成,其Primary shard的數量將不可更改。Replica shard是Primary Shard的副本,用於冗餘數據及提高搜索性能。每個Primary shard默認配置了一個Replica shard,但也可以配置多個,且其數量可動態更改。ES會根據需要自動增加或減少這些Replica shard的數量。

ES集羣可由多個節點組成,各Shard分佈式地存儲於這些節點上。

ES可自動在節點間按需要移動shard,例如增加節點或節點故障時。簡而言之,分片實現了集羣的分佈式存儲,而副本實現了其分佈式處理及冗餘功能。

數據持久化

原理部分

一、Elasticsearch & Lucene 是什麼

509099-20190815164638126-1924066651.jpg

什麼是 Elasticsearch ? Elasticsearch 是一個基於 Apache Lucene(TM) 的開源搜索引擎。

那 Lucene 是什麼? 無論在開源還是專有領域,Lucene 可以被認爲是迄今爲止最先進、性能最好的、功能最全的搜索引擎庫,並通過簡單的 RESTful API 來隱藏 Lucene 的複雜性,從而讓全文搜索變得簡單。

Elasticsearch 不僅僅是 Lucene 和全文搜索,我們還能這樣去描述它:

  • 分佈式的實時文件存儲,每個字段都被索引並可被搜索
  • 分佈式的實時分析搜索引擎
  • 可以擴展到上百臺服務器,處理 PB 級結構化或非結構化數據

二、Elasticsearch & Lucene 的關係

就像很多業務系統是基於 Spring 實現一樣,Elasticsearch 和 Lucene 的關係很簡單:Elasticsearch 是基於 Lucene 實現的。ES 基於底層這些包,然後進行了擴展,提供了更多的更豐富的查詢語句,並且通過 RESTful API 可以更方便地與底層交互。類似 ES 還有 Solr 也是基於 Lucene 實現的。

在應用開發中,用 Elasticsearch 會很簡單。但是如果你直接用 Lucene,會有大量的集成工作。

因此,入門 ES 的同學,稍微瞭解下 Lucene 即可。如果往高級走,還是需要學習 Lucene 底層的原理。因爲倒排索引、打分機制、全文檢索原理、分詞原理等等,這些都是不會過時的技術。

三、新文檔寫入流程

3.1 數據模型

509099-20190815164639539-1141496226.jpg

  • 一個 ES Index (索引,比如商品搜索索引、訂單搜索索引)集羣下,有多個 Node (節點)組成。每個節點就是 ES 的實例。
  • 每個節點上會有多個 shard (分片), P1 P2 是主分片 R1 R2 是副本分片
  • 每個分片上對應着就是一個 Lucene Index(底層索引文件)
  • Lucene Index 是一個統稱。由多個 Segment (段文件,就是倒排索引)組成。每個段文件存儲着就是 Doc 文檔。

3.2 Lucene Index

509099-20190815164640016-1655925172.jpg

lucene 中,單個倒排索引文件稱爲 segment。其中有一個文件,記錄了所有 segments 的信息,稱爲 commit point:

  • 文檔 create 新寫入時,會生成新的 segment。同樣會記錄到 commit point 裏面
  • 文檔查詢,會查詢所有的 segments
  • 當一個段存在文檔被刪除,會維護該信息在 .liv 文件裏面

3.3 新文檔寫入流程

新文檔創建或者更新時,進行如下流程:

更新不會修改原來的 segment,更新和創建操作都會生成新的一個 segment。數據哪裏來呢?先會存在內存的 bugger 中,然後持久化到 segment 。

數據持久化步驟如下:write -> refresh -> flush -> merge

3.3.1 write 過程

file

一個新文檔過來,會存儲在 in-memory buffer 內存緩存區中,順便會記錄 Translog。

這時候數據還沒到 segment ,是搜不到這個新文檔的。數據只有被 refresh 後,纔可以被搜索到。那麼 講下 refresh 過程

3.3.2 refresh 過程

509099-20190815164641273-2053589230.jpg

refresh 默認 1 秒鐘,執行一次上圖流程。ES 是支持修改這個值的,通過 index.refresh_interval 設置 refresh (沖刷)間隔時間。refresh 流程大致如下:

  • in-memory buffer 中的文檔寫入到新的 segment 中,但 segment 是存儲在文件系統的緩存中。此時文檔可以被搜索到
  • 最後清空 in-memory buffer。注意: Translog 沒有被清空,爲了將 segment 數據寫到磁盤

文檔經過 refresh 後, segment 暫時寫到文件系統緩存,這樣避免了性能 IO 操作,又可以使文檔搜索到。refresh 默認 1 秒執行一次,性能損耗太大。一般建議稍微延長這個 refresh 時間間隔,比如 5 s。因此,ES 其實就是準實時,達不到真正的實時。

3.3.3 flush 過程

file

上個過程中 segment 在文件系統緩存中,會有意外故障文檔丟失。那麼,爲了保證文檔不會丟失,需要將文檔寫入磁盤。那麼文檔從文件緩存寫入磁盤的過程就是 flush。寫入次怕後,清空 translog。

translog 作用很大:

  • 保證文件緩存中的文檔不丟失
  • 系統重啓時,從 translog 中恢復
  • 新的 segment 收錄到 commit point 中

具體可以看官方文檔:https://www.elastic.co/guide/en/elasticsearch/reference/7.3/indices-flush.html

3.3.4 merge 過程

file

file

上面幾個步驟,可見 segment 會越來越多,那麼搜索會越來越慢?怎麼處理呢?

通過 merge 過程解決:

  • 就是各個小段文件,合併成一個大段文件。段合併過程
  • 段合併結束,舊的小段文件會被刪除
  • .liv 文件維護的刪除文檔,會通過這個過程進行清除

3.3.5 Translog

Lucence基於節點宕機的考慮,每次寫入都會落盤Translog,類似db binlog,不同的是db binlog 通過expire_logs_days=7 設定過期時間以天爲單位 默認7天
而translog 是每次flush後會清除 可以通過幾個維度的設定清除策略

  • index.translog.flush_threshold_ops,執行多少次操作後執行一次flush,默認無限制
  • index.translog.flush_threshold_size,translog的大小超過這個參數後flush,默認512mb
  • index.translog.flush_threshold_period,多長時間強制flush一次,默認30m
  • index.translog.interval,es多久去檢測一次translog是否滿足flush條件

translog日誌提供了一個所有還未被flush到磁盤的操作的持久化記錄。當ES啓動的時候,它會使用最新的commit point從磁盤恢復所有已有的segments,然後將重現所有在translog裏面的操作來添加更新,這些更新發生在最新的一次commit的記錄之後還未被fsync。

translog日誌也可以用來提供實時的CRUD。當你試圖通過文檔ID來讀取、更新、刪除一個文檔時,它會首先檢查translog日誌看看有沒有最新的更新,然後再從響應的segment中獲得文檔。這意味着它每次都會對最新版本的文檔做操作,並且是實時的。
理論上設定好這個過期策略,在flush之前把translog拿到後做雙機房同步或者進一步的消息通知處理,還可以有很大作爲,可行性還有待嘗試。

3.3.6 寫操作

主分片+至少一個副分片全部寫成功後才返回成功

具體流程:

  1. 客戶端向node1發起寫入文檔請求
  2. Node1根據文檔ID(_id字段)計算出該文檔屬於分片shard0,然後將請求路由到Node3 的主分片P0上

路由公式 shard = hash(routing) % number_of_primary_shards

Node3在p0上執行了寫入請求後,如果成功則將請求並行的路由至Node1 Node2它的副本分片R0上,且都成功後Node1再報告至client

wait_for_active_shards 來配置副本分配同步策略 - 設置爲1 表示僅寫完主分片就返回 - 設置爲all 表示等所有副本分片都寫完才能返回 - 設置爲1-number_of_replicas+1之間的數值,比如2個副本分片,有1個寫成功即可返回 timeout 控制集羣異常副本同步分片不可用時候的等待時間

讀操作

一個文檔可以在任意主副分片上讀取

讀取流程:

  1. 客戶端發起讀請求到Node1
  2. Node1根據文檔ID(_id字段)計算出該文檔屬於分片shard0,在所有節點上都有,這次它根據負載均衡將請求路由至Node2
  3. Node2將文檔返回給Node1,Node1將文檔返回給client

更新操作

更新操作其實就是先讀然後寫

更新流程:

  1. 客戶端將更新請求發給Node1
  2. Node1根據文檔ID(_id字段)計算出該文檔屬於分片shard0,而其主分片在Node上,於是將請求路由到Node3
  3. Node3從p0讀取文檔,改變source字段的json內容,然後將修改後的數據在P0重新做索引。如果此時該文檔被其他進程修改,那麼將重新執行3步驟,這個過程如果超過retryon_confilct設置的重試次數,就放棄。
  4. 如果Node3成功更新了文檔,它將並行的將新版本的文檔同步到Node1 Node2的副本分片上重新建立索引,一旦所有的副本報告成功,Node3向被請求的Node1節點返回成功,然後Node1向client返回成功

更新和刪除

由於segments是不變的,所以文檔不能從舊的segments中刪除,也不能在舊的segments中更新來映射一個新的文檔版本。取之的是,每一個提交點都會包含一個.del文件,列舉了哪一個segment的哪一個文檔已經被刪除了。 當一個文檔被”刪除”了,它僅僅是在.del文件裏被標記了一下。被”刪除”的文檔依舊可以被索引到,但是它將會在最終結果返回時被移除掉。

文檔的更新同理:當文檔更新時,舊版本的文檔將會被標記爲刪除,新版本的文檔在新的segment中建立索引。也許新舊版本的文檔都會本檢索到,但是舊版本的文檔會在最終結果返回時被移除。

四、小結

file

如這個圖,ES 寫入原理不難,記住關鍵點即可。

write -> refresh -> flush

  • write:文檔數據到內存緩存,並存到 translog
  • refresh:內存緩存中的文檔數據,到文件緩存中的 segment 。此時可以被搜到
  • flush 是緩存中的 segment 文檔數據寫入到磁盤

寫入的原理告訴我們,考慮的點很多:性能、數據不丟失等等

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