剖析 Elasticsearch 集羣系列 第一篇 Elasticsearch 的存儲模型和讀寫操作

最近在學習ES,發現《剖析 Elasticsearch 集羣系列》文章寫得挺好,轉載過來記錄下。

原文:https://www.infoq.cn/article/analysis-of-elasticsearch-cluster-part01/

 

剖析 Elasticsearch 集羣系列涵蓋了當今最流行的分佈式搜索引擎 Elasticsearch 的底層架構和原型實例。

本文是這個系列的第一篇,在本文中,我們將討論的 Elasticsearch 的底層存儲模型及 CRUD(創建、讀取、更新和刪除)操作的工作原理。

本系列已經得到原文著者 Ronak Nathani 的授權

Elasticsearch 是當今最流行的分佈式搜索引擎,GitHub、 SalesforceIQ、Netflix 等公司將其用於全文檢索和分析應用。在 Insight,我們用到了 Elasticsearch 的諸多不同功能,比如:

  • 全文檢索
    • 比如找到與搜索詞項 (term) 最相關的維基百科文章。
  • 聚合
    • 比如在廣告網絡中,可視化的搜索詞項的競價直方圖。
  • 地理空間 API
    • 比如在順風車平臺,匹配最近的司機和乘客。

正是因爲 Elasticsearch 如此流行並且就在我們身邊,我決定深入研究一下。本文,我將分享 Elasticsearch 的存儲模型和 CRUD 操作的工作原理。

當我在思考分佈式系統是如何工作時,我腦海裏的圖案是這樣的:

水面以上的是 API,以下的纔是真正的引擎,一切魔幻般的事件都發生在水下。本文所關注的就是水下的部分,我們將關注:

  • Elasticsearch 是主從架構還是無主架構
  • Elasticsearch 的存儲模型是什麼樣的
  • Elasticsearch 是怎麼執行寫操作的
  • Elasticsearch 是怎麼執行讀操作的
  • 如何定義搜索結果的相關性

在我們深入這些概念之前,讓我們熟悉下相關的術語。

1 辨析 Elasticsearch 的索引與 Lucene 的索引

Elasticsearch 中的索引是組織數據的邏輯空間 (就好比數據庫)。1 個 Elasticsearch 的索引有 1 個或者多個分片 (默認是 5 個)。分片對應實際存儲數據的 Lucene 的索引,分片自身就是一個搜索引擎。每個分片有 0 或者多個副本 (默認是 1 個)。Elasticsearch 的索引還包含"type"(就像數據庫中的表),用於邏輯上隔離索引中的數據。在 Elasticsearch 的索引中,給定一個 type,它的所有文檔會擁有相同的屬性 (就像表的 schema)。

圖 a 展示了一個包含 3 個分片的 Elasticsearch 索引,每個分片擁有 1 個副本。這些分片組成了一個 Elasticsearch 索引,每個分片自身是一個 Lucene 索引。圖 b 展示了 Elasticsearch 索引、分片、Lucene 索引和文檔之間的邏輯關係。

對應於關係數據庫術語

Elasticsearch Index == Database 
Types == Tables 
Properties == Schema

現在我們熟悉了 Elasticsearch 世界的術語,接下來讓我們看一下節點有哪些不同的角色。

2 節點類型

一個 Elasticsearch 實例是一個節點,一組節點組成了集羣。Elasticsearch 集羣中的節點可以配置爲 3 種不同的角色:

  • 主節點:控制 Elasticsearch 集羣,負責集羣中的操作,比如創建 / 刪除一個索引,跟蹤集羣中的節點,分配分片到節點。主節點處理集羣的狀態並廣播到其他節點,並接收其他節點的確認響應。

    每個節點都可以通過設定配置文件 elasticsearch.yml 中的node.master屬性爲true(默認) 成爲主節點。

    對於大型的生產集羣來說,推薦使用一個專門的主節點來控制集羣,該節點將不處理任何用戶請求。

  • 數據節點:持有數據和倒排索引。默認情況下,每個節點都可以通過設定配置文件 elasticsearch.yml 中的node.data屬性爲true(默認) 成爲數據節點。如果我們要使用一個專門的主節點,應將其node.data屬性設置爲false

  • 客戶端節點:如果我們將node.master屬性和node.data屬性都設置爲false,那麼該節點就是一個客戶端節點,扮演一個負載均衡的角色,將到來的請求路由到集羣中的各個節點。

 

Elasticsearch 集羣中作爲客戶端接入的節點叫協調節點。協調節點會將客戶端請求路由到集羣中合適的分片上。對於讀請求來說,協調節點每次會選擇不同的分片處理請求,以實現負載均衡。

在我們開始研究發送給協調節點的 CRUD 請求是如何在集羣中傳播並被引擎執行之前,讓我們先來看一下 Elasticsearch 內部是如何存儲數據,以支持全文檢索結果的低延遲服務的。

存儲模型

Elasticsearch 使用了 Apache Lucene ,後者是 Doug Cutting( Apache Hadoop 之父) 使用 Java 開發的全文檢索工具庫,其內部使用的是被稱爲倒排索引的數據結構,其設計是爲全文檢索結果的低延遲提供服務。文檔是 Elasticsearch 的數據單位,對文檔中的詞項進行分詞,並創建去重詞項的有序列表,將詞項與其在文檔中出現的位置列表關聯,便形成了倒排索引。

這和一本書後面的索引非常類似,即書中包含的詞彙與其出現的頁碼列表關聯。當我們說文檔被索引了,我們指的是倒排索引。我們來看下如下 2 個文檔是如何被倒排索引的:

文檔 1(Doc 1): Insight Data Engineering Fellows Program

文檔 2(Doc 2): Insight Data Science Fellows Program

如果我們想找包含詞項"insight"的文檔,我們可以掃描這個 (單詞有序的) 倒排索引,找到"insight"並返回包含改詞的文檔 ID,示例中是 Doc 1 和 Doc 2。

爲了提高可檢索性 (比如希望大小寫單詞都返回),我們應當先分析文檔再對其索引。分析包括 2 個部分:

  • 將句子詞條化爲獨立的單詞
  • 將單詞規範化爲標準形式

爲了提高可檢索性 (比如希望大小寫單詞都返回),我們應當先分析文檔再對其索引。分析包括 2 個部分:

  • 將句子詞條化爲獨立的單詞
  • 將單詞規範化爲標準形式

還有很多可用的分析器在此不列舉,請參考相關文檔。

爲了實現查詢時能得到對應的結果,查詢時應使用與索引時一致的分析器,對文檔進行分析。

注意:標準分析器包含了停用詞過濾器,但默認情況下沒有啓用。

現在,倒排索引的概念已經清楚,讓我們開始 CRUD 操作的研究吧。我們從寫操作開始。

剖析寫操作

創建 ((C)reate)

當我們發送索引一個新文檔的請求到協調節點後,將發生如下一組操作:

  • Elasticsearch 集羣中的每個節點都包含了改節點上分片的元數據信息。協調節點 (默認) 使用文檔 ID 參與計算,以便爲路由提供合適的分片。Elasticsearch 使用 MurMurHash3 函數對文檔 ID 進行哈希,其結果再對分片數量取模,得到的結果即是索引文檔的分片。
    shard = hash(document_id) % (num_of_primary_shards)

     

  • 當分片所在的節點接收到來自協調節點的請求後,會將該請求寫入 translog(我們將在本系列接下來的文章中講到),並將文檔加入內存緩衝。如果請求在主分片上成功處理,該請求會並行發送到該分片的副本上。當translog 被同步( fsync ) 到全部的主分片及其副本上後,客戶端纔會收到確認通知。
  • 內存緩衝會被週期性刷新 (默認是 1 秒),內容將被寫到文件系統緩存的一個新段上。雖然這個段並沒有被同步 (fsync),但它是開放的,內容可以被搜索到。
  • 每 30 分鐘,或者當 translog 很大的時候,translog 會被清空,文件系統緩存會被同步。這個過程在 Elasticsearch 中稱爲沖洗 (flush)。在沖洗過程中,內存中的緩衝將被清除,內容被寫入一個新段。段的 fsync 將創建一個新的提交點,並將內容刷新到磁盤。舊的 translog 將被刪除並開始一個新的 translog。

下圖展示了寫請求及其數據流。

更新 ((U)pdate) 和刪除 ((D)elete)

刪除和更新也都是寫操作。但是 Elasticsearch 中的文檔是不可變的,因此不能被刪除或者改動以展示其變更。那麼,該如何刪除和更新文檔呢?

磁盤上的每個段都有一個相應的.del文件。當刪除請求發送後,文檔並沒有真的被刪除,而是在.del文件中被標記爲刪除。該文檔依然能匹配查詢,但是會在結果中被過濾掉。當段合併 (我們將在本系列接下來的文章中講到) 時,在.del文件中被標記爲刪除的文檔將不會被寫入新段。

接下來我們看更新是如何工作的。在新的文檔被創建時,Elasticsearch 會爲該文檔指定一個版本號。當執行更新時,舊版本的文檔在.del文件中被標記爲刪除,新版本的文檔被索引到一個新段。舊版本的文檔依然能匹配查詢,但是會在結果中被過濾掉。

文檔被索引或者更新後,我們就可以執行查詢操作了。讓我們看看在 Elasticsearch 中是如何處理查詢請求的。

剖析讀操作 ((R)ead)

讀操作包含 2 部分內容:

  • 查詢階段
  • 提取階段

我們來看下每個階段是如何工作的。

查詢階段

在這個階段,協調節點會將查詢請求路由到索引的全部分片 (主分片或者其副本) 上。每個分片獨立執行查詢,併爲查詢結果創建一個優先隊列,以相關性得分排序 (我們將在本系列的後續文章中講到)。全部分片都將匹配文檔的 ID 及其相關性得分返回給協調節點。協調節點創建一個優先隊列並對結果進行全局排序。會有很多文檔匹配結果,但是,默認情況下,每個分片只發送前 10 個結果給協調節點,協調節點爲全部分片上的這些結果創建優先隊列並返回前 10 個作爲 hit。

提取階段

當協調節點在生成的全局有序的文檔列表中,爲全部結果排好序後,它將向包含原始文檔的分片發起請求。全部分片填充文檔信息並將其返回給協調節點。

下圖展示了讀請求及其數據流。

 

如上所述,查詢結果是按相關性排序的。接下來,讓我們看看相關性是如何定義的。

搜索相關性

相關性是由搜索結果中 Elasticsearch 打給每個文檔的得分決定的。默認使用的排序算法是 tf/idf(詞頻 / 逆文檔頻率)。詞頻衡量了一個詞項在文檔中出現的次數 (頻率越高 == 相關性越高),逆文檔頻率衡量了詞項在全部索引中出現的頻率,是一個索引中文檔總數的百分比 (頻率越高 == 相關性越低)。最後的得分是 tf-idf 得分與其他因子比如 (短語查詢中的) 詞項接近度、(模糊查詢中的) 詞項相似度等的組合。

接下來有什麼?

這些 CRUD 操作由 Elasticsearch 內部的一些數據結構所支持,這對於理解 Elasticsearch 的工作機制非常重要。在接下來的系列文章中,我將帶大家走進類似的那些概念並告訴大家在使用 Elasticsearch 中有哪些坑。

  • Elasticsearch 中的腦裂問題及防治措施
  • 事務日誌
  • Lucene 的段
  • 爲什麼搜索時使用深層分頁很危險
  • 計算搜索相關性中困難及權衡
  • 併發控制
  • 爲什麼 Elasticsearch 是準實時的
  • 如何確保讀和寫的一致性

 

發佈了74 篇原創文章 · 獲贊 88 · 訪問量 35萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章