前言:在接口设计上,对数据进行查询时,往往会采用分页查询的形式进行数据的拉取,主要是为了避免一次性返回过大的结果导致对网络,内存,客户端应用程序,集群服务等产生过大的压力,导致出现性能问题。在elasticsearch中分页查询主要有两种方式,from size分页查询与scroll深度分页查询。
使用from
和size
参数来进行分页查询。from
参数用于指定查询结果的起始位置,size
参数用于指定每页返回的文档数量。
#Python
from elasticsearch import Elasticsearch
# 创建 Elasticsearch 客户端
es = Elasticsearch()
# 定义查询条件
query = {
"query": {
"match_all": {} # 这里可以替换为其他查询条件
},
"size": 10, # 每页返回的文档数量
"from": 0 # 查询结果的起始位置
}
# 执行查询
result = es.search(index="your_index_name", body=query)
# 处理查询结果
total_hits = result["hits"]["total"]["value"] # 总命中数
hits = result["hits"]["hits"] # 查询命中的文档列表
for hit in hits:
# 处理每个文档的数据
doc_id = hit["_id"]
source = hit["_source"]
# 输出分页信息
print(f"Total hits: {total_hits}")
print(f"Returned hits: {len(hits)}")
#Java
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.builder.SearchSourceBuilder;
public class PaginationExample {
public static void main(String[] args) {
// 创建 Elasticsearch 客户端
RestHighLevelClient client = new RestHighLevelClient();
// 定义查询请求
SearchRequest searchRequest = new SearchRequest("your_index_name");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.query(QueryBuilders.matchAllQuery()); // 这里可以替换为其他查询条件
sourceBuilder.from(0); // 查询结果的起始位置
sourceBuilder.size(10); // 每页返回的文档数量
searchRequest.source(sourceBuilder);
try {
// 执行查询
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
// 处理查询结果
long totalHits = response.getHits().getTotalHits().value; // 总命中数
SearchHit[] hits = response.getHits().getHits(); // 查询命中的文档列表
for (SearchHit hit : hits) {
// 处理每个文档的数据
String docId = hit.getId();
Map<String, Object> source = hit.getSourceAsMap();
// ...
}
// 输出分页信息
System.out.println("Total hits: " + totalHits);
System.out.println("Returned hits: " + hits.length);
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭 Elasticsearch 客户端
try {
client.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
当我们在使用from size这种方式对elasticsearch返回的数据进行分页时,使用方式上类似于关系型数据库的limit offset,offset
;
在日常搜索场景下,我们可以通过对结果进行评分的排序,来提高搜索结果的相关性,使用该方式将最相关的数据返回给客户端。设置from
参数来指定查询结果的起始位置,size
参数来指定每页返回的文档数量。
当我们使用这种方式进行分页查询时,elasticsearch默认上限为10000条数据。虽然我们可以通过设置该参数index.max_result_window
来提高分页查询返回条数的上限,但提高该上限可能会造成以下问题,影响集群稳定运行。
当分页的数据超过10000条时,我们又需要返回大量的结果,我们可以通过search_after的方式。其操作步骤如下: 1. 首先我们获取一个pit,并设置有效时间为1分钟,其作用为创建一个时间点,保留索引当前的搜索状态,以避免多次搜索后,结果不一致。
POST /my-index-000001/_pit?keep_alive=1m
然后elasticsearch会返回一个ID给我们。
{
"id": "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA=="
}
2. 在查询时,携带pit。此时我们在搜索时,搜索的结果均为该时间点的索引状态内的数据。搜索请求命中的数据会自动添加至携带了pit的搜索请求中。_shard_doc作为索引分片与文档在lucene内部的id的组合生成的唯一值,在我们的搜索请求中,我们可以自定义对齐排序。
GET /_search
{
"size": 10000,
"query": {
"match" : {
"user.id" : "elkbee"
}
},
"pit": {
"id": "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA==",
"keep_alive": "1m"
},
"sort": [
{"@timestamp": {"order": "asc", "format": "strict_date_optional_time_nanos", "numeric_type" : "date_nanos" }}
]
}
3. 当我们需要获取下一页结果时,只需要将上一次命中的排序值,作为参数,重新执行一次search_after请求即可。
GET /_search
{
"size": 10000,
"query": {
"match" : {
"user.id" : "elkbee"
}
},
"pit": {
"id": "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA==",
"keep_alive": "1m"
},
"sort": [
{"@timestamp": {"order": "asc", "format": "strict_date_optional_time_nanos"}}
],
"search_after": [
"2021-05-20T05:30:04.832Z",
4294967298
],
"track_total_hits": false
}
4. 在使用完成后,我们还需要将pit进行删除。以结束该时间点的索引状态。
DELETE /_pit
{
"id" : "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA=="
}
通过scroll游标在索引中对数据进行滚动请求,每次只需要携带_scroll_id,就在多个请求之间保持查询上下文,并逐步滚动结果集,以获取更多的文档。
scroll
参数来指定滚动请求的有效期,并设置 size
参数来指定每次滚动请求返回的文档数量。 scroll
参数和使用上一个请求返回的滚动 ID。 #发起一个scroll查询,游标id的有效时间为一分钟。
POST /my-index-000001/_search?scroll=1m
{
"size": 100,
"query": {
"match": {
"message": "foo"
}
}
}
在scroll查询中,scroll_id的有效时间默认为1分钟,我们在进行大量数据查询,或进行大量数据导出时,为了方便可能会将有效时间设置的很大,如果keep alive时间设置过大可能会造成以下问题:
因此,我们需要根据业务需求与集群资源负载,合理的设置keep alive的有效时间范围,将有效时间设置为适当的范围,以便集群能够一定时间内能够处理滚动查询,并及时释放资源。
在新版本的elasticsearch中,已经引入了Search_after API与Cursor API来逐步替代Scroll API,我们将在后续的文章中进行讨论。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。