1. 程式人生 > >Elasticsearch之四種查詢型別和搜尋原理

Elasticsearch之四種查詢型別和搜尋原理

Elasticsearch Client傳送搜尋請求,某個索引庫,一般預設是5個分片(shard)。

  它返回的時候,由各個分片彙總結果回來。

   官網API

https://www.elastic.co/guide/en/elasticsearch/client/java-api/2.4/index.html

es 在查詢時, 可以指定搜尋型別為下面四種:
  QUERY_THEN_FETCH
  QUERY_AND_FEATCH
  DFS_QUERY_THEN_FEATCH
  DFS_QUERY_AND_FEATCH

那麼這 4 種搜尋型別有什麼區別?

在講這四種搜尋型別的區別之前, 先分析一下分散式搜尋背景介紹:
  ES 天生就是為分散式而生, 但分散式有分散式的缺點。 比如要搜尋某個單詞, 但是資料卻分別在 5 個分片(Shard)上面, 這 5 個分片可能在 5 臺主機上面。 因為全文搜尋天生就要排序( 按照匹配度進行排名) ,但資料卻在 5 個分片上, 如何得到最後正確的排序呢? ES是這樣做的, 大概分兩步:
  step1、 ES 客戶端將會同時向 5 個分片發起搜尋請求。
  step2、 這 5 個分片基於本分片的內容獨立完成搜尋, 然後將符合條件的結果全部返回。

客戶端將返回的結果進行重新排序和排名,最後返回給使用者。也就是說,ES的一次搜尋,是一次scatter/gather過程(這個跟mapreduce也很類似)

  然而這其中有兩個問題:
  第一、 數量問題。 比如, 使用者需要搜尋"衣服", 要求返回符合條件的前 10 條。 但在 5個分片中, 可能都儲存著衣服相關的資料。 所以 ES 會向這 5 個分片都發出查詢請求, 並且要求每個分片都返回符合條件的 10 條記錄。當ES得到返回的結果後,進行整體排序,然後取最符合條件的前10條返給使用者。 這種情況, ES 中 5 個 shard 最多會收到 10*5=50條記錄, 這樣返回給使用者的結果數量會多於使用者請求的數量。
  第二、 排名問題。 上面說的搜尋, 每個分片計算符合條件的前 10 條資料都是基於自己分片的資料進行打分計算的。計算分值使用的詞頻和文件頻率等資訊都是基於自己分片的資料進行的, 而 ES 進行整體排名是基於每個分片計算後的分值進行排序的(相當於打分依據就不一樣, 最終對這些資料統一排名的時候就不準確了), 這就可能會導致排名不準確的問題。如果我們想更精確的控制排序, 應該先將計算排序和排名相關的資訊( 詞頻和文件頻率等打分依據) 從 5 個分片收集上來, 進行統一計算, 然後使用整體的詞頻和文件頻率為每個分片中的資料進行打分, 這樣打分依據就一樣了。



Elasticsearch在搜尋過程中存在以下幾個問題:

  (1)返回資料量問題

    如果資料分散在預設的5個分片上,ES會向5個分片同時發出請求,每個分片都返回10條資料,最終會返回總資料為:5 * 10 = 50條資料,遠遠大於使用者請求。

  (2)返回資料排名問題

    每個分片計算符合條件的前10條資料都是基於自己分片的資料進行打分計算的。計算分值(score)使用的詞頻和文件頻率等資訊都是基於自己分片的資料進行的,而ES進行整體排名是基於排名是基於每個分片計算後的分值進行排序的(打分依據就不一致,最終對這些資料統一排名的時候就不準確了)

=============================================================================
再舉個例子解釋一下【 排名問題】:
假設某學校有一班和二班兩個班級。
期末考試之後, 學校要給全校前十名學員發獎金。
但是一班和二班考試的時候使用的不是一套試卷。
一班: 使用的是 A 卷【 A 卷偏容易】
二班: 使用的是 B 卷【 B 卷偏難】
結果就是一班的最高分是 100 分, 最低分是 80 分。
二班的最高分是 70 分, 最低分是 30 分。

這樣全校前十名就都是一班的學員了。 這顯然是不合理的。
因為一班和二班的試卷難易程度不一樣, 也就是打分依據不一樣, 所以不能放在一塊排名。
【 這個就解釋了剛才的排名問題】
如果想要保證排名準確的話, 需要保證一班和二班使用的試卷內容一樣。
可以這樣做, 把 A 卷和 B 卷的內容組合到一塊, 作為 C 卷。
一班和二班考試都使用 C 卷, 這樣他們的打分依據就一樣了, 最終再根據所有學員的成績排

名求前十名就準確合理了。
=============================================================================

 這兩個問題, ES 也沒有什麼較好的解決方法, 最終把選擇的權利交給使用者, 方法就是在搜尋的時候指定 search type。

Elasticsearch在搜尋問題的解決思路

 (1)返回資料數量問題

    第一步:先從每個分片彙總查詢的資料id,進行排名,取前10條資料

    第二步:根據這10條資料id,到不同分片獲取資料

  (2)返回資料排名問題

    將各個分片打分標準統一

 Elasticsearch的搜尋型別(SearchType型別)

1、 query and fetch
  向索引的所有分片 ( shard)都發出查詢請求, 各分片返回的時候把元素文件 ( document)和計算後的排名資訊一起返回。
  這種搜尋方式是最快的。 因為相比下面的幾種搜尋方式, 這種查詢方法只需要去 shard查詢一次。 但是各個 shard 返回的結果的數量之和可能是使用者要求的 size 的 n 倍。
  優點:這種搜尋方式是最快的。因為相比後面的幾種es的搜尋方式,這種查詢方法只需要去shard查詢一次。
  缺點:返回的資料量不準確, 可能返回(N*分片數量)的資料並且資料排名也不準確,同時各個shard返回的結果的數量之和可能是使用者要求的size的n倍。
   

2、 query then fetch( es 預設的搜尋方式)
  如果你搜索時, 沒有指定搜尋方式, 就是使用的這種搜尋方式。 這種搜尋方式, 大概分兩個步驟:
  第一步, 先向所有的 shard 發出請求, 各分片只返回文件 id(注意, 不包括文件 document)和排名相關的資訊(也就是文件對應的分值), 然後按照各分片返回的文件的分數進行重新排序和排名, 取前 size 個文件。
  第二步, 根據文件 id 去相關的 shard 取 document。 這種方式返回的 document 數量與使用者要求的大小是相等的。
  優點:
    返回的資料量是準確的。
  缺點:
    效能一般,並且資料排名不準確。

3、 DFS query and fetch
  這種方式比第一種方式多了一個 DFS 步驟,有這一步,可以更精確控制搜尋打分和排名。也就是在進行查詢之前, 先對所有分片傳送請求, 把所有分片中的詞頻和文件頻率等打分依據全部彙總到一塊, 再執行後面的操作、
  優點:
    資料排名準確
  缺點:
    效能一般
    返回的資料量不準確, 可能返回(N*分片數量)的資料

4、 DFS query then fetch
  比第 2 種方式多了一個 DFS 步驟。
  也就是在進行查詢之前, 先對所有分片傳送請求, 把所有分片中的詞頻和文件頻率等打分依據全部彙總到一塊, 再執行後面的操作、

  優點
    返回的資料量是準確的
    資料排名準確
  缺點
    效能最差【 這個最差只是表示在這四種查詢方式中效能最慢, 也不至於不能忍受,如果對查詢效能要求不是非常高, 而對查詢準確度要求比較高的時候可以考慮這個】

  DFS 是一個什麼樣的過程?
  從 es 的官方網站我們可以發現, DFS 其實就是在進行真正的查詢之前, 先把各個分片的詞頻率和文件頻率收集一下, 然後進行詞搜尋的時候, 各分片依據全域性的詞頻率和文件頻率進行搜尋和排名。 顯然如果使用 DFS_QUERY_THEN_FETCH 這種查詢方式, 效率是最低的,因為一個搜尋, 可能要請求 3 次分片。 但, 使用 DFS 方法, 搜尋精度是最高的。

  總結一下, 從效能考慮 QUERY_AND_FETCH 是最快的, DFS_QUERY_THEN_FETCH 是最慢的。從搜尋的準確度來說, DFS 要比非 DFS 的準確度更高。

       檢索出結果後,通過response..getHits()可以得到所有的SearchHit,得到Hit後,便可迭代Hit取到對應的Document,轉化成為需要的實體。  前面提到如何進行搜尋,並將SearchRequestBuilder的一些方法進行了列舉,本文呼叫了SearchRequestBuilder的用於高亮的方法,處理了檢索中的高亮問題:

[java] view plain copy  print?
  1. SearchResponse response1 = client.prepareSearch("user")    
  2.         .setTypes("tb_person0""tb_person1""tb_person2""tb_person3""tb_person4")    
  3.         .setSearchType(SearchType.DFS_QUERY_THEN_FETCH)    
  4.         .setQuery(QueryBuilders.fieldQuery("name""張三"))             // Query  
  5.         .addHighlightedField("name")    
  6.         .setHighlighterPreTags("<span style="color:red">")    
  7.         .setHighlighterPostTags("</span>")    
  8.         .setFilter(FilterBuilders.rangeFilter("age").from(20).to(22))   // Filter  
  9.         .setFrom(0).setSize(60).setExplain(true)    
  10.         .execute()    
  11.         .actionGet();    
  12. SearchHits hits1 = response1.getHits();      
  13.       for(SearchHit hit : hits1){      
  14.     String json = hit.getSourceAsString();    
  15.     Person newPerson = mapper.readValue(json,    
  16.             Person.class);    
  17.           Map<String, HighlightField> result = hit.highlightFields();      
  18.           HighlightField titleField = result.get("name");      
  19.           Text[] titleTexts =  titleField.fragments();      
  20.           String name = "";    
  21.           for(Text text : titleTexts){      
  22.               name += text;    
  23.           }      
  24.           newPerson.setName(name);    
  25.     System.out.println("namett" + newPerson.getName());    
  26.     System.out.println("sextt" + newPerson.getSex());    
  27.     System.out.println("agett" + newPerson.getAge());    
  28.     System.out.println("isStudenttt" + newPerson.getIsStudent());    
  29.           System.out.println("--------------------------");      
  30.       }   
        addHighlightedField(String fieldName)指明要進行高亮處理的Field;setHighlighterPreTags設定了高亮文字的字首;setHighlighterPostTags設定了高亮文字的字尾。

  取得hit後,使用hit.highlightFields()取得結果中進行了高亮標識的域名-域值對,然後對這些域名-域值對進行分析得到高亮的域結果。

  關於es的四種查詢API程式設計,請見我下面寫的部落格