elasticsearch(五)---分佈式搜索

在繼續以前,咱們將講一下搜索是如何在分佈式環境中執行的。html

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

一個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
}
複製代碼

這個查詢的過程被描述在圖分佈式搜索查詢階段中。session

圖1 分佈式搜索查詢階段分佈式

查詢階段包含如下三步:fetch

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自己。這就是取回階段的工做,如圖分佈式搜索的取回階段所示。

圖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權威指南

相關文章
相關標籤/搜索