elasticsearch 查询超10000的解决方案

news2025/1/12 23:16:25

前言

默认情况下,Elasticsearch集群中每个分片的搜索结果数量限制为10000。这是为了避免潜在的性能问题。

但是我们 在实际工作过程中时常会遇到 需要深度分页,以及查询批量数据更新的情况

问题:当请求form + size >10000 时,请求直接报错

在这里插入图片描述

1:修改max_result_window 参数(不推荐)

在此方案中,我们建议仅限于测试用,生产禁用,毕竟当数据量大的时候,过大的数据量可能导致es的内存溢出,直接崩掉,一年绩效白干。

PUT wkl_test/_settings
{
   "index":{
        "max_result_window":2147483647
    }
}

查看索引的 settings
在这里插入图片描述
重新查数据:

在这里插入图片描述

2:使用游标 scroll API

使用scroll API:scroll API可以帮助我们在不加载所有数据的情况下获取所有结果。它会在后台执行查询以获取滚动ID,并将其用于进行后续查询。这样就可以一次性获取所有结果,而不必担心限制

ES语句查询

在游标方案中,我们只需要在第一次拿到游标id,之后通过游标就能唯一确定查询,在这个查询中通过我们指定的 size 移动游标,具体操作看看下面实操。

  • 游标查询,设置游标有效时间,有效时间内,游标都可以使用,过期就不行了
GET wkl_test/_search?scroll=5m
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "seq": {
        "order": "asc"
      }
    }
  ],
  "size": 200
}
  • 上面操作中通过游标的结果返回
    在这里插入图片描述
  • 之后将_scroll_id 复制到窗口,就可以不端通过这个_scroll_id 进行之前设置的页数不断翻页
    以此类推,后面每次滚屏都把前一个的scroll_id复制过来。注意到,后续请求时没有了index信息,size信息等,这些都在初始请求中,只需要使用scroll_id和scroll两个参数即可。
    在这里插入图片描述
    注意,此时游标移动了,所以我们可以通过游标的方式不断后移,直到移动到我们想要的 from+size 范围内。再次点击
    在这里插入图片描述

java实现


@Test
    public void testScroll(){
        RestHighLevelClient restHighLevelClient ;
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        boolQueryBuilder.mustNot(QueryBuilders.existsQuery("seq"));

        try {
            //滚动查询的Scroll,设置请求滚动时间窗口时间
            Scroll scroll = new Scroll(TimeValue.timeValueMillis(180000));

            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            //加入query语句
            sourceBuilder.query(boolQueryBuilder);
            //每次滚动的长度
            sourceBuilder.size(SIZE);
            //加入排序字段
            sourceBuilder.sort("id", SortOrder.DESC);
            //构建searchRequest
            //加入scroll和构造器
            SearchRequest searchRequest = new SearchRequest()
                    .indices("wkl_test")
                    .source(sourceBuilder)
                    .scroll(scroll);
            //存储scroll的list
            List<String> scrollIdList = new ArrayList<>();
            //执行首次检索
            SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
            //首次检索返回scrollId,用于下一次的滚动查询
            String scrollId = searchResponse.getScrollId();
            //拿到hits结果
            SearchHit[] hits = searchResponse.getHits().getHits();
            long value = searchResponse.getHits().getTotalHits().value;
            //保存返回结果List大小
            Long resultSize = 0L;
            scrollIdList.add(scrollId);
            try {
                //滚动查询将SearchHit封装到result中
                while (ArrayUtils.isNotEmpty(hits) && hits.length > 0) {
                    BulkRequest bulkRequest = new BulkRequest();
                    JSONArray esArray = new JSONArray();
                    for (SearchHit hit : hits) {
                        String sourceAsString = hit.getSourceAsString();
                        String index = hit.getIndex();
                        JSONObject jsonObject = JSONObject.parseObject(sourceAsString);
                        String seq = jsonObject.getString("seq");
                        if(StringUtils.isBlank(seq) ){
                            esArray.add(jsonObject);
                            String uuid = jsonObject.getString("id");
                            jsonObject.put("is_del",1);
                            bulkRequest.add(new UpdateRequest(index, uuid).doc(jsonObject));
                        }
                    }
                    resultSize = resultSize+hits.length;

                    //发送请求
                    //实时更新
                    bulkRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);
                    BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
                    System.out.println(bulk.getTook()+"-------"+bulk.getItems().length);

                    //说明滚动完了,返回结果即可
                    if (resultSize > 20000) {
                        break;
                    }
                    //继续滚动,根据上一个游标,得到这次开始查询位置
                    SearchScrollRequest searchScrollRequest = new SearchScrollRequest(scrollId);
                    searchScrollRequest.scroll(scroll);
                    //得到结果
                    SearchResponse searchScrollResponse = restHighLevelClient.scroll(searchScrollRequest, RequestOptions.DEFAULT);
                    //定位游标
                    scrollId = searchScrollResponse.getScrollId();
                    hits = searchScrollResponse.getHits().getHits();
                    scrollIdList.add(scrollId);
                }
                System.out.println("----彻底结束了-----");
            } finally {
                //清理scroll,释放资源
                ClearScrollRequest clearScrollRequest = new ClearScrollRequest();
                clearScrollRequest.setScrollIds(scrollIdList);
                restHighLevelClient.clearScroll(clearScrollRequest, RequestOptions.DEFAULT);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

scroll API 的优缺点和总结

优缺点:

  • scroll查询的相应数据是非实时的,如果遍历过程中插入新的数据,是查询不到的。并且保留上下文需要足够的堆内存空间。
  • 相比于 from/size 和 search_after 返回一页数据,Scroll API 可用于从单个搜索请求中检索大量结果。但是 scroll 滚动遍历查询是非实时的,数据量大的时候,响应时间可能会比较长

适用场景

  • 全量或数据量很大时遍历结果数据,而非分页查询。
  • scroll方案基于快照,不能用在高实时性的场景下,建议用在类似数据导出场景下使用

3: search_after + PIT 深度查询

  • Search_after是 ES 5 新引入的一种分页查询机制,其原理几乎就是和scroll一样,因此代码也几乎是一样的。
  • 官方文档说明不再建议使用scroll滚动分页和from size分页,建议使用search_after
  • search_after 分页的方式和 scroll 搜索有一些显著的区别,首先它是根据上一页的最后一条数据来确定下一页的位置,同时在分页请求的过程中,如果有索引数据的增删改查,这些变更也会实时的反映到游标上。

不带PIT

ES语句实现

检索第一页的查询如下所示:

GET wkl_test/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "seq": {
        "order": "asc"
      }
    }
  ],
  "size": 200
}

上述请求的结果包括每个文档的 sort 值数组。
在这里插入图片描述

这些 sort 值可以与 search_after 参数一起使用,以开始返回在这个结果列表之后的任何文档。例如,我们可以使用上一个文档的 sort 值并将其传递给 search_after 以检索下一页结果:

在这里插入图片描述

Java 实现

@Test
    public void testSearchAfter() throws IOException {
        RestHighLevelClient restHighLevelClient = es7UtilApi.getRestHighLevelClient();

        MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery();

        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(matchAllQueryBuilder);
        searchSourceBuilder.from(0);
        searchSourceBuilder.size(200);
        searchSourceBuilder.sort("seq", SortOrder.ASC);
        searchSourceBuilder.trackTotalHits(true);

        SearchRequest searchRequest = new SearchRequest()
                .indices("wkl_test")
                .source(searchSourceBuilder);

        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        SearchHits hits = searchResponse.getHits();
        long value = hits.getTotalHits().value;
        System.out.println("查询到记录数=" + value);

        List<JSONObject> list = new ArrayList<>();
        SearchHit[] searchHists = hits.getHits();
        Object[] sortValues = searchHists[searchHists.length - 1].getSortValues();
        if (searchHists.length > 0) {
            for (SearchHit hit : searchHists) {
                String sourceAsString = hit.getSourceAsString();
                JSONObject jsonObject = JSON.parseObject(sourceAsString);
                jsonObject.put("_id", hit.getId());
                list.add(jsonObject);
            }
        }

        //往后的每次请求都携带上一次的sort_id进行访问。
        while (ArrayUtils.isNotEmpty(searchHists) && searchHists.length > 0){
            searchSourceBuilder.searchAfter(sortValues);
            searchRequest.source(searchSourceBuilder);
            SearchResponse searchResponseAfter = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
            hits = searchResponseAfter.getHits();
            searchHists = hits.getHits();
            sortValues = searchHists[searchHists.length - 1].getSortValues();
            if (searchHists.length > 0) {
                for (SearchHit hit : searchHists) {
                    String sourceAsString = hit.getSourceAsString();
                    JSONObject jsonObject = JSON.parseObject(sourceAsString);
                    jsonObject.put("_id", hit.getId());
                    list.add(jsonObject);
                }
            }
            if(list.size()>20000){
                break;
            }
            System.out.println("-----彻底结束了-------");
        }

    }

问题

「优点:」

  • 无状态查询,可以防止在查询过程中,数据的变更无法及时反映到查询中。

  • 不需要维护scroll_id,不需要维护快照,因此可以避免消耗大量的资源。

「缺点:」

  • 由于无状态查询,因此在查询期间的变更可能会导致跨页面的不一值。

  • 排序顺序可能会在执行期间发生变化,具体取决于索引的更新和删除。

  • 至少需要制定一个唯一的不重复字段来排序。

  • 它不适用于大幅度跳页查询,或者全量导出,对第N页的跳转查询相当于对es不断重复的执行N次search after,而全量导出则是在短时间内执行大量的重复查询。

带PIT

关于PIT

  • 在7.*版本中,ES官方不再推荐使用Scroll方法来进行深分页,而是推荐使用带PIT的search_after来进行查询;

  • 从7.*版本开始,您可以使用SEARCH_AFTER参数通过上一页中的一组排序值检索下一页命中。

  • 使用SEARCH_AFTER需要多个具有相同查询和排序值的搜索请求。

  • 如果这些请求之间发生刷新,则结果的顺序可能会更改,从而导致页面之间的结果不一致。
    为防止出现这种情况,您可以创建一个时间点(PIT)来在搜索过程中保留当前索引状态。

ES语句实现

1:生成pit
#keep_alive必须要加上,它表示这个pit能存在多久,这里设置的是1分钟
POST wkl_test/_pit?keep_alive=1m

在这里插入图片描述

2:在搜索请求中指定PIT:

在每个搜索请求中添加 keep_alive 参数来延长 PIT 的保留期,相当于是重置了一下时间


GET _search
{
  "query": {
    "match_all": {}
  },
  "pit":{
    "id":"t_yxAwEId2tsX3Rlc3QWU0hzbEJkYWNTVEd0ZGRoN0xsQVVNdwAWUGQtaXJpT0xTa2VUN0RGLXZfTlBvZwAAAAAACHG1fxY1UWNKX1RHOFMybXBaV20zbWx3enp3ARZTSHNsQmRhY1NUR3RkZGg3TGxBVU13AAA=",
    "keep_alive":"5m"
  },
  "sort": [
    {
      "seq": {
        "order": "asc"
      }
    }
  ],
  "size": 200
}

在这里插入图片描述

3:删除PIT
DELETE _pit
{
 "id":"t_yxAwEId2tsX3Rlc3QWU0hzbEJkYWNTVEd0ZGRoN0xsQVVNdwAWUGQtaXJpT0xTa2VUN0RGLXZfTlBvZwAAAAAACHG1fxY1UWNKX1RHOFMybXBaV20zbWx3enp3ARZTSHNsQmRhY1NUR3RkZGg3TGxBVU13AAA="
}

在这里插入图片描述

总结

  • 如果数据量小(from+size在10000条内),或者只关注结果集的TopN数据,可以使用from/size 分页,简单粗暴

  • 数据量大,深度翻页,后台批处理任务(数据迁移)之类的任务,使用 scroll 方式

  • 数据量大,深度翻页,用户实时、高并发查询需求,使用 search after 方式

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1921187.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux环境下Oracle 11g的离线安装与配置历程

在成功体验了 Windows 版本的Oracle 11g 后&#xff0c;这几天心血来潮&#xff0c;决定再挑战一下Linux 环境下的安装&#xff0c;特别是在考虑到部门内部虚拟机无法联网的情况下&#xff0c;我选择了在CentOS 7上进行离线安装。这次安装之旅&#xff0c;主要参考了下面大佬的…

异步日志:性能优化的金钥匙

一、背景 2024 年 4 月的一个宁静的夜晚&#xff0c;正当大家忙完一天的工作准备休息时&#xff0c;应急群里“咚咚咚”开始报警&#xff0c;提示我们余利宝业务的赎回接口成功率下降。 通过 Monitor 监控发现&#xff0c;该接口的耗时已经超过了网关配置的超时阈值(2s)&#…

【验收支撑】软件系统验收计划书(直接套用原件doc)

编写软件验收计划是软件开发过程中的一个关键步骤&#xff0c;其重要性体现在以下几个方面&#xff1a; 明确验收标准&#xff1a;软件验收计划详细列出了验收的标准、测试方法、测试环境等&#xff0c;确保所有相关人员对验收的期望和要求有清晰的认识。这有助于避免在验收阶段…

JavaWeb系列二十一: 数据交换和异步请求(JSON, Ajax)

文章目录 官方文档official documents官方文件官方文件official documentsJSON介绍JSON快速入门JSON对象和字符串对象转换应用案例注意事项和细节 JSON在java中使用说明JSON在Java中应用场景应用实例 Ajax基本介绍Ajax是什么Ajax经典应用场景 Ajax原理示意图传统的web应用Ajax原…

用微客云搭建一套外卖霸王餐系统赚CPS佣金

在当下数字化快速发展的时代&#xff0c;外卖行业作为餐饮业的重要分支&#xff0c;正在经历着前所未有的变革。为了满足市场需求&#xff0c;提高用户体验和增加商户收入&#xff0c;越来越多的外卖平台开始寻求创新&#xff0c;其中&#xff0c;搭建一套高效、稳定且功能丰富…

昇思25天学习打卡营第19天|CycleGAN图像风格迁移互换

CycleGAN图像风格迁移互换 模型介绍 模型简介 CycleGAN(Cycle Generative Adversarial Network) 即循环对抗生成网络&#xff0c;来自论文 Unpaired Image-to-Image Translation using Cycle-Consistent Adversarial Networks 。该模型实现了一种在没有配对示例的情况下学习将…

jenkins打包java项目报错Error: Unable to access jarfile tlm-admin.jar

jenkins打包boot项目 自动重启脚本失败 查看了一下项目日志报错&#xff1a; Error: Unable to access jarfile tlm-admin.jar我检查了一下这个配置&#xff0c;感觉没有问题&#xff0c;包可以正常打&#xff0c; cd 到项目目录下面&#xff0c;手动执行这个sh脚本也是能正常…

本地Kali系统开启SSH服务并使用内网穿透生成公网地址实现ssh远程连接

文章目录 前言1. 启动kali ssh 服务2. kali 安装cpolar 内网穿透3. 配置kali ssh公网地址4. 远程连接5. 固定连接SSH公网地址6. SSH固定地址连接测试 前言 本文主要介绍如何在本地Kali Linux系统启动ssh服务&#xff0c;并结合cpolar内网穿透软件生成公网地址&#xff0c;轻松…

提示词工程(Prompt Engineering)是什么?

一、定义 Prompt Engineering 提示词工程&#xff08;Prompt Engineering&#xff09;是一项通过优化提示词&#xff08;Prompt&#xff09;和生成策略&#xff0c;从而获得更好的模型返回结果的工程技术。 二、System message 系统指令 System message可以被广泛应用在&am…

平凯星辰黄东旭出席 2024 全球数字经济大会 · 开放原子开源数据库生态论坛

7 月 5 日&#xff0c;以“开源生态筑基础&#xff0c;数字经济铸未来”为主题的 2024 全球数字经济大会——开放原子开源数据库生态论坛在北京成功举办。平凯星辰&#xff08;北京&#xff09;科技有限公司联合创始人黄东旭发表了题为《TiDB 助力金融行业关键业务系统实践》的…

【TS】typescript 获取函数入参类型、返回值类型、promise返回值类型

文章目录 1. 准备工作2. 获取函数入参的类型3. 获取函数返回值类型4. 获取promise返回值类型 1. 准备工作 创建 utils.ts interface User {id: number;name: string;age: number; } interface Params {method: string;url: string; }function getUserList(params: Params,other…

RocketMQ 消费者之顺序消费和流程详解附源码解析

1. 背景 本文是 RocketMQ 消费者系列的第六篇&#xff0c;上一篇主要介绍并发消费&#xff0c;而本片主要介绍 RocketMQ 顺序消费的设计和流程。 我把 RocketMQ 消费分成如下几个步骤 重平衡 消费者拉取消息 Broker 接收拉取请求后从存储中查询消息并返回 消费者消费消息 顺序…

算法学习day10(贪心算法)

贪心算法&#xff1a;由局部最优->全局最优 贪心算法一般分为如下四步&#xff1a; 将问题分解为若干个子问题找出适合的贪心策略求解每一个子问题的最优解将局部最优解堆叠成全局最优解 一、摆动序列&#xff08;理解难&#xff09; 连续数字之间的差有正负的交替&…

GO channel 学习

引言 单纯地将函数并发执行是没有意义的。函数与函数间需要交换数据才能体现并发执行函数的意义。 虽然可以使用共享内存进行数据交换&#xff0c;但是共享内存在不同的goroutine中容易发生竞态问题。为了保证数据交换的正确性&#xff0c;必须使用互斥量对内存进行加锁&#…

视频语音转文字工具用哪个好?推荐6款优质的视频转文字工具

在沉浸于电影情节时&#xff0c;周遭的喧嚣往往成了享受视听的障碍&#xff0c;这时&#xff0c;字幕的重要性便不言而喻。 字幕的作用远不止于此&#xff0c;它是听力受限观众的桥梁&#xff0c;也是语言学习者的得力助手。幸运的是&#xff0c;将视频语音转文字字幕现已变得…

红酒与未来科技:传统与创新的碰撞

在岁月的长河中&#xff0c;红酒以其深邃的色泽、丰富的口感和不同的文化魅力&#xff0c;成为人类文明中的一颗璀璨明珠。而未来科技&#xff0c;则以其迅猛的发展速度和无限的可能性&#xff0c;领着人类走向一个崭新的时代。当红酒与未来科技相遇&#xff0c;一场传统与创新…

电脑自动重启是什么原因呢?99%人都不知道的解决办法,直接打破循环

当你的电脑突然毫无预警地自动重启&#xff0c;不仅打断了工作流程&#xff0c;还可能导致未保存的数据丢失&#xff0c;这无疑是一件令人沮丧的事情。那么&#xff0c;电脑自动重启是什么原因呢&#xff1f;有什么方法可以解决呢&#xff1f;别担心&#xff0c;在大多数情况下…

对象与键值对数组的相互转换Object.entries与Object.fromEntries

Object.entries是JavaScript中的一个内置方法&#xff0c;它可以将一个对象的属性和值转换为一个包含键值对的数组。 let obj {name: mike,age: 18,sex: man } Object.entries(obj)Object.entries的使用场景: 1、动态更新对象属性 let obj {name: mike, age: 18, sex: man…

《昇思25天学习打卡营第02天|qingyun201003》

日期 心得 通过这次的学习&#xff0c;主要是了解过张量的基础概念&#xff0c;同时也知道有关构造张量的方法。通过索引查询张量&#xff0c;张量的运算。通过concat\stack 将张量进行维度链接。Tensor与NumPy的互相转换&#xff0c;但是我似乎并不了解什么它们的概念。也认知…

电脑视频去水印软件哪个好用,电脑上视频去水印的软件

在数字化时代&#xff0c;视频创作已成为许多人展示才华和创意的重要途径。然而&#xff0c;视频中的水印常常让人感到头疼&#xff0c;尤其是当水印影响了视频的整体美观时。本文将为你揭秘如何在电脑上使用各种软件去除视频水印&#xff0c;让你的作品更加专业&#xff01; 方…