关于SpringBoot集成ES Scroll API(滚动查询)的实践

news2024/12/29 10:40:42

待到秋来九月八,我花开后百花杀

  • 背景:
  • 大胆尝试实践:
  • 学习
  • 踩坑
  • 最终解决

背景:

那是年初在某个交付项目,从用户侧获知了一个elastic search作为分布式数据库的一个瓶颈,那就是单次查询量超过了ES的默认单次查询上限10000。

在大部分业务下,为了执行ES的数据查询,开发者往往都直接使用了query某个条件获取数据,这些条件对应的数据大多都不会超过10000,因此在一般测试下难以发现这类问题。但系统经过经年累月的使用,数据量在不断增长,又因业务需求不可清除旧数据的情况下,这类问题就诞生了。
在这里插入图片描述

于是,我想到Elasticsearch 中,传统的分页查询使用from+size的模式,类似如下语句:

GET /<index>/_search
{
  "from": 0,
  "size": 10,
  "query": {
    "match_all": {}
  }
}

那我们可以大可尝试使用while的方式,每次在拼接query时改变from的值为from+size,那么我每次拿到的就是下一个分页,如此往复,那也可以实现无穷尽也。

大胆尝试实践:

public void search() {
        int from = 0;
        long total = 0;
        int size = 100;

        while (true) {
            NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder()
                    .withPageable(PageRequest.of(from, size))
                    .withSort(new FieldSortBuilder("id").order(SortOrder.DESC))
                    .build();

            SearchHits<Book> searchHits = elasticsearchRestTemplate.search(nativeSearchQuery, Book.class);
            if (!searchHits.hasSearchHits()) {
                break;
            }
            for (SearchHit<Book> searchHit : searchHits.getSearchHits()) {
                Book book = searchHit.getContent();
            }
            page++;
            System.out.println(page);
            System.out.println(total += searchHits.getSearchHits().size());
        }
}

实现完成,点击运行。

Yes!我失败了。

于是,我意识到我并没有认真看关于报错的信息,在我自我批评和自我反省下,再次对报错信息认真解读
在这里插入图片描述
强大的Elasticsearch已经告诉了我答案,建议我扩大index.max_result_window的上限参数,或者参阅scroll api的文档。

学习

至此,开启了我对Elasticsearch Search Scroll API(滚动查询)的学习和探索!

在浅显的入门Scroll API之后,相比之下,自己之前的想法就像是关公面前耍了一次大刀——小聪明了,总结我自己的想法,那就是当程序运行到极限时,我的代码就好似拼装了以下的一个查询语句,尝试在可视化插件中执行该语句,发现依然会报错,报错与单次查询10000条并无差别。

GET /<index>/_search
{
  "from": 9980,
  "size": 10010,
  "query": {
    "match_all": {}
  }
}
{
        "type": "illegal_argument_exception",
        "reason": "Result window is too large, from + size must be less than or equal to: [10000] but was [19990]. See the scroll api for a more efficient way to request large data sets. This limit can be set by changing the [index.max_result_window] index level setting."
}

对于此类问题,Elasticsearch Search为我们提供了一个与springBoot集成的强大依赖ElasticsearchRestTemplate,可以参考官方的 API 文档:
https://www.elastic.co/guide/en/elasticsearch/client/java-rest/7.9/java-rest-high-search-scroll.html

尝试使用解决问题:

public void scrollSearch() {
        NativeSearchQuery query = new NativeSearchQueryBuilder()
                .withSort(new FieldSortBuilder("id").order(SortOrder.DESC))//排序
                .build();
                
        nativeSearchQuery.setMaxResults(1000);
    
        long scrollTimeOut = 60 * 1000;
        // 第一次查询
        SearchScrollHits<Book> searchScrollHits = elasticsearchRestTemplate.searchScrollStart(scrollTimeOut, query, Book.class, IndexCoordinates.of("ss.index.book"));

        while (searchScrollHits.hasSearchHits()) {
            System.out.println(total += searchScrollHits.getSearchHits().size());

            for (SearchHit<Book> searchHit : searchScrollHits.getSearchHits()) {
                Book book = searchHit.getContent();
            }
            // 再次查询
            searchScrollHits = elasticsearchRestTemplate.searchScrollContinue(searchScrollHits.getScrollId(), scrollTimeInMillis, Book.class, IndexCoordinates.of("book"));
        }
}

最后,感谢大家的聆听和阅读,我拿到了我期望的10000条以后的数据,

我以为我这次终于成功了,但我的麻烦才刚刚开始。

踩坑

该问题上线之后不久,一切都运行顺利,我以为已经高枕无忧的时候,我的麻烦再次来临。

用户侧反应了两个问题:

一、查询得时候发现有返回重复的数据。

二、从ES获取后上传数据发生了数据堆积。

查看日志,发现有大量的报错,报错信息表示存放的 scroll_id 超出了默认的500限制。

在这里插入图片描述

trying to creat too many scroll aontexts。Must be less than or equal to: [500].This limit can be set by changing the [search.max_open_scroll_context] setting.

因此我对问题做出以下分析

1、虽然进行 scroll 查询时会记录一个 scroll_id,但只有新请求时才会生成新的 scroll_id,这就有一定概率导致查询到的返回结果出现重复数据的可能。

2、scroll 开启时间过长,导致 scroll_id 的清除时间太久产生堆积,所以引起了数据查询延迟,引起了数据堆积。

这次由于我认真读取了报错信息,但我不愿以牺牲上限为代价,不然那将是无底洞式的调大上限,于是我在原先代码基础上做出了修改:

    public void scrollSearch() {
        NativeSearchQuery query = new NativeSearchQueryBuilder()
                .withSort(new FieldSortBuilder("id").order(SortOrder.DESC))//排序
                .build();

        nativeSearchQuery.setMaxResults(1000);

        long scrollTimeOut = 60 * 1000;
        // 第一次查询
        SearchScrollHits<Book> searchScrollHits = elasticsearchRestTemplate.searchScrollStart(scrollTimeOut, query, Book.class, IndexCoordinates.of("ss.index.book"));
        
        //记录新请求
        SearchScrollHits<Book> newHits;

        while (searchScrollHits.hasSearchHits()) {
            System.out.println(total += searchScrollHits.getSearchHits().size());

            for (SearchHit<Book> searchHit : searchScrollHits.getSearchHits()) {
                Book book = searchHit.getContent();
            }
            // 再次查询
            try {
                newHits = elasticsearchRestTemplate.searchScrollContinue(searchScrollHits.getScrollId(), scrollTimeInMillis, Book.class, IndexCoordinates.of("book"));
                searchScrollHits = newHits;
            } finally {
                elasticsearchRestTemplate.searchScrollClear(Lists.newArrayList(searchScrollHits.getScrollId()));
            }
        }
    }

这样,我每次发出查询请求获得数据之后,都会清除掉上一次生成的scroll_id,如此保证scroll_id不会超出500;

不出意外的,我又出意外了,显然我在对scroll_id理解上出了问题,它不是对每页的标记,而是每一次滚动查询整个生命周期的唯一id,上述代码导致的结果就是我永远只能拿到前两页的内容,之后因为找不到生命周期的唯一id报错。
在这里插入图片描述

最终解决

那么最终导致scroll_id堆积的原因也找到了:searchScrollStart入参从来都不是什么TimeOut时间,而是scroll_id存在的生命周期时间,如果这个时间过大,那么scroll_id就得不到及时清理,最终超过500进而报错,解决方案就会有两种:

1.根据实际每次请求的调用需求时间,调整scroll_id的生命周期,保持在一个合适的时间,从而防止堆积

2.动态清理scroll_id

显然第一种是在难为我胖虎,我选择了第二种,将searchScrollStart视为一次滚动查询请求的开始,searchScrollHits.hasSearchHits()为false时视为结束,此时searchScrollClear在合适不过。

第三次修改:

public void scrollSearch() {
        NativeSearchQuery query = new NativeSearchQueryBuilder()
                .withSort(new FieldSortBuilder("id").order(SortOrder.DESC))
                .build();
        // 设置每页数据量
        nativeSearchQuery.setMaxResults(1000);
    
        long scrollTimeInMillis = 60 * 1000;
        // 第一次查询
        SearchScrollHits<Book> searchScrollHits = elasticsearchRestTemplate.searchScrollStart(scrollTimeInMillis, nativeSearchQuery, Book.class, IndexCoordinates.of("book"));
        String scrollId = searchScrollHits.getScrollId();

        while (searchScrollHits.hasSearchHits()) {
            System.out.println(total += searchScrollHits.getSearchHits().size());

            for (SearchHit<Book> searchHit : searchScrollHits.getSearchHits()) {
                Book book = searchHit.getContent();
            }
            // 再次查询
            searchScrollHits = elasticsearchRestTemplate.searchScrollContinue(scrollId, scrollTimeInMillis, Book.class, IndexCoordinates.of("book"));
            scrollId = searchScrollHits.getScrollId();
        }

        List<String> scrollIds = new ArrayList<>();
        scrollIds.add(scrollId);
        // 清除 scroll
        elasticsearchRestTemplate.searchScrollClear(scrollIds);
}

至此,才是真正的谢谢大家的阅读啦~~~good bye!

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

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

相关文章

DirectX12 - Pipeline(管线)之IA

这里是SunshineBooming&#xff0c;GPU公司一枚小小的Driver工程师&#xff0c;主要工作是写DirectX12 Driver&#xff0c;我会持续更新这个DX12 Spec系列&#xff0c;可能比较冷门&#xff0c;但是都是干货和工作中的心得体会&#xff0c;有任何GPU相关的问题都可以在评论区互…

原来服务端的退出姿势也可以这么优雅

最简单的 http 服务端 咱们来写一个简单的 http 服务器 func main() { srvMux : http.NewServeMux() srvMux.HandleFunc("/getinfo", getinfo) http.ListenAndServe(":9090", srvMux)}func getinfo(w http.ResponseWriter, r *http.Request) { fmt.Printl…

程序猿入门|编程注重写注释,代码规范注释有哪些讲究?

注释风格 1.总述 一般使用 // 或 /* */,只要统一就好。 2.说明 // 或 /* */ 都可以,但 // 更 常用,要在如何注释及注释风格上确保统一。 文件注释 1.总述 在每一个文件开头加入版权、作者、时间等描述。 文件注释描述了该文件的内容,如果一个文件只声明,或实现,或测试…

JVM运行时参数

3.类型三&#xff1a;-XX参数选项 特点 作用 用于开发和调试jvm 分类 特别地 二、添加jvm参数选项 1.运行jar包 2.通过Tomcat运行war包 3.程序运行过程中 三、常用的JVM参数选项 1.打印设置的XX选项及值 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210219105124…

window安装torch环境--不踩坑须知!

window安装torch环境–不踩坑须知&#xff01; 1. 查看电脑安装的cudn版本 进入cmd输入&#xff1a; nvidia-smi2.查找cuda对应的torch版本 在官网查找&#xff1a;https://pytorch.org/get-started/previous-versions/ 如果呢你在新建的conda环境里输入该命令&#xff0c;…

Java基于JSP的网络音乐KTV点歌电台网站

随着我国网民的增加,也促进了网络音乐电台的开发。随着网络技术的发展,人们在利用网络学习的同时,也在享受着网络带来的各种附带产品所产生的效应,如网络游戏,网络歌曲。网络音乐电台正是在这样的需求前提下应运而生,给人们的日常生活带来了极大的乐趣,让人们在繁忙疲惫的工作之…

TensorRT从理论到实践

TensorRT理论 一. TensorRT介绍 TensorRT是一个高性能的深度学习推理优化器&#xff0c;可以为深度学习应用提供低延迟、高吞吐率的部署推理。TensorRT可用于对超大规模数据中心、嵌入式平台或自动驾驶平台进行推理加速。TensorRT现已能支持Tensorflow、Caffe、Mxnet、Pytorc…

《嵌入式基础》实验三 ARM编程模型和ARM指令

零、前言 本人不擅长写汇编相关的东西&#xff0c;所以以下内容也是不断摸索&#xff08;百度 &#xff09; 整出来的&#xff0c;和linux的实验报告的质量相比较低。 一、 实验目的 掌握ARM微处理器的汇编指令的使用方法。掌握使用 LDM/STM&#xff0c;B&#xff0c;BL 等指…

Struts、Struts2、Spring MVC、JSF、AngularJS、React以及Vue的对比

Struts是一种Java语言的Web应用框架&#xff0c;用于构建基于Java的Web应用程序。它旨在为开发人员提供一种简单易用的方法来构建动态Web页面。Struts框架提供了一组组件&#xff0c;用于处理常见的Web应用程序任务&#xff0c;包括处理用户输入&#xff0c;验证用户输入&#…

(算法设计与分析)第七章随机化算法概述

文章目录一&#xff1a;概述&#xff08;1&#xff09;什么是随机化算法&#xff08;2&#xff09;随机化算法的特点&#xff08;3&#xff09;随机化算法分类&#xff08;4&#xff09;随机数二&#xff1a;数值随机化算法&#xff08;以计算πππ值为例&#xff09;三&#…

分布式计算 MapReduce 究竟是怎么一回事?

前言 如果要对文件中的内容进行统计&#xff0c;大家觉得怎么做呢&#xff1f;一般的思路都是将不同地方的文件数据读取到内存中&#xff0c;最后集中进行统计。如果数据量少还好&#xff0c;但是面对海量数据、大数据的场景这样真的合适吗&#xff1f;不合适的话&#xff0c;…

操作系统装完之后,安装几个特别有用的经典软件,都是电脑必备,包含pdf编辑、图片编辑、wiki、压缩、影音等等

操作系统装完之后&#xff0c;安装几个特别有用的经典软件&#xff0c;都是电脑必备&#xff0c;包含pdf编辑、图片编辑、wiki、压缩、影音等等。 Gimp https://www.gimp.org/ Gimp 是一款小巧实用的图片编辑工具。 如果你不想用笨重的PS&#xff0c;那可以尝试一下Gimp&…

元胞自动机模拟病毒传染(SEIR模型)(Python代码实现)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客 &#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜…

SSM整合-如何配置相关文件

下述操作都是在IDEA上进行 1.首先新建一个Maven工程。 2.在pom.xml中增加相关依赖 <properties><spring.version>5.3.1</spring.version></properties><dependencies><dependency><groupId>org.springframework</groupId>&l…

安卓玩机搞机技巧综合资源----手机各种代码 查询信息 开启端口 调试选项【十】

接上篇 安卓玩机搞机技巧综合资源------如何提取手机分区 小米机型代码分享等等 【一】 安卓玩机搞机技巧综合资源------开机英文提示解决dm-verity corruption your device is corrupt. 设备内部报错 AB分区等等【二】 安卓玩机搞机技巧综合资源------EROFS分区格式 小米红…

踩坑Xxljob本地部署后调度一半成功一半失败原因分析及解决方案记录

缘由 入门学习和本地部署Xxljob过程中&#xff0c;发现Xxljob任务一半调度成功&#xff0c;一半调度失败&#xff0c;给我邮箱发爆了&#xff0c;为啥呢&#xff1f;查了半天资料都没解决 成功比例图&#xff1a; 实际操作时&#xff0c;发现单次手动执行一定成功&#xff0…

Python之数据库编程

目录 一、MySQL数据库的使用 数据库相关操作 二、数据库增删改查 增加 修改 删除 三、数据库标准写法 一、MySQL数据库的使用 建表 CREATE TABLE py_student( id INTEGER primary key auto_increment, name INTEGER not null, gender varchar(11) default 男 , birthday d…

CMake中define_property的使用

CMake中的define_property命令用于定义和记录自定义属性&#xff0c;其格式如下&#xff1a; define_property(<GLOBAL | DIRECTORY | TARGET | SOURCE |TEST | VARIABLE | CACHED_VARIABLE>PROPERTY <name> [INHERITED][BRIEF_DOCS <brief-doc> [docs...]]…

php+vue基于微信小程序的在线挂号预约小程序

网络的广泛应用给生活带来了十分的便利。所以把在线挂号管理与现在网络相结合&#xff0c;利用ThinkPHP5技术建设在线挂号微信小程序&#xff0c;实现在线挂号的信息化。则对于进一步提高在线挂号管理发展&#xff0c;丰富在线挂号管理经验能起到不少的促进作用。 在线挂号微信…

在飞书搞了个机器人,我让ChatGPT帮忙写算法

一、前言 环境&#xff1a; 系统&#xff1a;Windows 11 64位 Python版本&#xff1a;Python 3.9 注&#xff1a;本文不讲怎么实现&#xff0c;只讲实现的效果和一些思考。大家感兴趣再考虑去配置相关机器人。 先来问问ChatGPT两个问题&#xff1a; 1、ChatGPT是什么&#xff…