在ES執行分散式搜尋時,分散式搜尋操作需要分散到所有相關分片,若一個索引有3個主分片,每個主分片有一個副本分片,那麼搜尋請求會在這6個分片中隨機選擇3個分片,這3個分片有可能是主分片也可能是副本分片,然後收集所有分片的查詢結果。所以ES的搜尋過程分為兩個階段,Query階段和Fetch階段;ES有兩種搜尋型別:query_then_fetch,dfs_query_then_fetch。
1.Query階段
1)轉發請求。在Query階段客戶端向ES節點傳送,搜尋請求,Coordinate節點接受客戶端搜尋請求,Coordinate節點負責解析搜尋請求,並在索引的所有主副本分片中隨機選擇分片,並且傳送給分片所在的資料節點。
2)執行查詢。接收到查詢請求的資料節點執行查詢操作,並對查詢結果進行排序,每個節點都會根據請求中引數返回from+size個排序後的文件Id和排序值給Coordinate節點。
2.Fetch階段
1)重排序。Coordinate節點收到資料節點返回的資料後,會按照返回的排序值對從所有分片取回的值重新進行排序,最終只選取客戶端需要的from+size個文件的Id。
2)獲取文件資料。Coordinate節點根據選取的文件的Id,到相應的分片獲取詳細的文件資料,最終將查詢到的結果返回給客戶端。
查詢結果解讀:
{ "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潛在的問題
1.深度分頁
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和記憶體。