在ES執行分佈式搜索時,分佈式搜索操做須要分散到全部相關分片,若一個索引有3個主分片,每一個主分片有一個副本分片,那麼搜索請求會在這6個分片中隨機選擇3個分片,這3個分片有多是主分片也多是副本分片,而後收集全部分片的查詢結果。因此ES的搜索過程分爲兩個階段,Query階段和Fetch階段;ES有兩種搜索類型:query_then_fetch,dfs_query_then_fetch。html
1.Query階段java
1)轉發請求。在Query階段客戶端向ES節點發送,搜索請求,Coordinate節點接受客戶端搜索請求,Coordinate節點負責解析搜索請求,並在索引的全部主副本分片中隨機選擇分片,而且發送給分片所在的數據節點。api
2)執行查詢。接收到查詢請求的數據節點執行查詢操做,並對查詢結果進行排序,每一個節點都會根據請求中參數返回from+size個排序後的文檔Id和排序值給Coordinate節點。數組
2.Fetch階段分佈式
1)重排序。Coordinate節點收到數據節點返回的數據後,會按照返回的排序值對從全部分片取回的值從新進行排序,最終只選取客戶端須要的from+size個文檔的Id。性能
2)獲取文檔數據。Coordinate節點根據選取的文檔的Id,到相應的分片獲取詳細的文檔數據,最終將查詢到的結果返回給客戶端。學習
查詢結果解讀:fetch
{ "took":3, 查詢所用的毫秒數 "timed_out":false, 是否有分片超時,便是否只返回了部分結果 "_shards":{ "total":1, 一共查詢了多少分片 "successful":1, 多少分片成功返回 "skipped":0,跳過了多少分片 "failed":0 多少分片查詢失敗 }, "hits":{ "total":{ "value":1, 該搜索請求中返回的全部匹配的數量 "relation":"eq" 文檔與搜索值的關係,eq表示相等 }, "max_score":8.044733, 返回結果中文檔的最大得分 "hits":[ 查詢結果的文檔數組 { "_index":"kibana_sample_data_ecommerce", 查詢的索引 "_type":"_doc", 查詢的類型 "_id":"4X-j7XEB-r_IFm6PISqV", 返回文檔的主鍵 "_score":8.044733, 返回文檔的評分 "_source":{ 文檔的原始內容 "currency":"EUR", "customer_first_name":"Eddie", "customer_full_name":"Eddie Underwood", "customer_gender":"MALE" ...... } } ] } }
Query Then Fetch潛在的問題spa
1.深度分頁code
ES索引數據分佈在多個分片上,在查詢時,每一個分片都要查詢from+size個文檔,Coordinate節點會聚合全部的結果,因此Coordinate節點要處理查詢分片數*(from+size)個文檔記錄,對這些記錄進行從新排序,須要的size個文檔,from+size的值越大佔用內存越多,稱爲深度分頁問題,ES默認限制分頁的深度不能超過10000條,可經過max_result_window設置。
深度分頁解決辦法:
1)Search After
可使用Search After避免深度分頁的性能問題,實時獲取下一頁的文檔信息,search_after根據上一頁最後一個文檔的sort值來查詢下一頁,而且當索引數據有變化時,也能夠同步被查到,是一個實時查詢的方法。
例:http://127.0.0.1:9200/kibana_sample_data_ecommerce/_search
查詢參數:在使用Search_After查詢時,第一步查詢時須要指定sort字段,而且該sort字段的排序結果是惟一的,建議使用_id來進行sort,能夠指定多個sort字段。
{
"size": 1,
"query": {
"match": {
"currency": "EUR"
}
},
"sort": [
{
"order_id": {
"order": "asc"
}
}
]
}
返回中能夠看到第一頁查詢返回的sort值,查詢下一頁時使用該sort值進行文檔的定位,然後每一個查詢都會返回一個sort值,供下一頁進行定位使用。
"sort": [ "550375" ]
下一頁查詢:
{
"size": 1,
"query": {
"match": {
"currency": "EUR"
}
},
"search_after": [
550375
],
"sort": [
{
"order_id": {
"order": "asc"
}
}
]
}
Search_After存在的限制:
a.不能指定from值,即不能想翻到哪一頁就直接跳轉到那一頁,只能一頁一頁按照順序翻;
b.只能日後翻頁,不能往前翻頁。
2)Scroll API
scroll api能夠用於從單個搜索請求中檢索大量的結果,其原理是創建索引在某個時間點的快照,當快照創建後,以後的每次搜索都會在該快照上進行,對索引的全部新增操做都會被忽略,索引Scroll適合於處理大量數據,可是不能保證數據的實時性。
POST http://127.0.0.1:9200/kibana_sample_data_ecommerce/_search?scroll=1m
首次查詢時指定scroll=5m,表示當前搜索過時時間爲5分鐘,即查詢結果在搜到下一次請求以前會保存屢次時間,scroll的值不須要長到把整個快照的數據都處理完,只需保證下一次搜索請求到來以前能處理完前一批查詢結果便可。
{ "size": 2, "query": { "match" : { "currency" : "EUR" } } }
返回中能夠看到_scroll_id,total.value,scroll_id用於獲取下一批的查詢結果,total.value表示該查詢有總共多少個結果。
{ "_scroll_id":"DXF1ZXJ5QW5kRmV0Y2gBAAAAAAABAGUWdks0dUtFMHZTYmE1Rl9ucGp5X0hoUQ==", "took": 1, "timed_out": false, "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 }, "hits": { "total": { "value": 4675, "relation": "eq" }, } }
下一頁:
http://127.0.0.1:9200/_search/scroll
下一頁查詢的時候不用指定索引和查詢參數,只須要指定scroll時間和上一次請求返回的scroll_id,由於快照已經建好,只須要在快照上往下翻頁便可。每次執行該請求都會往下進行翻頁,直到查詢的結果爲空。
{ "scroll":"5m", "scroll_id":"DXF1ZXJ5QW5kRmV0Y2gBAAAAAAABAGUWdks0dUtFMHZTYmE1Rl9ucGp5X0hoUQ==" }
Scroll API存在的限制:當快照創建後,對索引有新的操做時,沒法被查詢到,因此不適合作實時查詢。
不一樣查詢的使用場景
通常查詢:須要獲取頂部的部分文檔,查詢索引最新的數據。
全量查詢:使用scroll,當須要導出所有數據,且對數據的實時性要求不高時。
分頁查詢:使用from+size,當from+size過大時,使用search after。
2.相關度評分不許問題
當搜索請求在多個shard進行數據查找時,每一個分片都會基於本身分片上的文檔數據進行相關度的計算,計算方法爲TD/IDF,
TF:詞頻,表示詞條在一個文檔中出現的頻率;IDF:逆文檔頻率,log(全本文檔數/詞條在全部文檔中出現的次數),表示該term在全部文檔中出現的頻率;若是查詢詞條在某一個文檔中出現的頻率(即TF)高,在所有文檔中出現的頻率低(即IDF)低,則代表該文檔的相關性高。
每一個分片計算IDF的時候只會基於本身分片上的數據進行計算,並不會包含其餘分片上的數據,因此這樣會致使相關性評分不許的狀況;特別在文檔總數不多狀況下,主分片數越多,相關性算分會越不許。
解決相關度評分不許問題的方法:
1)合理設置分片數量,保證數據均勻分佈。
當數據量不大時,能夠考慮僅設置一個主分數;當數據量較大時,保證文檔均勻的分佈在各個分片上。ES提供了
routing_partition_size越大,數據的分佈越均勻(【Elasticsearch學習】之一圖讀懂文檔索引全過程 中有說起)。routing_partition_size參數,
2)使用dfs_query_then_fetch
在搜索時,指定搜索的類型search_type=dfs_query_the_fetch,在搜索的時候,每一個分片會把每一個分片的TF和IDF進行蒐集,而後綜合全部的數據進行一次完整的相關性評分計算,可是通常不推薦,由於這樣會耗費較多的CPU和內存。