elasticsearch(五)---分散式搜尋

P城到底誰說的算發表於2018-08-21

在繼續之前,我們將講一下搜尋是如何在分散式環境中執行的。

它比我們之前講的基礎的增刪改查(create-read-update-delete ,CRUD)請求要複雜一些。

一個CRUD操作只處理一個單獨的文件。文件的唯一性由_index, _typerouting-value(通常預設是該文件的_id)的組合來確定。這意味著我們可以準確知道叢集中的哪個分片持有這個文件。

由於不知道哪個文件會匹配查詢(文件可能存放在叢集中的任意分片上),所以搜尋需要一個更復雜的模型。一個搜尋不得不通過查詢每一個我們感興趣的索引的分片副本,來看是否含有任何匹配的文件。

但是,找到所有匹配的文件只完成了這件事的一半。在搜尋(search)API返回一頁結果前,來自多個分片的結果必須被組合放到一個有序列表中。因此,搜尋的執行過程分兩個階段,稱為查詢然後取回(query then fetch)。

查詢階段

在初始化查詢階段(query phase),查詢被向索引中的每個分片副本(原本或副本)廣播。每個分片在本地執行搜尋並且建立了匹配document的優先佇列(priority queue)。

優先佇列

一個優先佇列(priority queue is)只是一個存有前n個(top-n)匹配document的有序列表。
這個優先佇列的大小由分頁引數from和size決定。
例如,下面這個例子中的搜尋請求要求優先佇列要能夠容納100個document

GET /_search
{
    "from": 90,
    "size": 10
}
複製程式碼

這個查詢的過程被描述在圖分散式搜尋查詢階段中。

elasticsearch(五)---分散式搜尋

圖1 分散式搜尋查詢階段

查詢階段包含以下三步:

1.客戶端傳送一個search(搜尋)請求給Node 3,Node 3建立了一個長度為from+size的空優先順序佇列。

2.Node 3 轉發這個搜尋請求到索引中每個分片的原本或副本。每個分片在本地執行這個查詢並且結果將結果到一個大小為from+size的有序本地優先佇列裡去。

3.每個分片返回document的ID和它優先佇列裡的所有document的排序值給協調節點Node 3。Node3把這些值合併到自己的優先佇列裡產生全域性排序結果。

當一個搜尋請求被髮送到一個節點Node,這個節點就變成了協調節點。這個節點的工作是向所有相關的分片廣播搜尋請求並且把它們的響應整合成一個全域性的有序結果集。這個結果集會被返回給客戶端。

第一步是向索引裡的每個節點的分片副本廣播請求。就像document的GET請求一樣,搜尋請求可以被每個分片的原本或任意副本處理。這就是更多的副本(當結合更多的硬體時)如何提高搜尋的吞吐量的方法。對於後續請求,協調節點會輪詢所有的分片副本以分攤負載。

每一個分片在本地執行查詢和建立一個長度為from+size的有序優先佇列——這個長度意味著它自己的結果數量就足夠滿足全域性的請求要求。分片返回一個輕量級的結果列表給協調節點。只包含documentID值和排序需要用到的值,例如_score

協調節點將這些分片級的結果合併到自己的有序優先佇列裡。這個就代表了最終的全域性有序結果集。到這裡,查詢階段結束。

整個過程類似於歸併排序演算法,先分組排序再歸併到一起,對於這種分散式場景非常適用。

注意

一個索引可以由一個或多個原始分片組成,所以一個對於單個索引的搜尋請求也需要能夠把來自多個分片的結果組合起來。
一個對於 多(multiple)或全部(all)索引的搜尋的工作機制和這完全一致——僅僅是多了一些分片而已。
複製程式碼

取回階段

查詢階段辨別出那些滿足搜尋請求的document,但我們仍然需要取回那些document本身。這就是取回階段的工作,如圖分散式搜尋的取回階段所示。

elasticsearch(五)---分散式搜尋

圖2 分散式搜尋取回階段

分發階段由以下步驟構成:

1.協調節點辨別出哪個document需要取回,並且向相關分片發出GET請求。

2.每個分片載入document並且根據需要豐富(enrich)它們,然後再將document返回協調節點。

3.一旦所有的document都被取回,協調節點會將結果返回給客戶端。

協調節點先決定哪些document是實際(actually)需要取回的。 例如,我們指定查詢{ "from": 90, "size": 10 },那麼前90條將會被丟棄,只有之後的10條會需要取回。這些document可能來自與原始查詢請求相關的某個、某些或者全部分片。

協調節點為每個持有相關document的分片建立多點get請求然後傳送請求到處理查詢階段的分片副本。

分片載入document主體——_source field。如果需要,還會根據後設資料豐富結果和高亮搜尋片斷。一旦協調節點收到所有結果,會將它們彙集到單一的回答響應裡,這個響應將會返回給客戶端。

深分頁

查詢然後取回過程雖然支援通過使用fromsize引數進行分頁,但是要在有限範圍內(within limited)。

還記得每個分片必須構造一個長度為from+size的優先佇列吧,所有這些都要傳回協調節點。這意味著協調節點要通過對分片數量 * (from + size)個document進行排序來找到正確的size個document。

根據document的數量,分片的數量以及所使用的硬體,對10,000到50,000條結果(1,000到5,000頁)深分頁是可行的。但是對於足夠大的from值,排序過程將會變得非常繁重,會使用巨大量的CPU,記憶體和頻寬。因此,強烈不建議使用深分頁。

在實際中,“深分頁者”也是很少的一部人。一般人會在翻了兩三頁後就停止翻頁,並會更改搜尋標準。那些不正常情況通常是機器人或者網路爬蟲的行為。它們會持續不斷地一頁接著一頁地獲取頁面直到伺服器到崩潰的邊緣。

如果你確實需要從叢集裡獲取大量documents,你可以通過設定搜尋型別scan禁用排序,來高效地做這件事。

搜尋選項

一些查詢字串(query-string)可選引數能夠影響搜尋過程。

preference(偏愛)

preference引數允許你控制使用哪個分片或節點來處理搜尋請求。

她接受如下一些引數 _primary_primary_first_local_only_node:xyz_prefer_node:xyz_shards:2,3

然而通常最有用的值是一些隨機字串,它們可以避免結果震盪問題(the bouncing results problem)。

結果震盪(Bouncing Results)

想像一下,你正在按照timestamp欄位來對你的結果排序,並且有兩個document有相同的timestamp。
由於搜尋請求是在所有有效的分片副本間輪詢的,這兩個document可能在原始分片裡是一種順序,在副本分片裡是另一種順序。

這就是被稱為結果震盪(bouncing results)的問題:
使用者每次重新整理頁面,結果順序會發生變化。
避免這個問題方法是對於同一個使用者總是使用同一個分片。
方法就是使用一個隨機字串例如使用者的會話ID(session ID)來設定preference引數。
複製程式碼

timeout(超時)

通常,協調節點會等待接收所有分片的回答。如果有一個節點遇到問題,它會拖慢整個搜尋請求。

timeout引數告訴協調節點最多等待多久,就可以放棄等待而將已有結果返回。返回部分結果總比什麼都沒有好。

搜尋請求的返回將會指出這個搜尋是否超時,以及有多少分片成功答覆了:

   ...
    "timed_out":     true,  (1)
    "_shards": {
       "total":      5,
       "successful": 4,
       "failed":     1     (2)
    },
    ...
複製程式碼

(1) 搜尋請求超時。

(2) 五個分片中有一個沒在超時時間內答覆。

如果一個分片的所有副本都因為其他原因失敗了——也許是因為硬體故障——這個也同樣會反映在該答覆的_shards部分裡。

routing(路由選擇)

在搜尋時,你可以指定一個或多個routing 值來限制只搜尋那些分片而不是搜尋index裡的全部分片:

GET /_search?routing=user_1,user2
複製程式碼

search_type(搜尋型別)

雖然query_then_fetch是預設的搜尋型別,但也可以根據特定目的指定其它的搜尋型別,例如:

GET /_search?search_type=count
複製程式碼

1.count(計數)

count(計數)搜尋型別只有一個query(查詢)的階段。當不需要搜尋結果只需要知道滿足查詢的document的數量時,可以使用這個查詢型別。

2.query_and_fetch(查詢並且取回)

query_and_fetch(查詢並且取回)搜尋型別將查詢和取回階段合併成一個步驟。這是一個內部優化選項,當搜尋請求的目標只是一個分片時可以使用,例如指定了routing(路由選擇)值時。雖然你可以手動選擇使用這個搜尋型別,但是這麼做基本上不會有什麼效果。

3.dfs_query_then_fetch 和 dfs_query_and_fetch

dfs搜尋型別有一個預查詢的階段,它會從全部相關的分片裡取回專案頻數來計算全域性的專案頻數。

4.scan(掃描)

scan(掃描)搜尋型別是和scroll(滾屏)API連在一起使用的,可以高效地取回巨大數量的結果。它是通過禁用排序來實現的。

掃描和滾屏

scan(掃描)搜尋型別是和scroll(滾屏)API一起使用來從Elasticsearch裡高效地取回巨大數量的結果而不需要付出深分頁的代價。

scroll(滾屏)

一個滾屏搜尋允許我們做一個初始階段搜尋並且持續批量從Elasticsearch里拉取結果直到沒有結果剩下。這有點像傳統資料庫裡的cursors(遊標)。

傳統資料庫遊標:遊標(cursor)是系統為使用者開設的一個資料緩衝區,存放SQL語句的執行結果。
每個遊標區都有一個名字,使用者可以用SQL語句逐一從遊標中獲取記錄,並賦給主變數,交由主語言進一步處理。
就本質而言,遊標實際上是一種能從包括多條資料記錄的結果集中每次提取一條記錄的機制。
遊標是一段私有的SQL工作區,也就是一段記憶體區域,用於暫時存放受SQL語句影響到的資料。
通俗理解就是將受影響的資料暫時放到了一個記憶體區域的虛表中,而這個虛表就是遊標。
複製程式碼

滾屏搜尋會及時製作快照。這個快照不會包含任何在初始階段搜尋請求後對index做的修改。它通過將舊的資料檔案儲存在手邊,所以可以保護index的樣子看起來像搜尋開始時的樣子。

為什麼使用Elasticsearch Scroll?
當Elasticsearch響應請求時,它必須確定docs的順序,排列響應結果。
如果請求的頁數較少(假設每頁20個docs), Elasticsearch不會有什麼問題,但是如果頁數較大時,比如請求第20頁,
Elasticsearch不得不取出第1頁到第20頁的所有docs,再去除第1頁到第19頁的docs,得到第20頁的docs。
解決的方法就是使用Scroll。因為Elasticsearch要做一些操作(確定之前頁數的docs)為每一次請求.
所以,我們可以讓Elasticsearch儲存這些資訊為之後的查詢請求。
這樣做的缺點是,我們不能永遠的儲存這些資訊,因為儲存資源是有限的。
所以Elasticsearch中可以設定我們需要儲存這些資訊的時長。
複製程式碼

scan(掃描)

深度分頁代價最高的部分是對結果的全域性排序,但如果禁用排序,就能以很低的代價獲得全部返回結果。為達成這個目的,可以採用scan(掃描)搜尋模式。掃描模式讓Elasticsearch不排序,只要分片裡還有結果可以返回,就返回一批結果。

為了使用scan-and-scroll(掃描和滾屏),需要執行一個搜尋請求,將search_type 設定成scan,並且傳遞一個scroll引數來告訴Elasticsearch滾屏應該持續多長時間。

GET /old_index/_search?search_type=scan&scroll=1m (1)
{
    "query": { "match_all": {}},
    "size":  1000
}
複製程式碼

(1)保持滾屏開啟1分鐘。

這個請求的應答沒有包含任何命中的結果,但是包含了一個Base-64編碼的_scroll_id(滾屏id)字串。現在我們可以將_scroll_id傳遞給_search/scroll末端來獲取第一批結果:

GET /_search/scroll?scroll=1m      (1)
c2Nhbjs1OzExODpRNV9aY1VyUVM4U0NMd2pjWlJ3YWlBOzExOTpRNV9aY1VyUVM4U0 <2>
NMd2pjWlJ3YWlBOzExNjpRNV9aY1VyUVM4U0NMd2pjWlJ3YWlBOzExNzpRNV9aY1Vy
UVM4U0NMd2pjWlJ3YWlBOzEyMDpRNV9aY1VyUVM4U0NMd2pjWlJ3YWlBOzE7dG90YW
xfaGl0czoxOw==
複製程式碼

(1) 保持滾屏開啟另一分鐘。

(2) _scroll_id可以在body或者URL裡傳遞,也可以被當做查詢引數傳遞。

注意,要再次指定?scroll=1m。滾屏的終止時間會在我們每次執行滾屏請求時重新整理,所以他只需要給我們足夠的時間來處理當前批次的結果而不是所有的匹配查詢的document。

這個滾屏請求的應答包含了第一批次的結果。雖然指定了一個1000的size ,但是獲得了更多的document。當掃描時,size被應用到每一個分片上,所以我們在每個批次裡最多或獲得size * number_of_primary_shards(size*主分片數)個document。

注意:

滾屏請求也會返回一個新的_scroll_id。每次做下一個滾屏請求時,必須傳遞前一次請求返回的_scroll_id。
複製程式碼

如果沒有更多的命中結果返回,就處理完了所有的命中匹配的document。

一些Elasticsearch官方客戶端提供掃描和滾屏的小助手。小助手提供了一個對這個功能的簡單封裝。
複製程式碼

參考:es權威指南

相關文章