ES升级后_分页查询getHits().getTotalHits() 获取总条目一直为0的问题

一、问题描述

公司将ES从原来的6.4.3版本升级到7.12.0后,原来的ES_client是6.4.3,在进行分页搜索的时候,发现总数一直为0。

二、问题排查

从旧版本的ES中查询时,得到的结果

升级后版本7.12.0查询结果为:

 

从上面的结果来看,发现ES6.4版本返回的直接是 "total": 5,ES7.12.0返回的结果是total里面封装的一个对象

"total": {

            "value": 5,

            "relation": "eq"

        }

 ES-server-7.12版本:response.getHits().getTotalHits()返回的是一个对象,除了有value标识总条目数,还有relation字段。
ES-server-6.4版本:response.getHits().getTotalHits()返回的直接就是总条目数total。

ES的结果发生了变化,那我们来看看Java是怎样处理的。

三、Java客户端源码分析

6.4版本

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.12版本

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.4版本在进行JSON转化时,由于服务端使用的时ES-Server-7.x版本,不能拿到正常的值,默认返回了的值为0。

看到这里,是不是已经知道为什么获取总条目时看到的结果一直是0了吧?

我们继续往下探讨:

四、问题解决

1、简单粗暴的方式

升级ES客户端,与ES版本一致,使用以下方式获取总数

SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
response.getHits().getTotalHits().value;

但是对于已经上线多年的项目来说,升级客户端涉及到太多的代码改动了,为了节省代码的改动量,继续一探究竟。

2、深入研究

我们都知道在JSON反序列化时,需要JSON中key应该和对象的字段名一一对应才可以,那ES客户端时如何处理的呢?具体是怎么转化的?以下是ES-client-6.4版本与ES-client-7.x版本的分析。

ES 6.4

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 7.12

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.12版本的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的话,这不是直接把totalHits.value直接赋值给total了吗?

很惊奇的发现:在HTTP请求上添加了rest_total_hits_as_int=true参数之后,结果报文的total结构确实有改变。那么,我们是不是在客户端调用的时候加上rest_total_hits_as_int参数进行请求就OK了,不用更换包的版本使客户端与服务端版本保持一致。

当我以为不用升级客户端版本了,只需要在High Level Client的api调用中加入上面的那个参数,就能解决问题的时候,“屁颠屁颠”的在High Level Client的api中找添加Http参数调用的接口。

 但是

我们来看下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接口,也没有接口来添加Http参数。

在绝望之际,想着要大改,结果查看在ES-github的Pull request中找到答案

地址:https://github.com/elastic/elasticsearch/pull/46076

 意思是:在6.8版本后,把rest_total_hits_as_int参数加入请求中,请看

 到了这里,问题不就简单了?然后我把ES client的版本升级到6.8.4,问题妥妥的解决了,又省了一大把时间。

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>6.8.4</version>
</dependency>
<dependency>
    <groupId>org.elasticsearch</groupId>
    <artifactId>elasticsearch</artifactId>
    <version>6.8.4</version>
</dependency>

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-client</artifactId>
    <version>6.8.4</version>
</dependency>

 

五、总结

  1. 如果改造不是很大,建议ES client版本随着ES升级,简单粗暴。
  2. 另外,高版本的ES无法升级解决时,可以使用参数rest_total_hits_as_int来让totalHits字段,仍然以int格式返回(即:使用es client 6.8以上版本)。
  3. ES更新很快,一定要注意版本问题带来的坑,最好让集群和客户端使用官方推荐的匹配版本。
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值