ES多种分页方案以及深分页处理

这篇具有很好参考价值的文章主要介绍了ES多种分页方案以及深分页处理。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

ElasticSearch 是一个实时的分布式搜索与分析引擎,常用于大量非结构化数据的存储和快速检索场景,具有很强的扩展性。纵使其有诸多优点,在搜索领域远超关系型数据库,但依然存在与关系型数据库同样的深度分页问题,本文将介绍ES的多种分页方式以及深分页的处理。

From + Size 分页方式

from + size 分页方式是 ES 最基本的分页方式,类似于关系型数据库中的 limit 方式。from 参数表示:分页起始位置;size 参数表示:每页获取数据条数。例如:

GET /index/_search
{
  "from": 10,
  "size": 20
}

代码示例

private SearchHits getSearchHits(BoolQueryBuilder builder, int from, int size) {
        SearchRequestBuilder searchRequestBuilder = this.prepareSearch();
        searchRequestBuilder.setQuery(builder).setFrom(from).setSize(size).setExplain(false);
        SearchResponse searchResponse = searchRequestBuilder.execute().actionGet();
        return searchResponse.getHits();
    }

该条 DSL 语句表示从搜索结果中第 10 条数据位置开始,取之后的 20 条数据作为结果返回。

注: ES 对结果窗口的返回数据有默认 10000 条的限制(参数:index.max_result_window = 10000),当> from + size 的条数大于 10000 条时 ES 提示可以通过 scroll 方式进行分页,非常不建议调大结果窗口参数值。

Scroll 滚动分页

scroll 分页方式类似关系型数据库中的 cursor,首次查询时会生成并缓存快照,返回给客户端快照读取的位置参数(scroll_id),后续每次请求都会通过 scroll_id 访问快照实现快速查询需要的数据,有效降低查询和存储的性能损耗。

                    SearchRequest searchRequest = new SearchRequest();
                    searchRequest.indices(index);
                    searchRequest.types(EsDicConstant.FULL_TEXT);
                    
                    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
                    sourceBuilder.fetchSource(new String[], null);
                    sourceBuilder.size(10);
                    searchRequest.scroll(TimeValue.timeValueSeconds(60));
                    searchRequest.source(sourceBuilder);
                    SearchResponse search = restClient.search(searchRequest, RequestOptions.DEFAULT);
                    while (search.getHits().getHits().length > 0) //滚动查询 
                    {
                        SearchHits hits = search.getHits();
                        BulkRequest bulkRequest = new BulkRequest();
                        bulkRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);
                        for (SearchHit searchHit : hits)
                        {
                            String id = searchHit.getId();
                            JSONObject jsonObject = JSONObject.parseObject(searchHit.getSourceAsString());
                        }
                        if (search.getHits().getHits().length < 10)
                        {
                            break;
                        }
                        String scrollId = search.getScrollId();
                        if (StringUtils.isEmpty(scrollId))
                        {
                            break;
                        }
                        SearchScrollRequest searchScrollRequest = new SearchScrollRequest(scrollId);
                        searchScrollRequest.scroll(TimeValue.timeValueSeconds(60));
                        search = restClient.scroll(searchScrollRequest, RequestOptions.DEFAULT);
                    }

第一次查询时不需要传入_scroll_id,只要带上 scroll 的过期时间参数(scroll=1m)、每页大小(size)以及需要查询数据的自定义条件即可,查询后不仅会返回结果数据,还会返回_scroll_id。

 GET /_search/scroll
  {   "scroll":"1m", 
	  "scroll_id" : "DnF1ZXJ5VGhlbkZldGNoIAAA"
 }

第二次查询时不需要指定索引,在 JSON 请求体中带上前一个查询返回的 scroll_id,同时传入 scroll 参数,指定刷新搜索结果的缓存时间(上一次查询缓存 1 分钟,本次查询会再次重置缓存时间为 1 分钟)

总结
scroll分页方式的优点就是减少了查询和排序的次数,避免性能损耗。缺点就是只能实现上一页、下一页的翻页功能,不兼容通过页码查询数据的跳页,同时由于其在搜索初始化阶段会生成快照,后续数据的变化无法及时体现在查询结果,因此更加适合一次性批量查询或非实时数据的分页查询。启用游标查询时,需要注意设定期望的过期时间(scroll= 1m),以降低维持游标查询窗口所需消耗的资源。注意这个过期时间每次查询都会重置刷新为 1 分钟,表示游标的闲置失效时间(第二次以后的查询必须带 scroll = 1m 参数才能实现)

Search After 分页

Search After 分页方式是 ES 5 新增的一种分页查询方式,其实现的思路同 Scroll 分页方式基本一致,通过记录上一次分页的位置标识,来进行下一次分页数据的查询。相比于 Scroll 分页方式,它的优点是可以实时体现数据的变化,解决了查询快照导致的查询结果延迟问题。

GET /index/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "range": {
            "shipmentOrderCreateTime": {
              "gte": "2021-10-12 00:00:00",
              "lt": "2021-10-15 00:00:00"
            }
          }
        }
      ]
    }
  },
  "size": 20,
  "sort": [
    {
      "_id": {
        "order": "desc"
      }
    },{
      "shipmentOrderCreateTime":{
        "order": "desc"
      }
    }
  ]
}

接下来每次查询时都带上本次查询的最后一条数据的 _id 和 shipmentOrderCreateTime 字段,循环往复就能够实现不断下一页的功能

实现示例

GET /INDEX/_search {   "query": {
    "bool": {
      "must": [
        {
          "range": {
            "shipmentOrderCreateTime": {
              "gte": "2021-10-12 00:00:00",
              "lt": "2021-10-15 00:00:00"
            }
          }
        }
      ]
    }   },   "size": 20,   "sort": [
    {
      "_id": {
        "order": "desc"
      }
    },{
      "shipmentOrderCreateTime":{
        "order": "desc"
      }
    }   ],   "search_after": ["SO-460_152-1447931043809128448-100017918838",1634077436000] }
public <T> ScrollDto<T> queryScrollDtoByParamWithSearchAfter(
            BoolQueryBuilder queryParam, Class<T> targetClass, int pageSize, String afterId,
            List<FieldSortBuilder> fieldSortBuilders) {
        SearchResponse scrollResp;
        long now = System.currentTimeMillis();
        SearchRequestBuilder builder = this.prepareSearch();
        if (CollectionUtils.isNotEmpty(fieldSortBuilders)) {
            fieldSortBuilders.forEach(builder::addSort);
        }
        builder.addSort("_id", SortOrder.DESC);
        if (StringUtils.isBlank(afterId)) {
            SearchRequestBuilder searchRequestBuilder = builder.setSearchType(SearchType.DFS_QUERY_THEN_FETCH)
                    .setQuery(queryParam).setSize(pageSize);
            scrollResp = searchRequestBuilder.execute()
                    .actionGet();
        } else {
            Object[] afterIds = JSON.parseObject(afterId, Object[].class);
            SearchRequestBuilder searchRequestBuilder = builder.setSearchType(SearchType.DFS_QUERY_THEN_FETCH)
                    .setQuery(queryParam).searchAfter(afterIds).setSize(pageSize);
            scrollResp = searchRequestBuilder.execute()
                    .actionGet();
        }
        SearchHit[] hits = scrollResp.getHits().getHits();
        now = System.currentTimeMillis();
        List<T> list = new ArrayList<>();
        if (ArrayUtils.getLength(hits) > 0) {
            list = Arrays.stream(hits)
                    .filter(Objects::nonNull)
                    .map(SearchHit::getSourceAsMap)
                    .filter(Objects::nonNull)
                    .map(JSON::toJSONString)
                    .map(e -> JSON.parseObject(e, targetClass))
                    .collect(Collectors.toList());
            afterId = JSON.toJSONString(hits[hits.length - 1].getSortValues());
        }
     scrollResp.getHits().getTotalHits(), hits.length, System.currentTimeMillis() - now);
        return ScrollDto.<T>builder().scrollId(afterId).result(list).totalRow((int) scrollResp.getHits().getTotalHits()).build();
    }

总结
Search After 分页方式采用记录作为游标,因此 Search After 要求 doc 中至少有一条全局唯一变量(示例中使用_id和时间戳,实际上_id 已经是全局唯一)。Search After 方式是无状态的分页查询,因此数据的变更能够及时的反映在查询结果中,避免了Scroll 分页方式无法获取最新数据变更的缺点。同时 Search After 不用维护 scroll_id 和快照,因此也节约大量资源。

三种对比

es深度分页问题及针对不同需求下的解决方案,elasticsearch,大数据,搜索引擎,es文章来源地址https://www.toymoban.com/news/detail-853884.html

  • 如果数据量小(from+size 在 10000 条内),或者只关注结果集的 TopN 数据,可以使用 from/size 分页,简单粗暴
  • 数据量大,深度翻页,后台批处理任务(数据迁移)之类的任务,使用 scroll 方式
  • 数据量大,深度翻页,用户实时、高并发查询需求,使用 search after 方式

到了这里,关于ES多种分页方案以及深分页处理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • ElasticSearch - DSL查询文档语法,以及深度分页问题、解决方案

    目录 一、DSL 查询文档语法 前言 1.1、DSL Query 基本语法 1.2、全文检索查询 1.2.1、match 查询 1.2.2、multi_match 1.3、精确查询 1.3.1、term 查询 1.3.2、range 查询 1.4、地理查询 1.4.1、geo_bounding_box 1.4.2、geo_distance 1.5、复合查询 1.5.1、相关性算分 1.5.2、function_score 1.5.3、boolean query 1.6、搜索

    2024年02月07日
    浏览(51)
  • ElasticSearch(ES)深度分页详解

    ElasticSearch 是一个实时的分布式搜索与分析引擎,常用于大量非结构化数据的存储和快速检索场景,具有很强的扩展性。纵使其有诸多优点,在搜索领域远超关系型数据库,但依然存在与关系型数据库同样的深度分页问题,本文就此问题做一个实践性分析探讨 from + size 分页方

    2024年01月23日
    浏览(42)
  • es中3种分页的介绍以及对比

    类型 原理 优点 缺点 使用场景 from + size 类似 msql的 limit 0,100;  limit  from,size 灵活性好,实现简单,适合浅分页 无法实现深度分页问题 ,当查询数量超过10000就会报错 top10000以内的查询 Scroll 首次查询会在内存中保存一个历史快照以及游标(scroll_id),记录当前消息查询的终

    2024年02月03日
    浏览(45)
  • 什么是limit 深分页问题?怎么解决?

    什么是limit 深分页问题?怎么解决? select * from table order by key limit 10; select * from table order by key limit 9999, 10; 我们想到给这个要排序的字段加上索引,但是加上索引之后,查询的速度依旧很慢。 当深分页的时候,优化器都不会选择走key这个辅助索引了,而是选择type = All,全表

    2024年02月06日
    浏览(44)
  • ElasticSearch进阶:多种查询操作,各种ES查询以及在Java中的实现

    目录 前言 1 词条查询 1.1 等值查询-term 1.2 多值查询-terms 1.3 范围查询-range 1.4 前缀查询-prefix 1.5 通配符查询-wildcard 2 复合查询 2.1 布尔查询 2.2 Filter查询 3 聚合查询 3.1 最值、平均值、求和 3.2 去重查询 3.3 分组聚合 3.3.1 单条件分组 3.3.2 多条件分组 3.4 过滤聚合 ElasticSearch 第一篇

    2024年02月02日
    浏览(53)
  • ElasticSearch | SpringBoot集成ES异常以及处理方法

    异常 Elasticsearch exception Elasticsearch exception [type=cluster_block_exception, reason=index [video_struct_vehicle] blocked by: [FORBIDDEN/12/index read-only / allow delete (api)]; 产生原因 服务磁盘内存不足,导致无法删除,所有索引都变成了只读 read-only状态。 一旦在存储超过95%的磁盘中的节点上分配了一个

    2024年02月13日
    浏览(40)
  • Linux中Es安装启动以及启动问题

    1:安装包 链接:https://pan.baidu.com/s/1-sohDpOA-8qh58miRPh8AQ 提取码:ukz3 安装包解压到自己的目录: 2:启动问题 (注意 es 只能用非root用户启动)

    2024年02月12日
    浏览(54)
  • ES千亿级检索实战 堆OOM 问题深度分析

      在特大规模的索引中检索,通常一次检索涉及到的分片数达到2000个左右。加上跨集检索,堆有非常大的压力,OOM的问题经常发生。本篇文章,对线上环境的堆进行深度分析,看看都有什么。   我使用prifile来分析查看堆快照。并结合目前我对es底层的了解,来分析堆中都有

    2023年04月15日
    浏览(37)
  • ES7版本索引创建失败问题处理

    The mapping definition cannot be nested under a type [_doc] unless include_type_name is set to true 在给ES7手动创建索引的时候,会出现 这个异常。 代码如下 这个异常是说不能在type类型上创建映射 , 在es7中已经在内部取消了, type。 只不过还保留着基本的语法留着过度,因此需要改成这下面这种

    2024年02月12日
    浏览(45)
  • 【ES实战】ES分页与去重

    介绍ES分页和ES去重的实现方式。 from + size 每次对结果集都是全面分页,数量少时可以用,10000条以内( index.max_result_window 的默认值)。对很占内存。10个一条 深度就是1000。控制分页的深度意在控制协调节点上的队列长度。 问题说明 一个有 5 个主分片的索引中搜索。 当我们请

    2023年04月25日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包