ElasticSearch深度分頁引發的機器性能問題

個人博客請訪問 http://www.x0100.top   

深度分頁引發的機器性能問題

最近碰到一個ElasticSearch深度分頁搜索,導致cpu佔用過高問題,通過查閱ElasticSearch: 權威指南,瞭解到了深度分頁爲何會引起機器資源佔用:

在集羣系統中深度分頁
爲了理解爲什麼深度分頁是有問題的,讓我們假設在一個有5個主分片的索引中搜索。當
我們請求結果的第一頁(結果1到10)時,每個分片產生自己最頂端10個結果然後返回它
們給請求節點(requesting node),它再排序這所有的50個結果以選出頂端的10個結果。

現在假設我們請求第1000頁——結果10001到10010。工作方式都相同,不同的是每個分
片都必須產生頂端的10010個結果。然後請求節點排序這50050個結果並丟棄50040個!

你可以看到在分佈式系統中,排序結果的資源和時間花費隨着分頁的深入而成倍增長。這也是爲什
麼網絡搜索引擎中任何語句不能返回多於1000個結果的原因。

理解以上那段文字,有必要了解ElasticSearch集羣以及在集羣中是查詢的底層原理,本文試圖通過總結ElasticSearch基本概念和底層原理,加深自身理解,同時希望對使用者有所幫助,避免不必要的踩坑。

基本概念

索引(index)

“索引” 這個詞在 ElasticSearch 語境中包含多重意思:索引(名詞):類比傳統的關係型數據庫領域來說,索引相當於SQL中的一個數據庫。索引由其名稱(必須爲全小寫字符)進行標識,並通過引用此名稱完成文檔的創建、搜索、更新及刪除操作。

索引(動詞):索引一個文檔就是存儲一個文檔到一個索引(名詞)中以便它可以被檢索和查詢到。這非常類似於SQL語句中的 INSERT關鍵詞,除了文檔已存在時新文檔會替換舊文檔情況之外。

倒排索引:關係型數據庫通過增加一個“索引”比如一個B樹(B-tree)索引到指定的列上,以便提升數據檢索速度。ElasticSearch 和 Lucene 使用了一個叫做 “倒排索引” 的結構來達到相同的目的。

舉個例子,文檔和詞條之間的關係如下圖:圖1:文檔和詞條的關係

字段值被分析之後,存儲在倒排索引中,倒排索引存儲的是分詞(Term)和文檔(Doc)之間的關係,簡化版的倒排索引如下圖:圖2:倒排索引

類型(Type)

類型是索引內部的邏輯分區(category/partition),然而其意義完全取決於用戶需求。因此,一個索引內部可定義一個或多個類型(type)。一般來說,類型就是爲那些擁有相同的域的文檔做的預定義。類比傳統的關係型數據庫領域來說,類型相當於“表”。

文檔(Document)

文檔類似於一行完整的數據,在ElasticSearch裏面文檔是基於JSON格式進行表示的,文檔是索引和搜索的原子單位,它是包含了一個或多個域(Field)的容器。每個文檔可以存儲不同的域集,但同一類型(Type)下的文檔至少應該有某種程度上的相似之處。

節點(Node)

一個運行中的 ElasticSearch實例稱爲一個節點,而集羣是由一個或者多個擁有相同cluster.name配置的節點組成, 它們共同承擔數據和負載的壓力。

ES集羣中的節點有三種不同的類型:

  • 主節點:負責管理集羣範圍內的所有變更,例如增加、刪除索引,或者增加、刪除節點等。主節點並不需要涉及到文檔級別的變更和搜索等操作。可以通過屬性node.master進行設置。

  • 數據節點:存儲數據和其對應的倒排索引。默認每一個節點都是數據節點(包括主節點),可以通過node.data屬性進行設置。

  • 協調節點:如果node.master和node.data屬性均爲false,則此節點稱爲協調節點,用來響應客戶請求,均衡每個節點的負載。

分片(Shard)

一個索引中的數據保存在多個分片中,相當於水平分表。一個分片便是一個Lucene 的實例,它本身就是一個完整的搜索引擎。我們的文檔被存儲和索引到分片內,但是應用程序是直接與索引而不是與分片進行交互。

一個分片可以是主分片或者副本分片。索引內任意一個文檔都歸屬於一個主分片,所以主分片的數目決定着索引能夠保存的最大數據量。一個副本分片只是一個主分片的拷貝。副本分片作爲硬件故障時保護數據不丟失的冗餘備份,併爲搜索和返回文檔等讀操作提供服務。

集羣分佈式底層實現

以上我們對ElasticSearch的基本概念有了一個初步認識,接下來我們深入這些內部細節來幫助你更好的理解數據是如何在分佈式系統中存儲和查詢的。

ES實際上就是利用分片來實現分佈式。分片是數據的容器,文檔保存在分片內,分片又被分配到集羣內的各個節點裏。當你的集羣規模擴大或者縮小時, ES會自動的在各節點中遷移分片,使得數據仍然均勻分佈在集羣裏。

在索引建立的時候就已經確定了主分片數,但是副本分片數可以隨時修改。默認情況下,一個索引會有5個主分片,而其副本可以有任意數量。

主分片和副本分片的狀態決定了集羣的健康狀態。每一個節點上都只會保存主分片或者其對應的一個副本分片,相同的副本分片不會存在於同一個節點中。如果集羣中只有一個節點,則副本分片將不會被分配,此時集羣健康狀態爲yellow,存在丟失數據的風險。

分佈式文檔CRUD

索引新文檔(Create)

當用戶向一個節點提交了一個索引新文檔的請求,節點會計算新文檔應該加入到哪個分片(shard)中。每個節點都存儲有每個分片存儲在哪個節點的信息,因此協調節點會將請求發送給對應的節點。注意這個請求會發送給主分片,等主分片完成索引,會並行將請求發送到其所有副本分片,保證每個分片都持有最新數據。

每次寫入新文檔時,都會先寫入內存中,並將這一操作寫入一個translog文件(transaction log)中,此時如果執行搜索操作,這個新文檔還不能被索引到。圖3:新文檔被寫入內存,操作被寫入translog

ES會每隔1秒時間(這個時間可以修改)進行一次刷新操作(refresh),此時在這1秒時間內寫入內存的新文檔都會被寫入一個文件系統緩存(filesystem cache)中,並構成一個分段(segment)。此時這個segment裏的文檔可以被搜索到,但是尚未寫入硬盤,即如果此時發生斷電,則這些文檔可能會丟失。圖4:在執行刷新後清空內存,新文檔寫入文件系統緩存

不斷有新的文檔寫入,則這一過程將不斷重複執行。每隔一秒將生成一個新的segment,而translog文件將越來越大。圖5:translog不斷加入新文檔記錄

每隔30分鐘或者translog文件變得很大,則執行一次fsync操作。此時所有在文件系統緩存中的segment將被寫入磁盤,而translog將被刪除(此後會生成新的translog)。

圖6:執行fsync後segment寫入磁盤,清空內存和translog

由上面的流程可以看出,在兩次fsync操作之間,存儲在內存和文件系統緩存中的文檔是不安全的,一旦出現斷電這些文檔就會丟失。所以ES引入了translog來記錄兩次fsync之間所有的操作,這樣機器從故障中恢復或者重新啓動,ES便可以根據translog進行還原。

當然,translog本身也是文件,存在於內存當中,如果發生斷電一樣會丟失。因此,ES會在每隔5秒時間或是一次寫入請求完成後將translog寫入磁盤。可以認爲一個對文檔的操作一旦寫入磁盤便是安全的可以復原的,因此只有在當前操作記錄被寫入磁盤,ES纔會將操作成功的結果返回發送此操作請求的客戶端。

此外,由於每一秒就會生成一個新的segment,很快將會有大量的segment。對於一個分片進行查詢請求,將會輪流查詢分片中的所有segment,這將降低搜索的效率。因此ES會自動啓動合併segment的工作,將一部分相似大小的segment合併成一個新的大segment。合併的過程實際上是創建了一個新的segment,當新segment被寫入磁盤,所有被合併的舊segment被清除。

圖7:合併segment!圖8:合併完成後刪除舊segment,新segment可供搜索

更新(Update)和刪除(Delete)文檔

ES的索引是不能修改的,因此更新和刪除操作並不是直接在原索引上直接執行。

每一個磁盤上的segment都會維護一個del文件,用來記錄被刪除的文件。每當用戶提出一個刪除請求,文檔並沒有被真正刪除,索引也沒有發生改變,而是在del文件中標記該文檔已被刪除。因此,被刪除的文檔依然可以被檢索到,只是在返回檢索結果時被過濾掉了。每次在啓動segment合併工作時,那些被標記爲刪除的文檔纔會被真正刪除。

更新文檔會首先查找原文檔,得到該文檔的版本號。然後將修改後的文檔寫入內存,此過程與寫入一個新文檔相同。同時,舊版本文檔被標記爲刪除,同理,該文檔可以被搜索到,只是最終被過濾掉。

讀操作(Read):查詢過程

查詢的過程大體上分爲查詢(query)和取回(fetch)兩個階段。這個節點的任務是廣播查詢請求到所有相關分片,並將它們的響應整合成全局排序後的結果集合,這個結果集合會返回給客戶端。

查詢階段

當一個節點接收到一個搜索請求,則這個節點就變成了協調節點。

查詢過程分佈式搜索圖9:查詢過程分佈式搜索

第一步是廣播請求到索引中每一個節點的分片拷貝。查詢請求可以被某個主分片或某個副本分片處理,協調節點將在之後的請求中輪詢所有的分片拷貝來分攤負載。

每個分片將會在本地構建一個優先級隊列。如果客戶端要求返回結果排序中從第from名開始的數量爲size的結果集,則每個節點都需要生成一個from+size大小的結果集,因此優先級隊列的大小也是from+size。分片僅會返回一個輕量級的結果給協調節點,包含結果集中的每一個文檔的ID和進行排序所需要的信息。

協調節點會將所有分片的結果彙總,並進行全局排序,得到最終的查詢排序結果。此時查詢階段結束。

取回階段

查詢過程得到的是一個排序結果,標記出哪些文檔是符合搜索要求的,此時仍然需要獲取這些文檔返回客戶端。

協調節點會確定實際需要返回的文檔,並向含有該文檔的分片發送get請求;分片獲取文檔返回給協調節點;協調節點將結果返回給客戶端

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