作者介绍:《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支持按数组或多值字段进行排序。模式选项控制选择的数组值,以便对它所属的文档进行排序。模式选项可以有以
下值:
其示例如下:
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还支持在一个或多个嵌套对象内部的字段进行排序。一个嵌套查询提包含如下选项(参数):
"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属性来确定,其可选值为:
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来使用。
plain highlighter高亮方式是个实时分析处理高亮器。即用户在查询的时候,搜索引擎查询到了目标数据docid后,将需要高亮的字段数据提取到内存,再调用该字段的分析器进行处理,分析器对文本进行分析处理,分析完成后采用相似度算法计算得分最高的前n组并高亮段返回数据。假设用户搜索的都是比较大的文档同时需要进行高亮。按照一页查询40条(每条数据20k)的方式进行显示,即使相似度计算以及搜索排序不耗时,整个查询也会被高亮拖累到接近两秒。highlighter高亮器是实时分析高亮器,这种实时分析机制会让ES占用较少的IO资源同时也占用较少的存储空间(词库较全的话相比fvh方式能节省一半的存储空间),其实时计算高亮是采用cpu资源来缓解io压力,在高亮字段较短(比如高亮文章的标题)时候速度较快,同时因io访问的次数少,io压力较小,有利于提高系统吞吐量。
参考资料:https://blog.csdn.net/kjsoftware/article/details/76293204
为解决大文本字段上高亮速度性能的问题,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)的策略:
注意:对于大型文本,Plain highlighting显示器可能需要大量的时间消耗和内存。为了防止这种情况,在下一个Elasticsearch中,对要分析的文本字符的最大数量将限制在100万。6.x版本默认无限制,但是可以使用索引设置参数index.highlight.max_analyzed_offset为特定索引设置。
7.3 高亮显示配置项
高亮显示的全局配置会被字段级别的覆盖。
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查询与原始查询分数的合计方式如下:
9、Search Type
查询类型,可选值:QUERY_THEN_FETCH、QUERY_AND_FETCH、DFS_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,为了提高分片过程,可以通过如下方式进行优化,并指定分片字段。
注意,默认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可选值:
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支持如下选项:
该部分示例将在下节重点阐述。
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专栏