【Elasticsearch學習】文檔搜索全過程
- 2020 年 5 月 10 日
- 筆記
- ElasticSeach, Elasticsearch學習, query_then_fetch, scroll, search_after
在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值來查詢下一頁,並且當索引數據有變化時,也可以同步被查到,是一個實時查詢的方法。
例://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 //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" }, } }
下一頁:
//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和記憶體。