文章目录

  • 问题描述
  • 问题原因
  • 解决办法查找
  • 总结


问题描述

ES分页查询的代码如下:

SearchResponse searchResponse = highLevelClient.search(searchRequest, RequestOptions.DEFAULT);
long totalNum = searchResponse.getHits().getTotalHits();   //返回的是long型的
SearchHit[] searchHits = searchResponse.getHits().getHits();

随着ES server 集群升级以后(从5.5.1->7.5.1),自动化脚步跑失败了,因为ES集群升级后totalNum的值为0了,哪怕searchHits 明明有数据

问题原因

既然使用High Level Client的Java api 查询有问题,那么直接通过http rest接口从ES 集群查询,结果如下,发现是有total是有数据而且大于0的

es7查询 es7查询数据gethitstotal一直为0_elasticsearch


细细一看,发现Java客户端中getHits().getTotalHits()的返回类型是long型的,但是上面的截图中,还有relation这样的额外字段。searchResponse.getHits()的返回类型为SearchHits。于是确认本地ES 客户端版本为6.7.1后,对比这两个版本的SearchHits,结果如下:

7.5.1

public final class SearchHits {
    private final SearchHit[] hits;
    private final TotalHits totalHits;   //看这里
    private final float maxScore;
}

6.7.1

public final class SearchHits {
    private SearchHit[] hits;
    public long totalHits;		//看这里
    private float maxScore;
}

果然两个版本的SearchHits 类的totalHits 字段类型不一样了,那么6.7.1的客户端在Json转对象的时候,当然不能拿正常赋值,所以totalHits 就是默认的0

解决办法查找

既然问题的原因找到了,那么怎么解决呢,我的第一想法就是客户端也升级为何集群一样的版本。正准备去尝试的时候,发现了一个问题。请各位胖友仔细观察前面Http rest 直接查询的结果和SearchHits对象的字段名字,把它们对比一下.有如下现象:

total -> totalHits
max_score -> maxScore

基于我们对Json 反序列,和反射的理解,Json字符串的key应该和对象字段名字一一对应才对,那么到底是哪儿在帮着转化处理了呢?

通过搜索在7.5.1SearchHits这个类的toXContent方法中我找到了答案。图中提示的地方做了字段名字的转换。

es7查询 es7查询数据gethitstotal一直为0_elasticsearch_02


各位胖友在仔细看看toXContent方法中的这段代码:

boolean totalHitAsInt = params.paramAsBoolean(RestSearchAction.TOTAL_HITS_AS_INT_PARAM, false);
 if (totalHitAsInt) {
      //按照老的方式处理,直接是long型的totalHits
     long total = totalHits == null ? -1 : totalHits.value;  
     builder.field(Fields.TOTAL, total);
 } else if (totalHits != null) {
     builder.startObject(Fields.TOTAL);
     builder.field("value", totalHits.value);
     builder.field("relation", totalHits.relation == Relation.EQUAL_TO ? "eq" : "gte");
     builder.endObject();
 }

这段代码也比较明了,基于RestSearchAction.TOTAL_HITS_AS_INT_PARAM(rest_total_hits_as_int)这个参数来控制,当值为truetotalHits以int类型返回。

Http rest接口我加这个参数试了试,还真可以解决问题,结果如下:

es7查询 es7查询数据gethitstotal一直为0_java_03


当我以为不用升级客户端版本了,只需要在High Level Client的api调用中加入上面的那个参数,就能解决问题的时候,“屁颠屁颠”的在High Level Client的api中找添加Http参数调用的接口,好一阵才发现似乎并没有接口来添加Http参数。看来此路不通,继续想办法。

一路进入highLevelClient.search的源码,我发现ES其实是处理了Http参数的添加的,如下:

//org.elasticsearch.client.RequestConverters#addSearchRequestParams

    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));
        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()));
        }
        .......等等

因为ES客户端在请求的时候,会把SearchRequest转化为Request,而这个Request中是可以放参数的。
但是它只是自己加了一些参数,而且似乎并没有给我们预留接口呀。

最终一番查找资料之后,在ES的github Pull Request中找到了说明。详情点击这里 截图如下:

es7查询 es7查询数据gethitstotal一直为0_客户端_04


意思就是在6.8版本后,把rest_total_hits_as_int参数加入请求中。怎么加的呢,如下:

es7查询 es7查询数据gethitstotal一直为0_字段_05

到了这里,整个过程已经很明了了,那么解决办法也呼之欲出,我本地把客户端版本升到6.8.4,问题解决。

总结

  1. 高版本的ES集群,可以使用参数rest_total_hits_as_int来让totalHits字段,仍然以int格式返回。
  2. ES更新很快,一定要注意版本问题带来的坑,最好让集群和客户端使用官方推荐的匹配版本。

结束语
如果胖友发现文章有不对或者不妥之处,还望在评论中不吝指出^ v ^。