解剖 Elasticsearch 叢集 - 之一
本篇文章是一系列涵蓋 Elasticsearch 底層架構和原型示例的其中一篇。在本篇文章中,我們會討論底層的儲存模型以及 CRUD(建立、讀取、更新和刪除)操作在 Elasticsearch 中是如何工作的。
全文搜尋(Full-text search)
- 例如,找到與搜尋詞最為相關的維基百科文章。
聚合(Aggregations)
- 例如,對搜尋詞在廣告網路中進行直方圖視覺化展示。
空間地理位置 API(Geospatial API)
- 例如,拼車平臺匹配最近的車主與乘客。
因為 Elasticsearch 在行業中非常流行,本篇文章會分享它的儲存模型以及 CRUD 操作是如何工作的。
當我們談到一個分散式系統是如何工作的時候,通常會有以下畫面:
浮於表面的是 API 而在它之下的是真正的引擎,也就是所有奇妙發生的地方。本篇文章會關注表面之下的部分。會主要介紹:
- 它是一個主從架構還是一個無主架構?
- 儲存模型是怎樣的?
- 寫是如何工作的?
- 讀是如何工作的?
- 搜尋結果的相關性如何?
在我們深入理解這些概念之前,讓我們熟悉一些基本術語。
混淆 Elasticsearch 的索引和 Lucene 的索引以及其他的術語...
一個 Elasticsearch 索引(index)是一個邏輯空間用來組織資料(像資料庫)。一個 Elasticsearch 索引有一個或多個分片(shard 預設是 5 個)。一個分片是一個 Lucene 的索引,它是資料真正儲存的地方,也是搜尋引擎本身。每個分片可以有零個或多個副本(預設是 1)。一個 Elasticsearch 索引也有 “型別(types)”(像資料庫裡的表一樣)它使我們可以對索引裡的資料進行分割槽。在 Elasticsearch 索引中給定 “型別(types)” 的所有文件都具有相同的屬性(像表模式)。
類比關係型資料庫的術語
- Elasticsearch 索引 ~ 資料庫
- 型別 ~ 表
- 對映 ~ 模式
NOTE: 以上僅作類比用,它們並不等價。推薦閱讀這篇文章來幫助決定何時選擇索引或型別來儲存資料。
現在我們熟悉了 Elasticsearch 世界中的術語,再來看看不同節點所扮演的角色。
節點的型別
一個 Elasticsearch 是一個節點,一組節點組成一個叢集。Elasticsearch 叢集中的節點有三種配置方式:
主節點(Master Node)
它控制 Elasticsearch 叢集,負責所有叢集範圍的操作如建立和刪除索引,跟蹤叢集的各個節點併為節點指定分片。主節點在同一時間只保持叢集的一個狀態,然後將狀態廣播其他所有節點並得到其他節點的確認響應。
一個節點可以通過設定 elasticsearch.yml 檔案中的 node.master 屬性 true(預設值)將其配置為主節點。
對於大規模生產環境的叢集,推薦配置僅用來控制叢集狀態而不處理任何使用者請求的主節點。
資料節點(Data Node)
- 它保持資料和倒排索引。預設情況下,每個節點都是被配置成資料節點的,屬性 node.data 在 elasticsearch.yml 檔案中設定為 true 。如果想要有專門的主節點,需要將 master.data 屬性設定為 false 。
客戶端節點(Client Node)
- 如果將 node.master 和 node.data 設定成 false ,節點被配置成為客戶端節點,它在叢集中扮演負載均衡器將請求路由至不同節點。
在 Elasticsearch 叢集中作為客戶端連線的節點叫做協調節點。協調節點將客戶端請求路由至叢集中相應的分片。對於讀請求,協調節點每次都會選擇一個不同的分片來應對請求從而達到負載均衡的目的。
在介紹 CRUD 請求被髮送到協調節點並傳送到叢集之前,我們先看看 Elasticsearch 內部的資料儲存是如何支援低延遲的全文搜尋的。
儲存模型
Elasticsearch 使用 Apache Lucene ,一個用 Java 語言開發的全文搜尋庫,它的作者是 Doug Cutting(Apache Hadoop 的建立人),它內部使用了一種被稱為倒排索引的資料結構來支援低延時的搜尋結果。文件是 Elasticsearch 裡的資料單元,倒排索引是根據對文件裡的詞進行 tokenizing 處理生成的,建立所有唯一詞的有序列表以及詞語再文件中的所處位置資訊。
它和書背後的索引很像,包括書裡面所有的唯一詞以及可以找到該詞的頁。當我們說一個文件被索引了,我們指的是倒排索引。讓我們看看以下兩個文件的倒排索引是什麼樣子:
- Doc 1: Insight Data Engineering Fellows Program
- Doc 2: Insight Data Science Fellows Program
如果我們想要找到包含 “insight” 的文件,我們可以掃描倒排索引(裡面的詞語已排序),找到詞 “insight” 然後返回包括該詞的文件 ID ,這裡是 Doc 1 和 Doc 2 。
為了提升搜尋能力(例如,大小寫都能獲得相同的結果),文件首先被分析,然後被索引。分析包括兩個過程:
- 將句子語彙單元化為單獨的詞
- 將詞語歸一化成標準形式
預設情況下,Elasticsearch 使用標準分析器,使用
- 標準分詞器(Standard tokenizer)在詞語邊界對詞語進行拆分
- 小寫分詞過濾器(Lowercase token filter)將詞轉換成小寫形式
還有很多其他的分析器,可以在官方文件中找到它們。
為了提供相關的搜尋結果,每個查詢會使用索引時相同的分析器。
NOTE: 標準分析器會用 stop token 過濾器,不過預設它是禁用的。
在清楚了倒排索引的概念後,讓我們看看 CRUD 操作。
結構寫操作
建立 - (C)reate
當向協調節點傳送請求,索引一個新文件時,會有以下一系列操作:
在 Elasticsearch 叢集中的所有節點都包含元資訊,知道哪個分片處於哪個節點。協調節點根據文件 ID 將文件路由至合適的分片。Elasticsearch 用 murmur3 雜湊函式對文件 ID 進行雜湊計算,然後根據索引內的主分片數進行模運算,以此決定文件索引的分片。
shard = hash(document_id) % (num_of_primary_shards)
當節點接收到來自協調節點的請求後,請求被寫入 translog (後續會介紹 translog 的相關內容)然後文件會被存入記憶體緩衝區。如果請求在主節點請求成功,請求會並行傳送到備份分片。只有所有主備分片上的 translog 同步後(fsync'ed),客戶端才會接收到請求成功的應答。
記憶體緩衝會定期重新整理(預設時間是 1 秒),內容會被寫入到檔案系統快取裡的新段(segment)中。這個段還沒有被同步(fsync'ed),但是,段是開放的而且內容也可供搜尋。
translog 會每隔 30 分鐘(或過大時)被清空,檔案系統快取同時也會進行同步操作(fsync'ed)。在 Elasticsearch 中,這個過程被稱為 flush 。在 flush 過程中,in-memory 緩衝會被清除,內容同時被寫到新段(segment)中。一個新的提交點發生在段同步並寫入磁碟時。舊的 translog 會被刪除,並開始於一個全新的。
更新和刪除 - (U)pdate and (D)elete
Delete 和 Update 操作也是寫操作。但 Elasticsearch 中的文件是不可變的,所以不能被刪除或修改。那麼一個文件是如何刪除或更新的呢?
磁碟上的每個段都有一個 .del 檔案關聯它。當 delete 請求傳送時,文件並不是真正被刪除了,而是在 .del 檔案中被標記為了已刪除狀態。這個文件仍然會匹配搜尋查詢,但是會在結果中被過濾掉。當段合併後(我們會在後續文章中介紹段合併),在 .del 檔案中被標記為已刪除的文件不會包括在新合併的段中。
現在,我們來看看更新的工作方式。當新文件建立後,Elasticsearch 會為文件指定版本號。文件的每次改變都會生成一個新的版本號。當更新完成後,舊版本會在 .del 檔案中標記為已刪除,新版本會被索引到新的段中。舊版本仍然會在搜尋查詢中被匹配到,但是會在結果中被過濾掉。
在文件索引/更新後,我們會處理搜尋請求。讓我們看看搜尋是如何在 Elasticsearch 中執行的。
解剖讀 - (R)ead
讀操作包括兩個部分:
- 查詢階段(Query Phase)
- 讀取階段(Fetch Phase)
讓我們看看每個階段的工作方式。
查詢階段(Query Phase)
在這個階段中,協調節點會將搜尋請求路由到索引內的所有分片(主分片或備分片)。分片會各自獨立完成搜尋,根據相關度評分對結果排序建立一個優先佇列(在後續文章中會介紹相關度評分)。所有分片都會返回匹配的文件 ID 以及相關度評分到協調節點。協調節點會建立一個優先佇列對全域性結果進行排序。匹配文件的數目可以很多,在預設狀態下,每個分片都會傳送前 10 個 結果到協調節點,協調節點會建立優先佇列對來自所有分片的結果進行排序並返回前 10 的結果。
讀取階段(Fetch Phase)
在協調節點對所有結果進行排序並建立一個有序的文件列表後,它會向所有分片發出請求獲取原始文件。所有分片都會補充文件內容,並將它們返回到協調節點。
下圖展現了讀請求和資料流向。
就像之前提到的,搜尋結果是根據相關度進行排序的。讓我們看看相關度是如何定義的。
搜尋的相關度
相關度是由 Elasticsearch 為搜尋結果中每個文件的評分所決定的。預設評分演算法是 tf/idf(term frequency/inverse document frequency)。詞頻(term frequency)度量詞項在文件中出現的次數(越高頻 = 越相關),逆向文件頻率(inverse document frequency)度量詞項在整個索引出現的次數作為索引內所有文件的總百分比(越高頻 = 越不相關)。最終的評分是 tf-idf 評分與其他如詞近似度(短語查詢 - phrase queries)、詞項相似度(模糊查詢 - fuzzy queries)等因子結合後的結果。
接下來是什麼?
CRUD 操作是由一些內部資料結構和技術支援的,它們對於理解 Elasticsearch 的工作方式十分重要。在接下的文章中,會涵蓋以下概念以及使用 Elasticsearch 時的一些陷阱:
- Elasticsearch 裡的腦裂問題以及如何避免
- 事務日誌(Transaction log)
- Lucene 段(Lucene segments)
- 為什麼搜尋中深度分頁是十分危險的?
- 計算搜尋相關度的難度以及權衡
- 併發控制(Concurrency control)
- 為什麼 Elasticsearch 是準實時的?
- 如何保證讀寫的一致?
參考
參考來源:
Anatomy of an Elasticsearch Cluster: Part I