Elasticsearch Search API之(Request Body Search 查询主体)-下篇
preference
查询选择副本分片的倾向性(即在一个复制组中选择副本的分片值。默认情况下,es以未指定的顺序从可用的碎片副本中进行选择,副本之间的路由将在集群章节更加详细的介绍 。可以通过该字段指定分片倾向与选择哪个副本。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
自定义字符串,其路由为 hashcod-e(该值)%赋值组内节点数。例如在web应用中通常以sessionId为倾向值。
explain
是否解释各分数是如何计算的。
1GET /_search
2{
3 "explain": true,
4 "query" : {
5 "term" : { "user" : "kimchy" }
6 }
7}
version
如果设置为true,则返回每个命中文档的当前版本号。
1GET /_search
2{
3 "version": true,
4 "query" : {
5 "term" : { "user" : "kimchy" }
6 }
7}
Index Boost
当搜索多个索引时,允许为每个索引配置不同的boost级别。当来自一个索引的点击率比来自另一个索引的点击率更重要时,该属性则非常方便。
使用示例如下:
1GET /_search
2{
3 "indices_boost" : [
4 { "alias1" : 1.4 },
5 { "index*" : 1.3 }
6 ]
7}
min_score
指定返回文档的最小评分,如果文档的评分低于该值,则不返回。
1GET /_search
2{
3 "min_score": 0.5,
4 "query" : {
5 "term" : { "user" : "kimchy" }
6 }
7}
Named Queries
每个过滤器和查询都可以在其顶级定义中接受_name。搜索响应中每个匹配文档中会增加matched_queries结构体,记录该文档匹配的查询名称。查询和筛选器的标记只对bool查询有意义。
java示例如下:
1public static void testNamesQuery() {
2 RestHighLevelClient client = EsClient.getClient();
3 try {
4 SearchRequest searchRequest = new SearchRequest();
5 searchRequest.indices("esdemo");
6 SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
7 sourceBuilder.query(
8 QueryBuilders.boolQuery()
9 .should(QueryBuilders.termQuery("context", "fox").queryName("q1"))
10 .should(QueryBuilders.termQuery("context", "brown").queryName("q2"))
11 );
12 searchRequest.source(sourceBuilder);
13 SearchResponse result = client.search(searchRequest, RequestOptions.DEFAULT);
14 System.out.println(result);
15 } catch (Throwable e) {
16 e.printStackTrace();
17 } finally {
18 EsClient.close(client);
19 }
20 }
返回结果如下:
1{
10 "hits":{
19 "_source":{
20 "context":"My quick brown as fox eats rabbits on a regular basis.",
21 "title":"Keeping pets healthy"
22 },
23 "matched_queries":[
24 "q1",
25 "q2"
26 ]
27 }
41 ]
42 }
43}
正如上面所说,每个匹配文档中都包含matched_queries,表明该文档匹配的是哪个查询条件。
Inner hits
用于定义内部嵌套层的返回规则,其inner hits支持如下选项:
from 用于内部匹配的分页。
size 用于内部匹配的分页,size。
sort 排序策略。
name 为内部嵌套层定义的名称。
该部分示例将在下节重点阐述。
field collapsing(字段折叠)
允许根据字段值折叠搜索结果。折叠是通过在每个折叠键上只选择排序最高的文档来完成的。有点类似于聚合分组,其效果类似于按字段进行分组,默认命中的文档列表第一层由该字段的第一条信息,也可以通过允许根据字段值折叠搜索结果。折叠是通过在每个折叠键上只选择排序最高的文档来完成的。例如下面的查询为每个用户检索最佳twee-t,并按喜欢的数量对它们进行排序。
下面首先通过示例进行展示field colla-psing的使用。
1)首先查询发的推特内容中包含elast-icsearch的推文:
1GET /twitter/_search
2{
3 "query": {
4 "match": {
5 "message": "elasticsearch"
6 }
7 },
8 "collapse" : {
9 "field" : "user"
10 },
11 "sort": ["likes"]
12}
返回结果:
1{
13 "hits":[
14 {
15 "_index":"mapping_field_collapsing_twitter",
16 "_type":"_doc",
17 "_id":"OYnecmcB-IBeb8B-bF2X",
18 "_score":null,
19 "_source":{
20 "message":"to be a elasticsearch",
21 "user":"user2",
22 "likes":3
23 },
24 "sort":[
25 3
26 ]
27 },
84 ]
85 }
86}
首先上述会列出所有用户的推特,如果只想每个用户只显示一条推文,并且点赞率最高,或者每个用户只显示2条推文呢?这个时候,按字段折叠就闪亮登场了。java demo如下:
1public static void search_field_collapsing() {
2 RestHighLevelClient client = EsClient.getClient();
3 try {
4 SearchRequest searchRequest = new SearchRequest();
5 searchRequest.indices("mapping_field_collapsing_twitter");
6 SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
7 sourceBuilder.query(
8 QueryBuilders.matchQuery("message","elasticsearch")
9 );
10 sourceBuilder.sort("likes", SortOrder.DESC);
11 CollapseBuilder collapseBuilder = new CollapseBuilder("user");
12 sourceBuilder.collapse(collapseBuilder);
13 searchRequest.source(sourceBuilder);
14 SearchResponse result = client.search(searchRequest, RequestOptions.DEFAULT);
15 System.out.println(result);
16 } catch (Throwable e) {
17 e.printStackTrace();
18 } finally {
19 EsClient.close(client);
20 }
21 }
其结果如下:
1{
10 "hits":{
11
13 "hits":[
14 {
15 "_index":"mapping_field_collapsing_twitter",
16 "_type":"_doc",
17 "_id":"OYnecmcB-IBeb8B-bF2X",
18 "_score":null,
19 "_source":{
20 "message":"to be a elasticsearch",
21 "user":"user2",
22 "likes":3
23 },
24 "fields":{
25 "user":[
26 "user2"
27 ]
28 },
29 "sort":[
30 3
31 ]
32 },
33 {
34 "_index":"mapping_field_collapsing_twitter",
35 "_type":"_doc",
36 "_id":"OInecmcB-IBeb8B-bF2G",
37 "_score":null,
38 "_source":{
39 "message":"elasticsearch is very high",
40 "user":"user1",
41 "likes":3
42 },
43 "fields":{
44 "user":[
45 "user1"
46 ]
47 },
48 "sort":[
49 3
50 ]
51 }
52 ]
53 }
54}
上面的示例只返回了每个用户的第一条数据,如果需要每个用户返回2条数据呢?可以通过inner_hit来设置。
1public static void search_field_collapsing() {
2 RestHighLevelClient client = EsClient.getClient();
3 try {
4 SearchRequest searchRequest = new SearchRequest();
5 searchRequest.indices("mapping_field_collapsing_twitter");
6 SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
7 sourceBuilder.query(
8 QueryBuilders.matchQuery("message","elasticsearch")
9 );
10 sourceBuilder.sort("likes", SortOrder.DESC);
11 CollapseBuilder collapseBuilder = new CollapseBuilder("user");
12
13 InnerHitBuilder collapseHitBuilder = new InnerHitBuilder("collapse_inner_hit");
14 collapseHitBuilder.setSize(2);
15 collapseBuilder.setInnerHits(collapseHitBuilder);
16 sourceBuilder.collapse(collapseBuilder);
17
18 searchRequest.source(sourceBuilder);
19 SearchResponse result = client.search(searchRequest, RequestOptions.DEFAULT);
20 System.out.println(result);
21 } catch (Throwable e) {
22 e.printStackTrace();
23 } finally {
24 EsClient.close(client);
25 }
26 }
返回结果如下:
1{
2 "took":42,
3 "timed_out":false,
4 "_shards":{
5 "total":5,
6 "successful":5,
7 "skipped":0,
8 "failed":0
9 },
10 "hits":{
11 "total":5,
12 "max_score":null,
13 "hits":[
14 {
15 "_index":"mapping_field_collapsing_twitter",
16 "_type":"_doc",
17 "_id":"OYnecmcB-IBeb8B-bF2X",
18 "_score":null,
19 "_source":{
20 "message":"to be a elasticsearch",
21 "user":"user2",
22 "likes":3
23 },
24 "fields":{
25 "user":[
26 "user2"
27 ]
28 },
29 "sort":[
30 3
31 ],
32 "inner_hits":{
33 "collapse_inner_hit":{
34 "hits":{
35 "total":2,
36 "max_score":0.19363807,
37 "hits":[
38 {
39 "_index":"mapping_field_collapsing_twitter",
40 "_type":"_doc",
41 "_id":"OonecmcB-IBeb8B-bF2q",
42 "_score":0.19363807,
43 "_source":{
44 "message":"to be elasticsearch",
45 "user":"user2",
46 "likes":3
47 }
48 },
49 {
50 "_index":"mapping_field_collapsing_twitter",
51 "_type":"_doc",
52 "_id":"OYnecmcB-IBeb8B-bF2X",
53 "_score":0.17225473,
54 "_source":{
55 "message":"to be a elasticsearch",
56 "user":"user2",
57 "likes":3
58 }
59 }
60 ]
61 }
62 }
63 }
64 },
65 {
66 "_index":"mapping_field_collapsing_twitter",
67 "_type":"_doc",
68 "_id":"OInecmcB-IBeb8B-bF2G",
69 "_score":null,
70 "_source":{
71 "message":"elasticsearch is very high",
72 "user":"user1",
73 "likes":3
74 },
75 "fields":{
76 "user":[
77 "user1"
78 ]
79 },
80 "sort":[
81 3
82 ],
83 "inner_hits":{
84 "collapse_inner_hit":{
85 "hits":{
86 "total":3,
87 "max_score":0.2876821,
88 "hits":[
89 {
90 "_index":"mapping_field_collapsing_twitter",
91 "_type":"_doc",
92 "_id":"O4njcmcB-IBeb8B-Rl2H",
93 "_score":0.2876821,
94 "_source":{
95 "message":"elasticsearch is high db",
96 "user":"user1",
97 "likes":1
98 }
99 },
100 {
101 "_index":"mapping_field_collapsing_twitter",
102 "_type":"_doc",
103 "_id":"N4necmcB-IBeb8B-bF0n",
104 "_score":0.2876821,
105 "_source":{
106 "message":"very likes elasticsearch",
107 "user":"user1",
108 "likes":1
109 }
110 }
111 ]
112 }
113 }
114 }
115 }
116 ]
117 }
118}
此时,返回结果是两级,第一级,还是每个用户第一条消息,然后再内部中嵌套inner_hits。
Search After
Elasticsearch支持的第三种分页获取方式,该方法不支持跳转页面。
es支持的分页方式目前已知:
通过from和size,当时当达到深度分页时,成本变的非常高昂,故es提供了索引参数:index.max_result_window来控制(from + size)的最大值,默认为10000,超过该值后将报错。
通过scroll滚动API,该方式类似于快照的工作方式,不具备实时性,并且滚动上下文的存储需要耗费一定的性能。
本节将介绍第3种分页方式,search after,基于上一页查询的结果进行下一页数据的查询。基本思想是选择一组排序字段,能做到全局唯一。es的排序查询响应结果中会返回sort数组,包含本排序字段的最大值,下一页查询将该组字段当成查询条件,es在此数据的基础下返回下一批合适的数据。
java示例如下:
1public static void search_search_after() {
2 RestHighLevelClient client = EsClient.getClient();
3 try {
4 SearchRequest searchRequest = new SearchRequest();
5 searchRequest.indices("mapping_search_after");
6 SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
7 sourceBuilder.query(
8 QueryBuilders.termQuery("user","user2")
9 );
10 sourceBuilder.size(1);
11 sourceBuilder.sort("id", SortOrder.ASC);
12 searchRequest.source(sourceBuilder);
13 SearchResponse result = client.search(searchRequest, RequestOptions.DEFAULT);
14 System.out.println(result);
15 if(hasHit(result)) { // 如果本次匹配到数据
16 // 省略处理数据逻辑
17 // 继续下一批查询
18 // result.getHits().
19 int length = result.getHits().getHits().length;
20 SearchHit aLastHit = result.getHits().getHits()[length - 1];
21 //开始下一轮查询
22 sourceBuilder.searchAfter(aLastHit.getSortValues());
23 result = client.search(searchRequest, RequestOptions.DEFAULT);
24 System.out.println(result);
25 }
26 } catch (Throwable e) {
27 e.printStackTrace();
28 } finally {
29 EsClient.close(client);
30 }
31 }
32 private static boolean hasHit(SearchResponse result) {
33 return !( result.getHits() == null ||
34 result.getHits().getHits() == null ||
35 result.getHits().getHits().length < 1 );
36 }
更多文章请关注微信公众号:
广告:作者新书《RocketMQ技术内幕》已上市
《RocketMQ技术内幕》已出版上市,目前可在主流购物平台(京东、天猫等)购买,本书从源码角度深度分析了RocketMQ NameServer、消息发送、消息存储、消息消费、消息过滤、主从同步HA、事务消息;在实战篇重点介绍了RocketMQ运维管理界面与当前支持的39个运维命令;并在附录部分罗列了RocketMQ几乎所有的配置参数。本书得到了RocketMQ创始人、阿里巴巴Messaging开源技术负责人、Linux OpenMessaging 主席的高度认可并作序推荐。目前是国内第一本成体系剖析RocketMQ的书籍。