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.讀取字段內容(多字段用空格隔開),根據提取的詞向量直接定位截取高亮字段
參考資料:https://blog.csdn.net/kjsoftware/article/details/76293204

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在此數據的基礎下返回下一批合適的數據。

java示例如下:

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
本文作者:丁威
本文來自中間件興趣圈,瞭解相關信息可以關注中間件興趣圈

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章