Elasticsearch Search API之(Request Body Search 查询主体)

作者介绍:《RocketMQ技术内幕》作者,中间件兴趣圈微信公众号维护者,文末有对应的二维码,关注后可以与作者更好的互动。

本文有点长,看完可能需要点耐心,本文详细介绍了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
    设置边界字符串集合,默认包含:.,!? \t\n
  • 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":[
                        "城中西路可以受理外地二代身份证的办理。"
                    ]
                }
             }
        ]
    }
}

这里主要对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*,x*yz,…
    根据节点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。


欢迎加笔者微信号(dingwpmz),加群探讨,笔者优质专栏目录:
1、源码分析RocketMQ专栏(40篇+)
2、源码分析Sentinel专栏(12篇+)
3、源码分析Dubbo专栏(28篇+)
4、源码分析Mybatis专栏
5、源码分析Netty专栏(18篇+)
6、源码分析JUC专栏
7、源码分析Elasticjob专栏
8、Elasticsearch专栏(20篇+)
9、源码分析MyCat专栏

你可能感兴趣的:(Elasticsearch)