本篇主要介紹一下分佈式環境中搜索的兩階段執行過程。java
回顧咱們以前的CRUD操做,由於只對單個文檔進行處理,文檔的惟一性很容易肯定,而且很容易知道是此文檔在哪一個node,哪一個shard中。node
但搜索比CRUD複雜,符合搜索條件的文檔,可能散落在各個node、各個shard中,咱們須要找到匹配的文檔,而且把從各個node,各個shard返回的結果進行彙總、排序,組成一個最終的結果排序列表,纔算完成一個搜索過程。咱們將按兩階段的方式對這個過程進行講解。算法
假定咱們的ES集羣有三個node,number_of_primary_shards爲3,replica shard爲1,咱們執行一個這樣的查詢請求:數據庫
GET /music/children/_search { "from": 980, "size": 20 }
查詢階段的過程示意圖以下:segmentfault
補充說明:服務器
在完成了查詢階段後,此時Coordinate Node已經獲得查詢的列表,但列表內的元素只有文檔ID和_score信息,並沒有實際的_source內容,取回階段就是根據文檔ID,取到完整的文檔對象的過程。以下圖所示:微信
前面幾篇有提到deep paging的問題,咱們在這裏又複習一遍,使用from和size進行分頁時,傳遞信息給Coordinate Node的每一個shard,都建立了一個from + size長度的隊列,而且Coordinate Node須要對全部傳過來的數據進行排序,工做量爲number_of_shards * (from + size),而後從裏面挑出size數量的文檔,若是from值特別大,那麼會帶來極大的硬件資源浪費,鑑於此緣由,強烈建議不要使用深分頁。網絡
不過深分頁操做不多符合人的行爲,翻幾頁還看不到想要的結果,人的第一反應是換一個搜索條件,只有機器人或爬蟲才這麼不知疲倦地一直翻頁直到服務器崩潰。session
查詢時使用preference參數,能夠影響哪些shard能夠用來執行搜索操做,6.1.0版本後,許多參數值已聲明爲棄用,咱們挑幾個目前還在使用的簡單介紹一下:架構
假如兩個文檔有相同的字段值,而且時間戳也同樣,若是按時間戳字段來排序,因爲請求是在全部可用的shard上輪詢的,可能存在一種狀況:這兩個文檔記錄在不一樣的shard之間保存的順序不相同。結果就是同一個條件的查詢,若是執行屢次,分配在primary shard獲得的是一種順序,分配在replica shard又是另外一個順序,這個就是所謂的bouncing results問題。
如何避免:讓同一個用戶始終使用同一個shard,就能夠避免這種問題,常見的作法是preference設置爲sessionid或userid,如:
GET /music/children/_search?preference=10086 { "from": 980, "size": 20 }
咱們回顧查詢階段和取回階段,必須全部的操做都完成了,纔給客戶端返回結果,若是中途有shard在執行特別重的任務,致使查詢很慢怎麼辦?會拖慢整個集羣嗎?
若是是高併發場景,那極有可能,由於某一個節點慢,整個查詢請求堆積,拖死集羣都有可能。
爲了防止這一狀況,咱們使用timeout參數,告訴shard容許處理數據的最大時間,時間一到,執行關門動做,能有多少數據返回多少數據,剩下的不要了,這樣能夠確保集羣是穩定運行的,以下圖所示:
在設計大規模數據搜索時,咱們爲了實現數據集中性,索引時會按必定規則將數據進行存儲,好比訂單數據,咱們會按userid爲route key,每一個userid的訂單數據,都放在同一個shard上,既然存儲時使用了route key,那麼搜索時一樣使用route key,可讓查詢只搜索相關的shard,如:
GET /music/children/_search?routing=10086 { "from": 980, "size": 20 }
這樣因爲精準到具體的shard,能夠極大的縮小搜索範圍,數據量越大,效果越明顯。
默認的搜索類型是query_then_fetch,咱們還能夠選擇dfs_query_then_fetch,這個有預查詢階段,能夠從全部相關shard中獲取詞頻來計算全局詞頻,能夠提高revelance sort精準度。
若是咱們要把大批量的數據從ES集羣中取出,用來執行一些計算,一次性取完確定不合適,IO壓力過大,性能容易出問題,分頁查詢又容易形成deep paging的問題。通常推薦使用scroll查詢,一批一批的查,直到全部數據都查詢完。
咱們假定每次取10條數據,時間窗口爲1秒
請求以下:
GET /music/children/_search?scroll=1s { "size": 10 }
響應以下(結果有刪減):
{ "_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帶上,直到數據查詢完成爲止。
scroll查詢看起來像分頁,但使用場景不同,分頁主要是按頁展現數據,主要受衆是人,scroll一批一批的獲取數據,主要受衆通常是數據分析的系統,是給系統用的。
性能也不一樣,前面咱們瞭解後,分頁查詢隨着頁數的加深,壓力愈來愈大,而scroll是基於_doc排序的數據處理,特別適用於大批量數據的獲取分析。
本篇詳細介紹了查詢的兩階段過程,以及可以影響查詢行爲的一些參數設置,歷經多個版本迭代,有些preference參數已經不用了,瞭解一下就行,另外介紹了bouncing results產生的原理及規避辦法,最後介紹了一下大批量數據查詢利器scroll的簡單用法。
專一Java高併發、分佈式架構,更多技術乾貨分享與心得,請關注公衆號:Java架構社區
能夠掃左邊二維碼添加好友,邀請你加入Java架構社區微信羣共同探討技術