Elasticsearch搜索執(zhí)行過程及scroll游標查詢

概要

本篇主要介紹一下分布式環(huán)境中搜索的兩階段執(zhí)行過程。

兩階段搜索過程

回顧我們之前的CRUD操作,因為只對單個文檔進行處理,文檔的唯一性很容易確定,并且很容易知道是此文檔在哪個node,哪個shard中。

但搜索比CRUD復(fù)雜,符合搜索條件的文檔,可能散落在各個node、各個shard中,我們需要找到匹配的文檔,并且把從各個node,各個shard返回的結(jié)果進行匯總、排序,組成一個最終的結(jié)果排序列表,才算完成一個搜索過程。我們將按兩階段的方式對這個過程進行講解。

查詢階段

假定我們的ES集群有三個node,number_of_primary_shards為3,replica shard為1,我們執(zhí)行一個這樣的查詢請求:

GET /music/children/_search
{
  "from": 980,
  "size": 20
}

查詢階段的過程示意圖如下:

image
  1. Java客戶端發(fā)起查詢請求,接受請求的node-1成為Coordinate Node(協(xié)調(diào)者),該node會創(chuàng)建一個priority queue,長度為from + size即1000。
  2. Coordinate Node將請求分發(fā)到所有的primary shard或replica shard中,每個shard在本地創(chuàng)建一個同樣大小的priority queue,長度也為from + size,用于存儲該shard執(zhí)行查詢的結(jié)果。
  3. 每個shard將各自priority queue的元素返回給Coordinate Node,元素內(nèi)只包含文檔的ID和排序值(如_score),Coordinate Node將合并所有的元素到自己的priority queue中,并完成排序動作,最終根據(jù)from、size值對結(jié)果進行截取。

補充說明:

  1. 哪個node接收客戶端的請求,該node就會成為Coordinate Node。
  2. Coordinate Node轉(zhuǎn)發(fā)請求時,會根據(jù)負載均衡算法分配到同一分片的primary shard或replica shard上,為什么說replica值設(shè)置得大一些可以增加系統(tǒng)吞吐量的原理就在這里,Coordinate Node的查詢請求負載均衡算法會輪詢所有的可用shard,并發(fā)場景時就會有更多的硬件資源(CPU、內(nèi)存,IO)會參與其中,系統(tǒng)整體的吞吐量就能提升。
  3. 此查詢過程Coordinate Node得到是輕量級的元素信息,只包含文檔ID和_score這些信息,這樣可以減輕網(wǎng)絡(luò)負載,因為分頁過程中,大部分的數(shù)據(jù)是會丟棄掉的。

取回階段

在完成了查詢階段后,此時Coordinate Node已經(jīng)得到查詢的列表,但列表內(nèi)的元素只有文檔ID和_score信息,并無實際的_source內(nèi)容,取回階段就是根據(jù)文檔ID,取到完整的文檔對象的過程。如下圖所示:

image
  1. Coordinate Node根據(jù)from、size信息截取要取回文檔的ID,如{"from": 980, "size": 20},則取第981到第1000這20條數(shù)據(jù),其余丟棄,from/size為空則默認取前10條,向其他shard發(fā)出mget請求。
  2. shard接收到請求后,根據(jù)_source參數(shù)(可選)加載文檔信息,返回給Coordinate Node。
  3. 一旦所有的shard都返回了結(jié)果,Coordinate Node將結(jié)果返回給客戶端。

前面幾篇有提到deep paging的問題,我們在這里又復(fù)習一遍,使用from和size進行分頁時,傳遞信息給Coordinate Node的每個shard,都創(chuàng)建了一個from + size長度的隊列,并且Coordinate Node需要對所有傳過來的數(shù)據(jù)進行排序,工作量為number_of_shards * (from + size),然后從里面挑出size數(shù)量的文檔,如果from值特別大,那么會帶來極大的硬件資源浪費,鑒于此原因,強烈建議不要使用深分頁。

不過深分頁操作很少符合人的行為,翻幾頁還看不到想要的結(jié)果,人的第一反應(yīng)是換一個搜索條件,只有機器人或爬蟲才這么不知疲倦地一直翻頁直到服務(wù)器崩潰。

preference設(shè)置

查詢時使用preference參數(shù),可以影響哪些shard可以用來執(zhí)行搜索操作,6.1.0版本后,許多參數(shù)值已聲明為棄用,我們挑幾個目前還在使用的簡單介紹一下:

  • _only_local:只搜索當前node中的shard
  • _local:優(yōu)先搜索當前node中的shard,搜不到再去其他的shard
  • _prefer_nodes:abc,xyz:優(yōu)先從指定的abc/xyz節(jié)點上搜索,如果兩個節(jié)點都有存在數(shù)據(jù)的shard,隨機從里面挑一個節(jié)點執(zhí)行搜索
  • _only_nodes:abc,xyz,...:只在符合通配abc、xyz名稱的節(jié)點上搜索,如果多個節(jié)點都有存在數(shù)據(jù)的shard,隨機從里面挑一個節(jié)點執(zhí)行搜索
  • _shards:2,3:指定shard進行搜索,這個條件如與其他條件搭配使用,此條件要寫在前面,如_shards:2,3|_local
  • 自定義字符串:一般用sessionid或userid

bouncing results問題

假如兩個文檔有相同的字段值,并且時間戳也一樣,如果按時間戳字段來排序,由于請求是在所有可用的shard上輪詢的,可能存在一種情況:這兩個文檔記錄在不同的shard之間保存的順序不相同。結(jié)果就是同一個條件的查詢,如果執(zhí)行多次,分配在primary shard得到的是一種順序,分配在replica shard又是另一個順序,這個就是所謂的bouncing results問題。

如何避免:讓同一個用戶始終使用同一個shard,就可以避免這種問題,常見的做法是preference設(shè)置為sessionid或userid,如:

GET /music/children/_search?preference=10086
{
  "from": 980,
  "size": 20
}

超時問題

我們回顧查詢階段和取回階段,必須所有的操作都完成了,才給客戶端返回結(jié)果,如果中途有shard在執(zhí)行特別重的任務(wù),導(dǎo)致查詢很慢怎么辦?會拖慢整個集群嗎?

如果是高并發(fā)場景,那極有可能,因為某一個節(jié)點慢,整個查詢請求堆積,拖死集群都有可能。

為了防止這一情況,我們使用timeout參數(shù),告訴shard允許處理數(shù)據(jù)的最大時間,時間一到,執(zhí)行關(guān)門動作,能有多少數(shù)據(jù)返回多少數(shù)據(jù),剩下的不要了,這樣可以確保集群是穩(wěn)定運行的,如下圖所示:

image

routing

在設(shè)計大規(guī)模數(shù)據(jù)搜索時,我們?yōu)榱藢崿F(xiàn)數(shù)據(jù)集中性,索引時會按一定規(guī)則將數(shù)據(jù)進行存儲,比如訂單數(shù)據(jù),我們會按userid為route key,每個userid的訂單數(shù)據(jù),都放在同一個shard上,既然存儲時使用了route key,那么搜索時同樣使用route key,可以讓查詢只搜索相關(guān)的shard,如:

GET /music/children/_search?routing=10086
{
  "from": 980,
  "size": 20
}

這樣由于精準到具體的shard,可以極大的縮小搜索范圍,數(shù)據(jù)量越大,效果越明顯。

搜索類型

默認的搜索類型是query_then_fetch,我們還可以選擇dfs_query_then_fetch,這個有預(yù)查詢階段,可以從所有相關(guān)shard中獲取詞頻來計算全局詞頻,可以提升revelance sort精準度。

scroll游標查詢

如果我們要把大批量的數(shù)據(jù)從ES集群中取出,用來執(zhí)行一些計算,一次性取完肯定不合適,IO壓力過大,性能容易出問題,分頁查詢又容易造成deep paging的問題。一般推薦使用scroll查詢,一批一批的查,直到所有數(shù)據(jù)都查詢完。

原理

  • scroll查詢會先做查詢初始化,然后再批量地拉取結(jié)果,有點像數(shù)據(jù)庫的cursor。
  • scroll查詢會取某個時間點的快照數(shù)據(jù),查詢初始化后索引上的數(shù)據(jù)發(fā)生了變化,快照數(shù)據(jù)還是原來的,有點像數(shù)據(jù)庫的索引視圖。
  • scroll查詢用字段_doc排序,去掉了全局排序,性能比較高。
  • scroll查詢要設(shè)置過期時間,每次搜索在這個時間內(nèi)完成即可。

示例

我們假定每次取10條數(shù)據(jù),時間窗口為1秒
請求如下:

GET /music/children/_search?scroll=1s
{
  "size": 10
}

響應(yīng)如下(結(jié)果有刪減):

{
  "_scroll_id": "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAABJQFkExczF1dXM3VHB1RFNpVDR4RkxPb1EAAAAAAAASUhZBMXMxdXVzN1RwdURTaVQ0eEZMT29RAAAAAAAAElMWQTFzMXV1czdUcHVEU2lUNHhGTE9vUQAAAAAAABJUFkExczF1dXM3VHB1RFNpVDR4RkxPb1EAAAAAAAASURZBMXMxdXVzN1RwdURTaVQ0eEZMT29R",
  "took": 2,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 4,
    "max_score": 1,
    "hits": [
      {
        "_index": "music",
        "_type": "children",
        "_id": "2",
        "_score": 1,
        "_source": {
          "name": "wake me, shark me",
          "content": "don't let me sleep too late, gonna get up brightly early in the morning",
          "language": "english",
          "length": "55",
          "likes": 0,
          "author": "John Smith"
        }
      }
    ]
  }
}

注意那個scroll_id,下次再查詢時,只要帶上這個就行了

GET /_search/scroll
{
    "scroll": "1s", 
    "scroll_id" : "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAABJQFkExczF1dXM3VHB1RFNpVDR4RkxPb1EAAAAAAAASUhZBMXMxdXVzN1RwdURTaVQ0eEZMT29RAAAAAAAAElMWQTFzMXV1czdUcHVEU2lUNHhGTE9vUQAAAAAAABJUFkExczF1dXM3VHB1RFNpVDR4RkxPb1EAAAAAAAASURZBMXMxdXVzN1RwdURTaVQ0eEZMT29R"
}

每次的查詢,都把最新的scroll_id帶上,直到數(shù)據(jù)查詢完成為止。

scroll查詢看起來像分頁,但使用場景不一樣,分頁主要是按頁展示數(shù)據(jù),主要受眾是人,scroll一批一批的獲取數(shù)據(jù),主要受眾一般是數(shù)據(jù)分析的系統(tǒng),是給系統(tǒng)用的。
性能也不同,前面我們了解后,分頁查詢隨著頁數(shù)的加深,壓力越來越大,而scroll是基于_doc排序的數(shù)據(jù)處理,特別適用于大批量數(shù)據(jù)的獲取分析。

小結(jié)

本篇詳細介紹了查詢的兩階段過程,以及能夠影響查詢行為的一些參數(shù)設(shè)置,歷經(jīng)多個版本迭代,有些preference參數(shù)已經(jīng)不用了,了解一下就行,另外介紹了bouncing results產(chǎn)生的原理及規(guī)避辦法,最后介紹了一下大批量數(shù)據(jù)查詢利器scroll的簡單用法。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容