ES查询时,通过response.getHits().getTotalHits()获取总条目结果始终为0
ES客户端与服务端版本
文章目录
1. 问题描述
ES-server版本为7版本,ES-client版本为6.3版本。在进行分页查询时获取总条目数一直为0:
SearchResponse response = restHighLevelClient.search(searchRequest);
long total = response.getHits().getTotalHits();
total得到的结果一直时0。
2. 问题排查
既然使用Java api查询总条目数有问题,那可以直接使用http从ES集群中进行查询。
以下提到的system_log_index索引是6.3版本、bate_sys_operate_log_index是7版本,以此进行对比分析。
2.1. 使用http从es中分别进行查询
从图中可以看到ES-server版本为7.x中的total是有数据的且大于0,但是对比ES-server版本为6.3中的total数据结构有所不同。
ES-server-7.x版本:response.getHits().getTotalHits()返回的是一个对象,除了有value标识总条目数,还有relation字段。
ES-server-6.3版本:response.getHits().getTotalHits()返回的直接就是总条目数total。
既然服务端返回的数据结构不一致,那么我们接下来查看Java客户端是如何处理的。
2.2. Java客户端源码分析
查看客户端 response.getHits() 返回的SearchHits源码。
2.2.1. SearchHits源码
6.3版本
public final class SearchHits implements Streamable, ToXContentFragment, Iterable<SearchHit> {
public static final SearchHit[] EMPTY = new SearchHit[0];
private SearchHit[] hits;
public long totalHits; //重点看这里
private float maxScore;
}
7.x版本
public final class SearchHits implements Writeable, ToXContentFragment, Iterable<SearchHit> {
public static final SearchHit[] EMPTY = new SearchHit[0];
private final SearchHit[] hits;
private final TotalHits totalHits; //重点看这里
private final float maxScore;
}
从以上SearchHits可以看出来,两个版本的totalHits返回的类型果然是不一样的。那么使用ES-client-6.3版本在进行JSON转化时,由于服务端使用的时ES-Server-7.x版本,不能拿到正常的值,默认返回了的值为0。
截止到此,小伙伴们是不是看到了光(●’◡’●)。
我们已经知道了为什么在获取总条目时得到的结果始终是0了。
那如何解决呢?继续追随着光寻找吧
3. 如何解决
3.1. 简单粗暴法
直接撸起袖子去更换ES-Java客户端版本,与服务端保持一致。
使用以下方式获取条目总数:
SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
response.getHits().getTotalHits().value;
3.2. 一探究竟
细心的小伙伴有没有发现服务端返回的JSON数据结构和客户端的字段是不一致的,究竟怎么不一致,为什么不一致,我们继续来探索吧。
3.2.1. 客户端与服务端字段对比
我们都知道在JSON反序列化时,需要JSON中key应该和对象的字段名一一对应才可以,那ES客户端时如何处理的呢?具体是怎么转化的?以下是ES-client-6.3版本与ES-client-7.x版本的分析。
ES-client 6.3
public final class SearchHits implements Streamable, ToXContentFragment, Iterable<SearchHit> {
public static final SearchHit[] EMPTY = new SearchHit[0];
private SearchHit[] hits;
public long totalHits;
private float maxScore;
}
"hits": {
"total": 10, //对应的java字段为totalHits
"max_score": 1, //对应的java字段为maxScore
"hits": [ ]
}
在toXContent方法中进行的字段名称的替换:注意标注替换的地方
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject("hits");
builder.field("total", this.totalHits); //替换
if (Float.isNaN(this.maxScore)) {
builder.nullField("max_score"); //替换
} else {
builder.field("max_score", this.maxScore); //替换
}
builder.field("hits");
builder.startArray();
SearchHit[] var3 = this.hits;
int var4 = var3.length;
for(int var5 = 0; var5 < var4; ++var5) {
SearchHit hit = var3[var5];
hit.toXContent(builder, params);
}
builder.endArray();
builder.endObject();
return builder;
}
ES-client 7.3
public final class SearchHits implements Writeable, ToXContentFragment, Iterable<SearchHit> {
public static final SearchHit[] EMPTY = new SearchHit[0];
private final SearchHit[] hits;
private final TotalHits totalHits;
private final float maxScore;
}
"hits": {
"total": { //对应的java字段为totalHits
"value": 2,
"relation": "eq"
},
"max_score": 1, //对应的java字段为maxScore
"hits": []
}
在toXContent方法中进行的字段名称的替换:注意标注替换的地方
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject("hits");
boolean totalHitAsInt = params.paramAsBoolean("rest_total_hits_as_int", false);
if (totalHitAsInt) {
long total = this.totalHits == null ? -1L : this.totalHits.value;
builder.field("total", total); //替换
} else if (this.totalHits != null) {
builder.startObject("total"); //替换
builder.field("value", this.totalHits.value);
builder.field("relation", this.totalHits.relation == Relation.EQUAL_TO ? "eq" : "gte");
builder.endObject();
}
if (Float.isNaN(this.maxScore)) {
builder.nullField("max_score"); //替换
} else {
builder.field("max_score", this.maxScore); //替换
}
builder.field("hits");
builder.startArray();
SearchHit[] var8 = this.hits;
int var5 = var8.length;
for(int var6 = 0; var6 < var5; ++var6) {
SearchHit hit = var8[var6];
hit.toXContent(builder, params);
}
builder.endArray();
builder.endObject();
return builder;
}
重点来了,在分析ES-client-7.x版本的toXContent方法时,有一个条件判断rest_total_hits_as_int
boolean totalHitAsInt = params.paramAsBoolean("rest_total_hits_as_int", false);
if (totalHitAsInt) {
long total = this.totalHits == null ? -1L : this.totalHits.value;
builder.field("total", total);
} else if (this.totalHits != null) {
builder.startObject("total");
builder.field("value", this.totalHits.value);
builder.field("relation", this.totalHits.relation == Relation.EQUAL_TO ? "eq" : "gte");
builder.endObject();
}
从代码中可以看到如果rest_total_hits_as_int是ture的话,则total就以long类型返回了。
使用HTTP接口加上rest_total_hits_as_int参数试一试,看报文中的total结构是否有改变:
很惊奇的发现:在HTTP请求上添加了rest_total_hits_as_int=true参数之后,结果报文的total结构确实有改变。那么,我们是不是在客户端调用的时候加上rest_total_hits_as_int参数进行请求就OK了,不用更换包的版本使客户端与服务端版本保持一致。
理想很丰满,显示很残酷:
ES-client并没有为我们提供添加HTTP参数的接口。那HTTP接口参数使如何处理的呢?我们继续查看restHighLevelClient.search()的源码。
3.2.1.1. restHighLevelClient.search源码
public final SearchResponse search(SearchRequest searchRequest, RequestOptions options) throws IOException {
return (SearchResponse)this.performRequestAndParseEntity((ActionRequest)searchRequest, (r) -> {
return RequestConverters.search(r, "_search"); //重点
}, (RequestOptions)options, (CheckedFunction)(SearchResponse::fromXContent), (Set)Collections.emptySet());
}
static Request search(SearchRequest searchRequest, String searchEndpoint) throws IOException {
Request request = new Request("POST", endpoint(searchRequest.indices(), searchRequest.types(), searchEndpoint));
RequestConverters.Params params = new RequestConverters.Params(request);
addSearchRequestParams(params, searchRequest); //重点
if (searchRequest.source() != null) {
request.setEntity(createEntity(searchRequest.source(), REQUEST_BODY_CONTENT_TYPE));
}
return request;
}
private static void addSearchRequestParams(RequestConverters.Params params, SearchRequest searchRequest) {
params.putParam("typed_keys", "true");
params.withRouting(searchRequest.routing());
params.withPreference(searchRequest.preference());
params.withIndicesOptions(searchRequest.indicesOptions());
params.putParam("search_type", searchRequest.searchType().name().toLowerCase(Locale.ROOT));
params.putParam("ccs_minimize_roundtrips", Boolean.toString(searchRequest.isCcsMinimizeRoundtrips()));
params.putParam("pre_filter_shard_size", Integer.toString(searchRequest.getPreFilterShardSize()));
params.putParam("max_concurrent_shard_requests", Integer.toString(searchRequest.getMaxConcurrentShardRequests()));
if (searchRequest.requestCache() != null) {
params.putParam("request_cache", Boolean.toString(searchRequest.requestCache()));
}
if (searchRequest.allowPartialSearchResults() != null) {
params.putParam("allow_partial_search_results", Boolean.toString(searchRequest.allowPartialSearchResults()));
}
params.putParam("batched_reduce_size", Integer.toString(searchRequest.getBatchedReduceSize()));
if (searchRequest.scroll() != null) {
params.putParam("scroll", searchRequest.scroll().keepAlive());
}
}
通过走读search方法的源码,ES-client在请求的时候,会自己处理一下请求的参数信息,但是没有为用户提供添加参数的API接口。
是不是无路可走了/(ㄒoㄒ)/~~,我们必须使用简单粗暴的方法。不要放弃,成功就在眼前!!!!!
3.2.1.2. ES的github中找答案
在ES-github的Pull request中找到答案:请点击
译文:
此提交向所有 HLRC 的搜索请求添加了一个名为 rest_total_hits_as_int 的查询参数
支持它(_search,_msearch,...)。这使得 HLRC 客户端的搜索与版本 >= 6.6.0 中的任何节点兼容,因为在 6.6.0 中添加了 rest_total_hits_as_int。这意味着版本 < 6.6.0 中的节点将无法处理版本 6.8.3 中的 HLRC 发送的搜索请求,但我们已经在文档中警告说客户端仅向前兼容
经尝试,ES在6.8.3版本开始,已经将rest_total_hits_as_int=true参数加入到请求当中:
private static void addSearchRequestParams(RequestConverters.Params params, SearchRequest searchRequest) {
params.putParam("typed_keys", "true");
params.putParam("rest_total_hits_as_int", "true"); //重点要看
params.withRouting(searchRequest.routing());
params.withPreference(searchRequest.preference());
params.withIndicesOptions(searchRequest.indicesOptions());
params.putParam("search_type", searchRequest.searchType().name().toLowerCase(Locale.ROOT));
if (searchRequest.requestCache() != null) {
params.putParam("request_cache", Boolean.toString(searchRequest.requestCache()));
}
if (searchRequest.allowPartialSearchResults() != null) {
params.putParam("allow_partial_search_results", Boolean.toString(searchRequest.allowPartialSearchResults()));
}
params.putParam("batched_reduce_size", Integer.toString(searchRequest.getBatchedReduceSize()));
if (searchRequest.scroll() != null) {
params.putParam("scroll", searchRequest.scroll().keepAlive());
}
}
所以,我们将ES-client版本更换为6.8.3即可。这样获取总条目的方式与6.3版本一致:
SearchResponse response = restHighLevelClient.search(searchRequest);
long total = response.getHits().getTotalHits();
4. 总结
E是客户端版本:6.3版本->7.x版本->6.8.3版本
ES-server使用7.x版本,获取总条目的方式:
- ES-Client使用6.3版本——得到的total为0
- ES-Client使用与ES-Server一致的版本:response.getHits().getTotalHits().value
- ES-Client使用6.8.3版本:response.getHits().getTotalHits()
更多推荐
所有评论(0)