1. 程式人生 > >Elasticsearch(三)-分散式增刪改查和搜尋

Elasticsearch(三)-分散式增刪改查和搜尋

分散式增刪改查

路由

路由文件到分片

Elasticsearch是如何知道文件屬於哪個分片的呢?當你建立一個新文件,它是如何知道是應該儲存在分片1還是分片2上的呢?

程序不能是隨機的,因為我們將來要檢索文件。事實上,它根據一個簡單的演算法決定:

shard = hash(routing) % number_of_primary_shards

routing值是一個任意字串,它預設是_id但也可以自定義。這個routing字串通過雜湊函式生成一個數字,然後除以主切片的數量得到一個餘數(remainder),餘數的範圍永遠是0到number_of_primary_shards - 1

,這個數字就是特定文件所在的分片。

這也解釋了為什麼主分片的數量只能在建立索引時定義且不能修改:如果主分片的數量在未來改變了,所有先前的路由值就失效了,文件也就永遠找不到了

分片交換

主分片和複製分片如何互動

我們假設有三個節點的叢集。它包含一個叫做bblogs的索引並擁有兩個主分片。每個主分片有兩個複製分片。相同的分片不會放在同一個節點上。

3-1主分片和複製分片如何互動

我們能夠傳送請求給叢集中任意一個節點。每個節點都有能力處理任意請求。每個節點都知道任意文件所在的節點,所以也可以將請求轉發到需要的節點。被請求的節點叫做請求節點。

新建、索引和刪除

新建、索引和刪除文件

新建、索引和刪除請求都是寫(write)操作,它們必須在主分片上成功完成才能複製到相關的複製分片上。

3-2 新建、索引和刪除文件

在主分片和複製分片上成功新建、索引或刪除一個文件必要的順序步驟:

  1. 客戶端給Node 1傳送新建、索引或刪除請求。
  2. 節點使用文件的_id確定文件屬於分片0。它轉發請求到Node 3,分片0位於這個節點上。
  3. Node 3在主分片上執行請求,如果成功,它轉發請求到相應的位於Node 1和Node 2的複製節點上。當所有的複製節點報告成功,Node 3報告成功到請求的節點,請求的節點再報告給客戶端。

客戶端接收到成功響應的時候,文件的修改已經被應用於主分片和所有的複製分片。你的修改生效了。

有很多可選的請求引數允許你更改這一過程。你可能想犧牲一些安全來提高效能。這一選項很少使用因為Elasticsearch已經足夠快,不過為了內容的完整我們將做一些闡述。

replication

複製預設的值是sync。這將導致主分片得到複製分片的成功響應後才返回。

如果你設定replication為async,請求在主分片上被執行後就會返回給客戶端。它依舊會轉發請求給複製節點,但你將不知道複製節點成功與否。

上面的這個選項不建議使用。預設的sync複製允許Elasticsearch強制反饋傳輸。async複製可能會因為在不等待其它分片就緒的情況下發送過多的請求而使Elasticsearch過載。

consistency

預設主分片在嘗試寫入時需要規定數量(quorum)或過半的分片(可以是主節點或複製節點)可用。這是防止資料被寫入到錯的網路分割槽。規定的數量計算公式如下:

int( (primary + number_of_replicas) / 2 ) + 1

consistency允許的值為one(只有一個主分片),all(所有主分片和複製分片)或者預設的quorum或過半分片。

注意number_of_replicas是在索引中的的設定,用來定義複製分片的數量,而不是現在活動的複製節點的數量。如果你定義了索引有3個複製節點,那規定數量是:

int( (primary + 3 replicas) / 2 ) + 1 = 3

但如果你只有2個節點,那你的活動分片不夠規定數量,也就不能索引或刪除任何文件。

timeout

當分片副本不足時會怎樣?Elasticsearch會等待更多的分片出現。預設等待一分鐘。如果需要,你可以設定timeout引數讓它終止的更早:100表示100毫秒,30s表示30秒。

注意:

新索引預設有1個複製分片,這意味著為了滿足quorum的要求需要兩個活動的分片。當然,這個預設設定將阻止我們在單一節點叢集中進行操作。為了避開這個問題,規定數量只有在number_of_replicas大於一時才生效

檢索

檢索文件

文件能夠從主分片或任意一個複製分片被檢索。

3-3檢索文件

在主分片或複製分片上檢索一個文件必要的順序步驟:

  1. 客戶端給Node 1傳送get請求。
  2. 節點使用文件的_id確定文件屬於分片0。分片0對應的複製分片在三個節點上都有。此時,它轉發請求到Node 2。
  3. Node 2返回文件(document)給Node 1然後返回給客戶端。

對於讀請求,為了平衡負載,請求節點會為每個請求選擇不同的分片——它會迴圈所有分片副本。

可能的情況是,一個被索引的文件已經存在於主分片上卻還沒來得及同步到複製分片上。這時複製分片會報告文件未找到,主分片會成功返回文件。一旦索引請求成功返回給使用者,文件則在主分片和複製分片都是可用的。

區域性更新

區域性更新文件

update API 結合了之前提到的讀和寫的模式。

順序如下圖:

區域性更新

  1. 客戶端給Node 1傳送更新請求。
  2. 它轉發請求到主分片所在節點Node 3。
  3. Node 3從主分片檢索出文檔,修改_source欄位的JSON,然後在主分片上重建索引。如果有其他程序修改了文件,它以retry_on_conflict設定的次數重複步驟3,都未成功則放棄。
  4. 如果Node 3成功更新文件,它同時轉發文件的新版本到Node 1和Node 2上的複製節點以重建索引。當所有複製節點報告成功,Node 3返回成功給請求節點,然後返回給客戶端。

update API還接受《新建、索引和刪除》章節提到的routing、replication、consistency和timout引數。

基於文件的複製

當主分片轉發更改給複製分片時,並不是轉發更新請求,而是轉發整個文件的新版本。記住這些修改轉發到複製節點是非同步的,它們並不能保證到達的順序與傳送相同。如果Elasticsearch轉發的僅僅是修改請求,修改的順序可能是錯誤的,那得到的就是個損壞的文件。

批量請求

多文件模式

mget和bulk API與單獨的文件類似。差別是請求節點知道每個文件所在的分片。它把多文件請求拆成每個分片的對文件請求,然後轉發每個參與的節點。

一旦接收到每個節點的應答,然後整理這些響應組合為一個單獨的響應,最後返回給客戶端。

3-5批量請求

mget請求檢索多個文件的順序步驟:

  1. 客戶端向Node 1傳送mget請求。
  2. Node 1為每個分片構建一個多條資料檢索請求,然後轉發到這些請求所需的主分片或複製分片上。當所有回覆被接收,Node 1構建響應並返回給客戶端。

3-6mget

bulk執行多個create、index、delete和update請求的順序步驟:

  1. 客戶端向Node 1傳送bulk請求。
  2. Node 1為每個分片構建批量請求,然後轉發到這些請求所需的主分片上。
  3. 主分片一個接一個的按序執行操作。當一個操作執行完,主分片轉發新文件(或者刪除部分)給對應的複製節點,然後執行下一個操作。一旦所有複製節點報告所有操作已成功完成,節點就報告success給請求節點,後者(請求節點)整理響應並返回給客戶端。

bulk API還可以在最上層使用replication和consistency引數,routing引數則在每個請求的元資料中使用。

搜尋

空搜尋

空搜尋

最基本的搜尋API表單是空搜尋(empty search),它沒有指定任何的查詢條件,只返回叢集索引中的所有文件:GET /_search

返回資料說明:

hits

響應中最重要的部分是hits,它包含了total欄位來表示匹配到的文件總數,hits陣列還包含了匹配到的前10條資料。

hits陣列中的每個結果都包含_index、_type和文件的_id欄位,被加入到_source欄位中這意味著在搜尋結果中我們將可以直接使用全部文件。

每個節點都有一個_score欄位,這是相關性得分(relevance score),它衡量了文件與查詢的匹配程度。預設的,返回的結果中關聯性最大的文件排在首位;

max_score

指的是所有文件匹配查詢中_score的最大值。

took

告訴我們整個搜尋請求花費的毫秒數。

shards

_shards節點告訴我們參與查詢的分片數(total欄位),有多少是成功的(successful欄位),有多少的是失敗的(failed欄位)。通常我們不希望分片失敗,不過這個有可能發生。如果我們遭受一些重大的故障導致主分片和複製分片都故障,那這個分片的資料將無法響應給搜尋請求。這種情況下,Elasticsearch將報告分片failed,但仍將繼續返回剩餘分片上的結果。

timeout

time_out值告訴我們查詢超時與否。一般的,搜尋請求不會超時。如果響應速度比完整的結果更重要,你可以定義timeout引數。GET /_search?timeout=10ms Elasticsearch將返回在請求超時前收集到的結果。

需要注意的是timeout不會停止執行查詢,它僅僅告訴你目前順利返回結果的節點然後關閉連線。在後臺,其他分片可能依舊執行查詢,儘管結果已經被髮送。

多索引和多類別

多索引和多類別

搜尋一個或幾個自定的索引或型別,我們能通過定義URL中的索引或型別達到這個目的

  1. 在所有索引的所有型別中搜索

    /_search

  2. 在索引gb的所有型別中搜索

    /gb/_search

  3. 在索引gb和us的所有型別中搜索

    /gb,us/_search

  4. 在以g或u開頭的索引的所有型別中搜索

    /g*,u*/_search

  5. 在索引gb的型別user中搜索

    /gb/user/_search

  6. 在索引gb和us的型別為user和tweet中搜索

    /gb,us/user,tweet/_search

  7. 在所有索引的user和tweet中搜索 search types user and tweet in all indices

    /_all/user,tweet/_search

當你搜索包含單一索引時,Elasticsearch轉發搜尋請求到這個索引的主分片或每個分片的複製分片上,然後聚集每個分片的結果。搜尋包含多個索引也是同樣的方式——只不過或有更多的分片被關聯。

分頁

分頁

和SQL使用LIMIT關鍵字返回只有一頁的結果一樣,Elasticsearch接受from和size引數:

size: 結果數,預設10

from: 跳過開始的結果數,預設0

如果你想每頁顯示5個結果,頁碼從1到3,那請求如下:

GET /_search?size=5
GET /_search?size=5&from=5
GET /_search?size=5&from=10

應該當心分頁太深或者一次請求太多的結果。結果在返回前會被排序。但是記住一個搜尋請求常常涉及多個分片。每個分片生成自己排好序的結果,它們接著需要集中起來排序以確保整體排序正確。

在集群系統中深度分頁

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

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

你可以看到在分散式系統中,排序結果的花費隨著分頁的深入而成倍增長。這也是為什麼網路搜尋引擎中任何語句不能返回多於1000個結果的原因。

查詢字串

簡易搜尋

search API有兩種表單:一種是“簡易版”的查詢字串(query string)將所有引數通過查詢字串定義,另一種版本使用JSON完整的表示請求體(request body),這種富搜尋語言叫做結構化查詢語句(DSL)

查詢字串搜尋對於在命令列下執行點對點(ad hoc)查詢特別有用。例如這個語句查詢所有型別為tweet並在tweet欄位中包含elasticsearch字元的文件:

GET /_all/tweet/_search?q=tweet:elasticsearch

下一個語句查詢name欄位中包含”john”和tweet欄位包含”mary”的結果。實際的查詢只需要:

+name:john +tweet:mary

但是百分比編碼(percent encoding)需要將查詢字串引數變得更加神祕:

GET /_search?q=%2Bname%3Ajohn+%2Btweet%3Amary

“+”字首表示語句匹配條件必須被滿足。類似的”-“字首表示條件必須不被滿足。所有條件如果沒有+或-表示是可選的:匹配越多,相關的文件就越多。

_all欄位

返回包含”mary”字元的所有文件的簡單搜尋:

GET /_search?q=mary

在前一個例子中,我們搜尋tweet或name欄位中包含某個字元的結果。然而,這個語句返回的結果在三個不同的欄位中包含”mary”:

  1. 使用者的名字是“Mary”
  2. “Mary”發的六個推文
  3. 針對“@mary”的一個推文

Elasticsearch是如何設法找到三個不同欄位的結果的?

當你索引一個文件,Elasticsearch把所有字串欄位值連線起來放在一個大字串中,它被索引為一個特殊的欄位_all。例如,當索引這個文件:

{
    "tweet":    "However did I manage before Elasticsearch?",
    "date":     "2014-09-14",
    "name":     "Mary Jones",
    "user_id":  1
}

這好比我們增加了一個叫做_all的額外欄位值:

"However did I manage before Elasticsearch? 2014-09-14 Mary Jones 1"

若沒有指定欄位,查詢字串搜尋(即q=xxx)使用_all欄位搜尋。

更復雜的語句

下一個搜尋推特的語句:

_all field

  1. name欄位包含”mary”或”john”
  2. date晚於2014-09-10
  3. _all欄位包含”aggregations”或”geo”

+name:(mary john) +date:>2014-09-10 +(aggregations geo)

查詢字串搜尋允許任意使用者在索引中任何一個欄位上執行潛在的慢查詢語句,可能暴露私有資訊甚至使你的叢集癱瘓。