目录
一. 问题描述
开发环境:
JDK1.8、Elasticsearch7.3.1、RestHighLevelClient
问题:
最近在通过Java客户端操作ES进行分页查询(from+size)时,需要返回满足条件的数据总数。我发现满足条件的数据总数一旦超过10000条,使用SearchResponse的getHits().getTotalHits().value返回的结果永远是10000。
为什么会被限制只能搜索10000条数据呢?如何查询精确的数据总数呢?
Tips: 本文侧重点在如何精确的获取数据总数,如果想知道如何深度搜索,请参考我的另一篇博客
Elasticsearch from+size与scroll混合使用实现深度分页搜索
二. 问题分析
查看官方文档:
Elasticsearch 7.3
Elasicsearch通过index.max_result_window参数控制了能够获取的数据总数from+size的最大值,默认是10000条。但是,由于数据需要从其它节点分别上报到协调节点,因此搜索请求的数据越多,会导致在协调节点占用分配给Elasticsearch的堆内存和搜索、排序时间越大。针对这种满足条件数量较多的深度搜索,官方建议我们使用Scroll。
三. 解决方案
3.1 调大index.max_result_window(不推荐)
既然知道了是index.max_result_window参数限制了搜索数量,我们可以通过
适当
调高index.max_result_window的值,以此来满足需求。设置方法如下:
- kibana上执行
新建索引:
PUT your_index
{
"settings": {
"max_result_window": "100000"
}
}
在原有索引的基础上,调大index.max_result_window的默认值:
PUT your_index/_settings?preserve_existing=true
{
"max_result_window": "100000"
}
- 服务器上执行
curl -H "Content-Type: application/json" -X PUT 'http://127.0.0.1:9200/your_index/_settings?preserve_existing=true' -d '{"max_result_window" : "100000"}'
这个方案我个人不太推荐,除非能预估出生产环境中索引内数据总量可能达到的上限,否则在未来实际数据量可能会超过设置的值,仍然会再次引发搜索数量受限的问题。
3.2 cardinality(不推荐)
cardinality字面意思是基数,作为聚合函数,它的作用与Mysql中的distinct类似,用于统计给定字段的不同值的数量。值得注意的是,
cardinality获取的仅仅是估计值。
使用方式如下:
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
// 设置聚合函数
AggregationBuilder aggregationBuilder = AggregationBuilders.cardinality("distinct_id").field("_id");
sourceBuilder.aggregation(aggregationBuilder);
// 调用ES客户端,发起请求,得到响应结果
response = search("INDEX_NAME索引名称", sourceBuilder);
// 获取总记录数
total = ((ParsedCardinality)response.getAggregations().getAsMap().get("distinct_id")).getValue();
其中,“distinct_id”是我为聚合函数随便起的名称,可以任意指定,”_id”是希望进行分组统计的字段名称。上方这一段代码实际上可以翻译成以下执行语句:
GET index_name/_search
{
"aggs": {
"distinct_id": {
"cardinality": {
"field": "_id"
}
}
}
}
3.3 track_total_hits(推荐)
文档:
track_total_hits
使用方式:
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.trackTotalHits(true);
// 省略查询方法...
SearchResponse sumResponse = search(sourceBuilder);
if(sumResponse != null) {
// 满足条件的总记录数
long total = sumResponse.getHits().getTotalHits().value;
}