天天看點

Elasticsearch Search API之(Request Body Search 查詢主體)

本文有點長,看完可能需要點耐心,本文詳細介紹了es三種分頁方式、排序、from、size、source filter、dov values fields、post filter、高亮顯示、rescoring、search type、scroll、preference、preference、explain、version、index boost、min_score、names query、Inner hits、field collapsing、Search After。

大家可以根據關鍵字,選擇對應感興趣的内容進行閱讀。上述内容基本都給出了JAVA使用示例。

本節将詳細介紹Elasticsearch Search API的查詢主體,定制化查詢條件的實作主體。

1、query

搜尋請求體中查詢條件使用Elasticsearch DSL查詢文法來定義。通過使用query來定義查詢體。

GET /_search
    {
            "query" : {
                "term" : { "user" : "kimchy" }
            }
    }           

2、From / Size

ElasticSearch的一種分頁文法。通過使用from和size參數來對結果集進行分頁。from設定第一條資料的偏移量。size設定本批資料傳回的條數(針對每個分片生效),由于Elasticsearch天生就是分布式的,通過設定主分片個數來進行資料水準切分,一個查詢請求通常需要從多個背景節點(分片)進行資料彙聚,故此方式會遇到分布式資料庫一個通用的問題:深度分頁。Elasticsearch提供了另外一種分頁方式,滾動API(Scroll),後續會詳細分析。注意:from + size 不能超過index.max_result_window配置項的值,其預設值為10000。

3、sort (排序)

與傳統關系型資料庫類似,elasticsearch支援根據一個或多個字段進行排序,同時支援asc升序或desc降序。另外Elasticsearch可以按照_score(基于得分)的排序,預設值。如果使用了排序,每個文檔的排序值(字段為sort)也會作為響應的一部分傳回。

3.1 排序順序

Elasticsearch提供了兩種排序順序,SortOrder.ASC(asc)升序、SortOrder.DESC(desc)降序,如果排序類型為_score,其預設排序順序為降序(desc),

如果排序類型為字段,則預設排序順序為升序(asc)。

3.2 排序模型選型

Elasticsearch支援按數組或多值字段進行排序。模式選項控制選擇的數組值,以便對它所屬的文檔進行排序。模式選項可以有以

下值:

  • min 使用數組中最小的值參與排序。
  • max 使用數組中最大的值參與排序。
  • sum 使用數組中的總和參與排序。
  • avg 使用數組中的平均值參與排序。
  • median 使用數組中的中位數參與排序。
  • 其示例如下:
PUT /my_index/_doc/1?refresh
{
   "product": "chocolate",
   "price": [20, 4]
}
POST /_search
{
   "query" : {
      "term" : { "product" : "chocolate" }
   },
   "sort" : [
      {"price" : {"order" : "asc", "mode" : "avg"}}   // @1
   ]
}            

如果是一個數組類型的值,參與排序,通常會對該數組元素進行一些計算得出一個最終參與排序的值,例如取平均數、最大值、最小值,求和等運算。es通過排序模型mode來指定。

3.3 嵌套字段排序

Elasticsearch還支援在一個或多個嵌套對象内部的字段進行排序。一個嵌套查詢提包含如下選項(參數):

  • path

    定義要排序的嵌套對象。排序字段必須是這個嵌套對象中的一個直接字段(非嵌套字段),并且排序字段必須存在。

  • filter

    定義過濾上下文,定義排序環境中的過濾上下文。

  • max_children

    排序是要考慮根文檔下子屬性文檔的最大個數,預設為無限制。

  • nested

    排序體支援嵌套。

"sort" : [
  {
    "parent.child.age" : {      // @1
        "mode" :  "min",
         "order" : "asc",
         "nested": {                // @2
            "path": "parent",
            "filter": {
                "range": {"parent.age": {"gte": 21}}
            },
            "nested": {                            // @3
                "path": "parent.child",
                "filter": {
                    "match": {"parent.child.name": "matt"}
                }
            }
         }
    }
  }
]           

代碼@1:排序字段名,支援級聯表示字段名。

代碼@2:通過nested屬性定義排序嵌套文法提,其中path指定目前的嵌套對象,filter定義過濾上下文,@3内部可以再通過nested屬性再次嵌套定義。

3.4 missing values

由于es的索引,類型下的字段可以在索引文檔時動态增加,那如果有些文檔沒有包含排序字段,這部分文檔的順序如何确定呢?es通過missing屬性來确定,其可選值為:

  • _last

    預設值,排在最後。

  • _first

    排在最前。

3.5 ignoring unmapped fields

預設情況下,如果排序字段為未映射的字段将抛出異常。可通過unmapped_type來忽略該異常,該參數指定一個類型,也就是告訴ES如果未找該字段名的映射,就認為該字段是一個unmapped_type指定的類型,所有文檔都未存該字段的值。

3.6 Geo sorting

地圖類型排序,該部分将在後續專題介紹geo類型時講解。

4、字段過濾(_source與stored_fields)

預設情況下,對命中的結果會傳回_source字段下的所有内容。字段過濾機制允許使用者按需要傳回_source字段裡面部分字段。其過濾設定機制已在在《Elasticsearch Document Get API詳解、原理與示例》中已詳細介紹,在這裡就不重複介紹了。

5、 Doc Value Fields

使用方式如下:

GET /_search
{
    "query" : {
        "match_all": {}
    },
    "docvalue_fields" : [
        {
            "field": "my_date_field",   
            "format": "epoch_millis" 

        }
    ]
}           

通過使用docvalue_fields指定需要轉換的字段與格式,doc value fields對于在映射檔案中定義stored=false的字段同樣生效。字段支援用通配符,例如"field":"myfield*"。docvalue_fields中指定的字段并不會改變_souce字段中的值,而是使用fields傳回值進行額外傳回。

java執行個體代碼片段如下(完整的Demo示例将在文末給出):

SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.query(QueryBuilders.termQuery("user", "dingw"))
        .sort(new FieldSortBuilder("post_date").order(SortOrder.DESC))
        .docValueField("post_date", "epoch_millis")           

其傳回結果如下:

{
    "took":88,
    "timed_out":false,
    "_shards":{
        "total":5,
        "successful":5,
        "skipped":0,
        "failed":0
    },
    "hits":{
        "total":2,
        "max_score":null,
        "hits":[
            {
                "_index":"twitter",
                "_type":"_doc",
                "_id":"11",
                "_score":null,
                "_source":{
                    "post_date":"2009-11-19T14:12:12",
                    "message":"test bulk update",
                    "user":"dingw"
                },
                "fields":{
                    "post_date":[
                        "1258639932000"
                    ]
                },
                "sort":[
                    1258639932000
                ]
            },
            {
                "_index":"twitter",
                "_type":"_doc",
                "_id":"12",
                "_score":null,
                "_source":{
                    "post_date":"2009-11-18T14:12:12",
                    "message":"test bulk",
                    "user":"dingw"
                },
                "fields":{
                    "post_date":[
                        "1258553532000"
                    ]
                },
                "sort":[
                    1258553532000
                ]
            }
        ]
    }
}           

6、Post Filter

post filter對查詢條件命中後的文檔再做一次篩選。

GET /shirts/_search
{
  "query": {
    "bool": {
      "filter": {
        "term": { "brand": "gucci" }      // @1
      }
    }
  },
  "post_filter": {     // @2
    "term": { "color": "red" }
  }
}           

首先根據@1條件對索引進行檢索,然後得到比對的文檔後,再利用@2過濾條件對結果再一次篩選。

7、Highlighting(查詢結果高亮顯示)

7.1 Es支援的高亮分析器

用于對查詢結果中對查詢關鍵字進行高亮顯示,以指明查詢條件在查詢結果中比對的部分處以另外的顔色突出顯示。

注意:高亮顯示器在提取要高亮顯示的術語時不能反映查詢的布爾邏輯。是以對于一些複雜的布爾查詢(例如嵌套的布爾查詢,或使用minimum_should_match等查詢)可能高亮顯示會出現一些誤差。

高亮顯示需要字段的實際内容。如果字段沒有存儲(映射沒有将store設定為true),則從_source中提取相關字段。

Elasticsearch支援三種高亮顯示工具,通過為每個字段指定type來使用。

  • unified highlighter

    使用Lucene unified高亮顯示器。首先将文本分解成句子并使用BM25算法對單個句子進行評分,就好像它們是語料庫中的文檔一樣。支援精确的短語和多術語(模糊、字首、正規表達式)高亮顯示。這是es預設的高亮顯示器。

  • plain highlighter

    使用standard Lucene highlighter(Lucene标準高亮顯示器)。plain highlighter最适合單個字段的比對高亮顯示需求。為了準确地反映查詢邏輯,它在記憶體中建立一個很小的索引,并通過Lucene的查詢執行計劃重新運作原來的查詢條件,以便擷取目前文檔的更低級别的比對資訊。如果需要對多個字段進行高亮顯示,建議還是使用unified highlighter或term_vector fields。

plain highlighter高亮方式是個實時分析處理高亮器。即使用者在查詢的時候,搜尋引擎查詢到了目标資料docid後,将需要高亮的字段資料提取到記憶體,再調用該字段的分析器進行處理,分析器對文本進行分析處理,分析完成後采用相似度算法計算得分最高的前n組并高亮段傳回資料。假設使用者搜尋的都是比較大的文檔同時需要進行高亮。按照一頁查詢40條(每條資料20k)的方式進行顯示,即使相似度計算以及搜尋排序不耗時,整個查詢也會被高亮拖累到接近兩秒。highlighter高亮器是實時分析高亮器,這種實時分析機制會讓ES占用較少的IO資源同時也占用較少的存儲空間(詞庫較全的話相比fvh方式能節省一半的存儲空間),其實時計算高亮是采用cpu資源來緩解io壓力,在高亮字段較短(比如高亮文章的标題)時候速度較快,同時因io通路的次數少,io壓力較小,有利于提高系統吞吐量。

參考資料:

https://blog.csdn.net/kjsoftware/article/details/76293204
  • fast vector highlighter

    使用lucene fast vector highlingter,基于詞向量,該高亮處理器必須開啟 term_vector=with_positions_offsets(存儲詞向量,即位置與偏移量)。

為解決大文本字段上高亮速度性能的問題,lucene高亮子產品提供了基于向量的高亮方式 fast-vector-highlighter(也稱為fvh)。fast-vector-highlighter(fvh)高亮顯示器利用建索引時候儲存好的詞向量來直接計算高亮段落,在高亮過程中比plain高亮方式少了實時分析過程,取而代之的是直接從磁盤中将分詞結果直接讀取到記憶體中進行計算。故要使用fvh的前置條件就是在建索引時候,需要配置存儲詞向量,詞向量需要包含詞位置資訊、詞偏移量資訊。

注意:fvh高亮器不支援span查詢。如果您需要對span查詢的支援,請嘗試其他高亮顯示,例如unified highlighter。

fvh在高亮時候的邏輯如下:

1.分析高亮查詢文法,提取表達式中的高亮詞集合

2.從磁盤上讀取該文檔字段下的詞向量集合

3.周遊詞向量集合,提取自表達式中出現的詞向量

4.根據提取到目标詞向量讀取詞頻資訊,根據詞頻擷取每個位置資訊、偏移量

5.通過相似度算法擷取得分較高的前n組高亮資訊

6.讀取字段内容(多字段用空格隔開),根據提取的詞向量直接定位截取高亮字段

7.2 Offsets Strategy

擷取偏移量政策。高亮顯示要解決的一個核心就是高亮顯示的詞根以及該詞根的位置(位置與偏移量)。

ES中提供了3中擷取偏移量資訊(Offsets)的政策:

  • The postings list

    如果将index_options設定為offsets,unified highlighter将使用該資訊突出顯示文檔,而無需重新分析文本。它直接對索引重新運作原始查詢,并從索引中提取比對偏移量。如果字段很大,這一點很重要,因為它不需要重新分析需要高亮顯示的文本。比term_vector方式占用更少的磁盤空間。

  • Term vectors

    如果在字段映射中将term_vector設定為with_positions_offset,unified highlighter将自動使用term_vector來高亮顯示字段。它特别适用于大字段(> 1MB)和高亮顯示多詞根查詢(如字首或通配符),因為它可以通路每個文檔的術語字典。fast vector highlighter高亮器必須将字段映射term_vector設定為with_positions_offset時才能生效。

  • Plain highlighting

    當沒有其他選擇時,統一使用這種模式。它在記憶體中建立一個很小的索引,并通過Lucene的查詢執行計劃重新運作原來的查詢條件,以通路目前文檔上的低級比對資訊。對于每個需要突出顯示的字段和文檔,都要重複此操作。Plain highlighting高亮顯示器就是這種模式。

注意:對于大型文本,Plain highlighting顯示器可能需要大量的時間消耗和記憶體。為了防止這種情況,在下一個Elasticsearch中,對要分析的文本字元的最大數量将限制在100萬。6.x版本預設無限制,但是可以使用索引設定參數index.highlight.max_analyzed_offset為特定索引設定。

7.3 高亮顯示配置項

高亮顯示的全局配置會被字段級别的覆寫。

  • boundary_chars

    設定邊界字元串集合,預設包含:.,!? tn

  • boundary_max_scan

    掃描邊界字元。預設為20

  • boundary_scanner

    指定如何分解高亮顯示的片段,可選值為chars、sentence、word

  • chars

    字元。使用由bordery_chars指定的字元作為高亮顯示邊界。通過boundary_max_scan控制掃描邊界字元的距離。該掃描方式隻适用于fast vector highlighter。

  • sentence

    句子,使用Java的BreakIterator确定的下一個句子邊界處的突出顯示片段。您可以使用boundary_scanner_locale指定要使用的區域設定。unified highlighter高亮器預設行為。

  • word

    單詞,由Java的BreakIterator确定的下一個單詞邊界處高亮顯示的片段。

  • boundary_scanner_locale

    區域設定。該參數采用語言标記的形式,例如。“en - us”、“- fr”、“ja-JP”。更多資訊可以在Locale語言标記文檔中找到。預設值是local . root。

  • encoder

    訓示代碼段是否應該編碼為HTML:預設(無編碼)或HTML (HTML-轉義代碼段文本,然後插入高亮标記)。

  • fields

    指定要檢索高亮顯示的字段,支援通配符。例如,您可以指定comment_*來獲得以comment_開頭的所有文本和關鍵字字段的高亮顯示。

注意:當您使用通配符時,隻會比對text、keyword類型字段。

  • force_source

    是否強制從_source高亮顯示,預設為false。其實預設情況就是根據源字段内容(_source)内容高亮顯示,即使字段是單獨存儲的。

  • fragmenter

    指定如何在高亮顯示代碼片段中拆分文本:可選值為simple、span。僅适用于Plain highlighting。預設為span。

  • simple

    将文本分成大小相同的片段。

  • span

    将文本分割成大小相同的片段,但盡量避免在突出顯示的術語之間分割文本。這在查詢短語時很有用。

  • fragment_offset

    控制開始高亮顯示的margin(空白),僅适用于fast vector highlighter。

  • fragment_size

    高亮顯示的片段,預設100。

  • highlight_query

    高亮顯示比對搜尋查詢以外的查詢。如果您使用rescore查詢,這尤其有用,因為預設情況下高亮顯示并不會考慮這些查詢。通常,應該将搜尋查詢包含在highlight_query中。

  • matched_fields

    組合多個字段上的比對項以突出顯示單個字段。對于以不同方式分析相同字元串的多個字段,這是最直覺的。所有matched_fields必須将term_vector設定為with_positions_offset,但是隻加載比對項組合到的字段,是以建議該字段store設定為true。隻适用于fast vector highlighter熒光筆。

  • no_match_size

    如果沒有要高亮顯示的比對片段,則希望從字段開頭傳回的文本數量。預設值為0(不傳回任何内容)。

  • number_of_fragments

    傳回的高亮顯示片段的最大數量。如果片段的數量設定為0,則不傳回片段。預設為5。

  • order

    該值預設為none,按照字段的順序傳回高亮文檔,可以設定為score(按相關性排序)。

  • phrase_limit

    控制要考慮的文檔中比對短語的數量。防止fast vector highlighter分析太多的短語和消耗太多的記憶體。在使用matched_fields時,将考慮每個比對字段的phrase_limit短語。提高限制會增加查詢時間并消耗更多記憶體。隻支援fast vector highlighter。預設為256。

  • pre_tags

    用于高亮顯示HTML标簽,與post_tags一起使用,預設用高亮顯示文本。

  • post_tags

    用于高亮顯示HTML标簽,與pre_tags一起使用,預設用高亮顯示文本。

  • require_field_match

    預設情況下,隻有包含查詢比對的字段才會高亮顯示。将require_field_match設定為false以突出顯示所有字段。預設值為true。

  • tags_schema

    定義高亮顯示樣式,例如。

  • type

    指定高亮顯示器,可選值:unified、plain、fvh。預設值為unified。

7.4 高亮顯示demo

public static void testSearch_highlighting() {
        RestHighLevelClient client = EsClient.getClient();
        try {
            SearchRequest searchRequest = new SearchRequest();
            searchRequest.indices("map_highlighting_01");
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            sourceBuilder.query(
            //        QueryBuilders.matchAllQuery()
                    QueryBuilders.termQuery("context", "身份證")
                    );
            
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("context");
            
            sourceBuilder.highlighter(highlightBuilder);
            searchRequest.source(sourceBuilder);
            System.out.println(client.search(searchRequest, RequestOptions.DEFAULT));
        } catch (Exception e) {
            // TODO: handle exception
        }
    }           

其傳回值如下:

{
    "took":2,
    "timed_out":false,
    "_shards":{
        "total":5,
        "successful":5,
        "skipped":0,
        "failed":0
    },
    "hits":{
        "total":1,
        "max_score":0.2876821,
        "hits":[
            {
                "_index":"map_highlighting_01",
                "_type":"_doc",
                "_id":"erYsbmcBeEynCj5VqVTI",
                "_score":0.2876821,
                "_source":{
                    "context":"城中西路可以受理外地二代身份證的辦理。"
                },
                "highlight":{   // @1
                    "context":[
                        "城中西路可以受理外地二代<em>身份證</em>的辦理。"
                    ]
                }
             }
        ]
    }
}           

這裡主要對highlight再做一次說明,其中每一個字段傳回的内容是對應原始資料的子集,最多fragmentSize個待關鍵字的比對條目,通常,在頁面上顯示文本時,應該用該字段取代原始值,這樣才能有高亮顯示的效果。

8、Rescoring

重打分機制。一個查詢首先使用高效的算法查找文檔,然後對傳回結果的top n 文檔運用另外的查詢算法,通常這些算法效率低效但能提供比對精度。

resoring查詢與原始查詢分數的合計方式如下:

  • total

    兩個評分相加

  • multiply

    将原始分數乘以rescore查詢分數。用于函數查詢重定向。

  • avg

    取平均數

  • max

    取最大值

  • min

    取最小值。

9、Search Type

查詢類型,可選值:QUERY_THEN_FETCH、QUERY_AND_FETCH、DFS_QUERY_THEN_FETCH。預設值:query_then_fetch。

  • QUERY_THEN_FETCH:首先根據路由算法向相關分片(多個)發送請求,此時隻傳回documentId與一些必要資訊(例如用于排序等),然後對各個分片的結果進行彙聚,排序,然後選取用戶端指定需要擷取的資料條數(top n),然後根據documentId再向各個分片請求具體的文檔資訊。
  • QUERY_AND_FETCH:在5.4.x版本開始廢棄,是直接向各個分片節點請求資料,每個分片傳回用戶端請求數量的文檔資訊,然後彙聚全部傳回給用戶端,傳回的資料為用戶端請求數量size * (路由後的分片數量)。
  • DFS_QUERY_THEN_FETCH:在開始向各個節點發送請求之前,會進行一次詞頻、相關性的計算,後續流程與QUERY_THEN_FETCH相同,可以看出,該查詢類型的文檔相關性會更高,但性能比QUERY_THEN_FETCH要差。

10、scroll

滾動查詢。es另外一種分頁方式。雖然搜尋請求傳回結果的單個“頁面”,但scroll API可以用于從單個搜尋請求檢索大量結果(甚至所有結果),這與在傳統資料庫上使用遊标的方式非常相似。scroll api不用于實時使用者請求,而是用于處理大量資料,例如為了将一個索引的内容重新索引到具有不同配置的新索引中。

10.1 如何使用scroll API

scroll API使用分為兩步:

1、第一步,首先通過scroll參數,指定該滾動查詢(類似于資料庫的遊标的存活時間)

POST /twitter/_search?scroll=1m
{
    "size": 100,
    "query": {
        "match" : {
            "title" : "elasticsearch"
        }
    }
}           

該方法會傳回一個重要的參數:scrollId。

2、第二步,使用該scrollId去es伺服器拉取下一批(下一頁資料)

POST  /_search/scroll 
{
    "scroll" : "1m", 
    "scroll_id" : "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==" 
}           

循環第三步,可以循環批量處理資料。

3、第三步,清除scrollId,類似于清除資料庫遊标,快速釋放資源。

DELETE /_search/scroll
{
    "scroll_id" : "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ=="
}           

下面給出scoll api的java版本示例程式:

public static void testScoll() {
        RestHighLevelClient client = EsClient.getClient();
        String scrollId = null;
        try {
            System.out.println("step 1 start ");
            // step 1 start
            SearchRequest searchRequest = new SearchRequest();
            searchRequest.indices("map_highlighting_01");
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            sourceBuilder.query(
                    QueryBuilders.termQuery("context", "身份證")
                    );
            searchRequest.source(sourceBuilder);
            searchRequest.scroll(TimeValue.timeValueMinutes(1));
            SearchResponse result = client.search(searchRequest, RequestOptions.DEFAULT);
            scrollId = result.getScrollId();
            // step 1 end
            
            // step 2 start
            if(!StringUtils.isEmpty(scrollId)) {
                System.out.println("step 2 start ");
                SearchScrollRequest scrollRequest = new SearchScrollRequest(scrollId);
                scrollRequest.scroll(TimeValue.timeValueMinutes(1));
                while(true) { //循環周遊
                    SearchResponse scollResponse = client.scroll(scrollRequest, RequestOptions.DEFAULT);
                    if(scollResponse.getHits().getHits() == null ||
                            scollResponse.getHits().getHits().length < 1) {
                        break;
                    }
                    scrollId = scollResponse.getScrollId();
                    // 處理文檔
                    scrollRequest.scrollId(scrollId);
                }
            // step 2 end    
            }
            System.out.println(result);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(!StringUtils.isEmpty(scrollId)) {
                System.out.println("step 3 start ");
                // step 3 start
                ClearScrollRequest clearScrollRequest = new ClearScrollRequest();
                clearScrollRequest.addScrollId(scrollId);
                try {
                    client.clearScroll(clearScrollRequest, RequestOptions.DEFAULT);
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            // step 3 end
            }
        } 
        
    }           

這裡重點闡述一下第一次查詢時,不僅傳回scrollId,也會傳回第一批資料。

10.2 Keeping the search context alive

scroll參數(傳遞給搜尋請求和每個滾動請求)告訴Elasticsearch它應該保持搜尋上下文活動多長時間。它的值(例如1m,參見Time unitsedit)不需要足夠長的時間來處理所有資料——它隻需要足夠長的時間來處理前一批結果。每個scroll請求(帶有scroll參數)設定一個新的過期時間。如果scroll請求沒有傳入scroll,那麼搜尋上下文将作為scroll請求的一部分被釋放。scroll其内部實作類似于快照,當第一次收到一個scroll請求時,就會為該搜尋上下文所比對的結果建立一個快照,随後文檔的變化并不會反映到該API的結果。

10.3 sliced scroll

對于傳回大量文檔的scroll查詢,可以将滾動分割為多個可以獨立使用的片,通過slice指定。

例如:

GET /twitter/_search?scroll=1m     // @1
{
    "slice": {                                      // @11
        "id": 0,                                    // @12
        "max": 2                                 // @13
    },
    "query": {
        "match" : {
            "title" : "elasticsearch"
        }
    }
}
GET /twitter/_search?scroll=1m        // @2
{
    "slice": {
        "id": 1,
        "max": 2
    },
    "query": {
        "match" : {
            "title" : "elasticsearch"
        }
    }
}           

@1,@2兩個并列的查詢,按分片去查詢。

@11:通過slice定義分片查詢。

@12:該分片查詢的ID。

@13:本次查詢總片數。

這個機制非常适合多線程處理資料。

具體分片機制是,首先将請求轉發到各分片節點,然後在每個節點使用比對到的文檔(hashcode(_uid)%slice片數),然後各分片節點傳回資料到協調節點。也就是預設情況下,分片是根據文檔的_uid,為了提高分片過程,可以通過如下方式進行優化,并指定分片字段。

  • 分片字段類型為數值型。
  • 字段的doc_values設定為true。
  • 每個文檔中都索引了該字段。
  • 該字段值隻在建立時指派,并不會更新。
  • 字段的基數應該很高(相當于資料庫索引選擇度),這樣能確定每個片傳回的資料相當,資料分布較均勻。
注意,預設slice片數最大為1024,可以通過索引設定項index.max_slices_per_scroll來改變預設值。
GET /twitter/_search?scroll=1m
{
    "slice": {
        "field": "date",
        "id": 0,
        "max": 10
    },
    "query": {
        "match" : {
            "title" : "elasticsearch"
        }
    }
}           

11、preference

查詢選擇副本分片的傾向性(即在一個複制組中選擇副本的分片值。預設情況下,Elasticsearch以未指定的順序從可用的碎片副本中進行選擇,副本之間的路由将在叢集章節更加詳細的介紹 。可以通過該字段指定分片傾向與選擇哪個副本。

preference可選值:

  • _primary

    隻在節點上執行,在6.1.0版本後廢棄,将在7.x版本移除。

  • _primary_first

    優先在主節點上執行。在6.1.0版本後廢棄,将在7.x版本移除。

  • _replica

    操作隻在副本分片上執行,如果有多個副本,其順序随機。在6.1.0版本後廢棄,将在7.x版本移除。

  • _replica_first

    優先在副本分片上執行,如果有多個副本,其順序随機。在6.1.0版本後廢棄,将在7.x版本移除。

  • _only_local

    操作将隻在配置設定給本地節點的分片上執行。_only_local選項保證隻在本地節點上使用碎片副本,這對于故障排除有時很有用。所有其他選項不能完全保證在搜尋中使用任何特定的碎片副本,而且在索引更改時,這可能意味着如果在處于不同重新整理狀态的不同碎片副本上執行重複搜尋,則可能産生不同的結果。

  • _local

    優先在本地分片上執行。

  • _prefer_nodes:abc,xyz

    優先在指定節點ID的分片上執行,示例中的節點ID為abc、xyz。

  • _shards:2,3

    将操作限制到指定的分片上執行。(這裡是2和3)這個首選項可以與其他首選項組合,但必須首先出現:_shards:2,3|_local。

  • _only_nodes:abc,xyz,...

    根據節點ID進行限制。

  • Custom (string) value

    自定義字元串,其路由為 hashcode(該值)%指派組内節點數。例如在web應用中通常以sessionId為傾向值。

12、explain

是否解釋各分數是如何計算的。

GET /_search
{
    "explain": true,
    "query" : {
        "term" : { "user" : "kimchy" }
    }
}           

13、version

如果設定為true,則傳回每個命中文檔的目前版本号。

GET /_search
{
    "version": true,
    "query" : {
        "term" : { "user" : "kimchy" }
    }
}           

14、Index Boost

當搜尋多個索引時,允許為每個索引配置不同的boost級别。當來自一個索引的點選率比來自另一個索引的點選率更重要時,該屬性則非常友善。

使用示例如下:

GET /_search
{
    "indices_boost" : [
        { "alias1" : 1.4 },
        { "index*" : 1.3 }
    ]
}           

15、min_score

指定傳回文檔的最小評分,如果文檔的評分低于該值,則不傳回。

GET /_search
{
    "min_score": 0.5,
    "query" : {
        "term" : { "user" : "kimchy" }
    }
}           

16、Named Queries

每個過濾器和查詢都可以在其頂級定義中接受_name。搜尋響應中每個比對文檔中會增加matched_queries結構體,記錄該文檔比對的查詢名稱。查詢和篩選器的标記隻對bool查詢有意義。

java示例如下:

public static void testNamesQuery() {
        RestHighLevelClient client = EsClient.getClient();
        try {
            SearchRequest searchRequest = new SearchRequest();
            searchRequest.indices("esdemo");
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            sourceBuilder.query(
                    QueryBuilders.boolQuery()
                        .should(QueryBuilders.termQuery("context", "fox").queryName("q1"))
                        .should(QueryBuilders.termQuery("context", "brown").queryName("q2"))
                    );
            searchRequest.source(sourceBuilder);
            SearchResponse result = client.search(searchRequest, RequestOptions.DEFAULT);
            System.out.println(result);
        } catch (Throwable e) {
            e.printStackTrace();
        } finally {
            EsClient.close(client);
        }
    }           

傳回結果如下:

{
    "took":4,
    "timed_out":false,
    "_shards":{
        "total":5,
        "successful":5,
        "skipped":0,
        "failed":0
    },
    "hits":{
        "total":2,
        "max_score":0.5753642,
        "hits":[
            {
                "_index":"esdemo",
                "_type":"matchquerydemo",
                "_id":"2",
                "_score":0.5753642,
                "_source":{
                    "context":"My quick brown as fox eats rabbits on a regular basis.",
                    "title":"Keeping pets healthy"
                },
                "matched_queries":[
                    "q1",
                    "q2"
                ]
            },
            {
                "_index":"esdemo",
                "_type":"matchquerydemo",
                "_id":"1",
                "_score":0.39556286,
                "_source":{
                    "context":"Brown rabbits are commonly seen brown.",
                    "title":"Quick brown rabbits"
                },
                "matched_queries":[
                    "q2"
                ]
            }
        ]
    }
}           

正如上面所說,每個比對文檔中都包含matched_queries,表明該文檔比對的是哪個查詢條件。

17、Inner hits

用于定義内部嵌套層的傳回規則,其inner hits支援如下選項:

  • from 用于内部比對的分頁。
  • size 用于内部比對的分頁,size。
  • sort 排序政策。
  • name 為内部嵌套層定義的名稱。

該部分示例将在下節重點闡述。

18、field collapsing(字段折疊)

允許根據字段值折疊搜尋結果。折疊是通過在每個折疊鍵上隻選擇排序最高的文檔來完成的。有點類似于聚合分組,其效果類似于按字段進行分組,預設命中的文檔清單第一層由該字段的第一條資訊,也可以通過允許根據字段值折疊搜尋結果。折疊是通過在每個折疊鍵上隻選擇排序最高的文檔來完成的。例如,下面的查詢為每個使用者檢索最佳tweet,并按喜歡的數量對它們進行排序。

下面首先通過示例進行展示field collapsing的使用。

1)首先查詢發的推特内容中包含elasticsearch的推文:

GET /twitter/_search
{
    "query": {
        "match": {
            "message": "elasticsearch"
        }
    },
    "collapse" : {
        "field" : "user" 
    },
    "sort": ["likes"]
}           

傳回結果:

{
    "took":8,
    "timed_out":false,
    "_shards":{
        "total":5,
        "successful":5,
        "skipped":0,
        "failed":0
    },
    "hits":{
        "total":5,
        "max_score":null,
        "hits":[
            {
                "_index":"mapping_field_collapsing_twitter",
                "_type":"_doc",
                "_id":"OYnecmcB-IBeb8B-bF2X",
                "_score":null,
                "_source":{
                    "message":"to be a elasticsearch",
                    "user":"user2",
                    "likes":3
                },
                "sort":[
                    3
                ]
            },
            {
                "_index":"mapping_field_collapsing_twitter",
                "_type":"_doc",
                "_id":"OonecmcB-IBeb8B-bF2q",
                "_score":null,
                "_source":{
                    "message":"to be elasticsearch",
                    "user":"user2",
                    "likes":3
                },
                "sort":[
                    3
                ]
            },
            {
                "_index":"mapping_field_collapsing_twitter",
                "_type":"_doc",
                "_id":"OInecmcB-IBeb8B-bF2G",
                "_score":null,
                "_source":{
                    "message":"elasticsearch is very high",
                    "user":"user1",
                    "likes":3
                },
                "sort":[
                    3
                ]
            },
            {
                "_index":"mapping_field_collapsing_twitter",
                "_type":"_doc",
                "_id":"O4njcmcB-IBeb8B-Rl2H",
                "_score":null,
                "_source":{
                    "message":"elasticsearch is high db",
                    "user":"user1",
                    "likes":1
                },
                "sort":[
                    1
                ]
            },
            {
                "_index":"mapping_field_collapsing_twitter",
                "_type":"_doc",
                "_id":"N4necmcB-IBeb8B-bF0n",
                "_score":null,
                "_source":{
                    "message":"very likes elasticsearch",
                    "user":"user1",
                    "likes":1
                },
                "sort":[
                    1
                ]
            }
        ]
    }
}           

首先上述會列出所有使用者的推特,如果隻想每個使用者隻顯示一條推文,并且點贊率最高,或者每個使用者隻顯示2條推文呢?

這個時候,按字段折疊就閃亮登場了。

java demo如下:

public static void search_field_collapsing() {
        RestHighLevelClient client = EsClient.getClient();
        try {
            SearchRequest searchRequest = new SearchRequest();
            searchRequest.indices("mapping_field_collapsing_twitter");
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            sourceBuilder.query(
                    QueryBuilders.matchQuery("message","elasticsearch")
            );
            sourceBuilder.sort("likes", SortOrder.DESC);
            CollapseBuilder collapseBuilder = new CollapseBuilder("user");
            sourceBuilder.collapse(collapseBuilder);
            searchRequest.source(sourceBuilder);
            SearchResponse result = client.search(searchRequest, RequestOptions.DEFAULT);
            System.out.println(result);
        } catch (Throwable e) {
            e.printStackTrace();
        } finally {
            EsClient.close(client);
        }
    }           

其結果如下:

{
    "took":22,
    "timed_out":false,
    "_shards":{
        "total":5,
        "successful":5,
        "skipped":0,
        "failed":0
    },
    "hits":{
        "total":5,
        "max_score":null,
        "hits":[
            {
                "_index":"mapping_field_collapsing_twitter",
                "_type":"_doc",
                "_id":"OYnecmcB-IBeb8B-bF2X",
                "_score":null,
                "_source":{
                    "message":"to be a elasticsearch",
                    "user":"user2",
                    "likes":3
                },
                "fields":{
                    "user":[
                        "user2"
                    ]
                },
                "sort":[
                    3
                ]
            },
            {
                "_index":"mapping_field_collapsing_twitter",
                "_type":"_doc",
                "_id":"OInecmcB-IBeb8B-bF2G",
                "_score":null,
                "_source":{
                    "message":"elasticsearch is very high",
                    "user":"user1",
                    "likes":3
                },
                "fields":{
                    "user":[
                        "user1"
                    ]
                },
                "sort":[
                    3
                ]
            }
        ]
    }
}           

上面的示例隻傳回了每個使用者的第一條資料,如果需要每個使用者傳回2條資料呢?可以通過inner_hit來設定。

public static void search_field_collapsing() {
        RestHighLevelClient client = EsClient.getClient();
        try {
            SearchRequest searchRequest = new SearchRequest();
            searchRequest.indices("mapping_field_collapsing_twitter");
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            sourceBuilder.query(
                    QueryBuilders.matchQuery("message","elasticsearch")
            );
            sourceBuilder.sort("likes", SortOrder.DESC);
            CollapseBuilder collapseBuilder = new CollapseBuilder("user");
            
            InnerHitBuilder collapseHitBuilder = new InnerHitBuilder("collapse_inner_hit");
            collapseHitBuilder.setSize(2);
            collapseBuilder.setInnerHits(collapseHitBuilder);
            sourceBuilder.collapse(collapseBuilder);
            
            searchRequest.source(sourceBuilder);
            SearchResponse result = client.search(searchRequest, RequestOptions.DEFAULT);
            System.out.println(result);
        } catch (Throwable e) {
            e.printStackTrace();
        } finally {
            EsClient.close(client);
        }
    }           
{
    "took":42,
    "timed_out":false,
    "_shards":{
        "total":5,
        "successful":5,
        "skipped":0,
        "failed":0
    },
    "hits":{
        "total":5,
        "max_score":null,
        "hits":[
            {
                "_index":"mapping_field_collapsing_twitter",
                "_type":"_doc",
                "_id":"OYnecmcB-IBeb8B-bF2X",
                "_score":null,
                "_source":{
                    "message":"to be a elasticsearch",
                    "user":"user2",
                    "likes":3
                },
                "fields":{
                    "user":[
                        "user2"
                    ]
                },
                "sort":[
                    3
                ],
                "inner_hits":{
                    "collapse_inner_hit":{
                        "hits":{
                            "total":2,
                            "max_score":0.19363807,
                            "hits":[
                                {
                                    "_index":"mapping_field_collapsing_twitter",
                                    "_type":"_doc",
                                    "_id":"OonecmcB-IBeb8B-bF2q",
                                    "_score":0.19363807,
                                    "_source":{
                                        "message":"to be elasticsearch",
                                        "user":"user2",
                                        "likes":3
                                    }
                                },
                                {
                                    "_index":"mapping_field_collapsing_twitter",
                                    "_type":"_doc",
                                    "_id":"OYnecmcB-IBeb8B-bF2X",
                                    "_score":0.17225473,
                                    "_source":{
                                        "message":"to be a elasticsearch",
                                        "user":"user2",
                                        "likes":3
                                    }
                                }
                            ]
                        }
                    }
                }
            },
            {
                "_index":"mapping_field_collapsing_twitter",
                "_type":"_doc",
                "_id":"OInecmcB-IBeb8B-bF2G",
                "_score":null,
                "_source":{
                    "message":"elasticsearch is very high",
                    "user":"user1",
                    "likes":3
                },
                "fields":{
                    "user":[
                        "user1"
                    ]
                },
                "sort":[
                    3
                ],
                "inner_hits":{
                    "collapse_inner_hit":{
                        "hits":{
                            "total":3,
                            "max_score":0.2876821,
                            "hits":[
                                {
                                    "_index":"mapping_field_collapsing_twitter",
                                    "_type":"_doc",
                                    "_id":"O4njcmcB-IBeb8B-Rl2H",
                                    "_score":0.2876821,
                                    "_source":{
                                        "message":"elasticsearch is high db",
                                        "user":"user1",
                                        "likes":1
                                    }
                                },
                                {
                                    "_index":"mapping_field_collapsing_twitter",
                                    "_type":"_doc",
                                    "_id":"N4necmcB-IBeb8B-bF0n",
                                    "_score":0.2876821,
                                    "_source":{
                                        "message":"very likes elasticsearch",
                                        "user":"user1",
                                        "likes":1
                                    }
                                }
                            ]
                        }
                    }
                }
            }
        ]
    }
}           

此時,傳回結果是兩級,第一級,還是每個使用者第一條消息,然後再内部中嵌套inner_hits。

19、Search After

Elasticsearch支援的第三種分頁擷取方式,該方法不支援跳轉頁面。

ElasticSearch支援的分頁方式目前已知:

1、通過from和size,當時當達到深度分頁時,成本變的非常高昂,故es提供了索引參數:index.max_result_window來控制(from + size)的最大值,預設為10000,超過該值後将報錯。

2、通過scroll滾動API,該方式類似于快照的工作方式,不具備實時性,并且滾動上下文的存儲需要耗費一定的性能。

本節将介紹第3種分頁方式,search after,基于上一頁查詢的結果進行下一頁資料的查詢。其 基本思想是選擇一組字段(排序字段,能做到全局唯一),es的排序查詢響應結果中會傳回sort數組,包含本排序字段的最大值,下一頁查詢将該組字段當成查詢條件,es在此資料的基礎下傳回下一批合适的資料。

public static void search_search_after() {
        RestHighLevelClient client = EsClient.getClient();
        try {
            SearchRequest searchRequest = new SearchRequest();
            searchRequest.indices("mapping_search_after");
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            sourceBuilder.query(
                    QueryBuilders.termQuery("user","user2")
            );
            sourceBuilder.size(1);
            sourceBuilder.sort("id", SortOrder.ASC);
            searchRequest.source(sourceBuilder);
            SearchResponse result = client.search(searchRequest, RequestOptions.DEFAULT);
            System.out.println(result);
            if(hasHit(result)) { // 如果本次比對到資料
                // 省略處理資料邏輯
                // 繼續下一批查詢
                // result.getHits().
                int length = result.getHits().getHits().length;
                SearchHit aLastHit = result.getHits().getHits()[length - 1];
                //開始下一輪查詢
                sourceBuilder.searchAfter(aLastHit.getSortValues());
                result = client.search(searchRequest, RequestOptions.DEFAULT);
                System.out.println(result);
            }
        } catch (Throwable e) {
            e.printStackTrace();
        } finally {
            EsClient.close(client);
        }
    }
    private static boolean hasHit(SearchResponse result) {
        return !( result.getHits() == null ||
                result.getHits().getHits() == null ||
                result.getHits().getHits().length < 1 );
    }           

本文詳細介紹了es三種分頁方式、排序、from、size、source filter、dov values fields、post filter、高亮顯示、rescoring、search type、scroll、preference、preference、explain、version、index boost、min_score、names query、Inner hits、field collapsing、Search After。

原文釋出時間為:2019-03-12

本文作者:丁威

本文來自

中間件興趣圈

,了解相關資訊可以關注