关于SpringBoot集成ES Scroll API(滚动查询)的实践
创始人
2024-04-21 20:54:22
0

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

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

背景:

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

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

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

GET //_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 searchHits = elasticsearchRestTemplate.search(nativeSearchQuery, Book.class);if (!searchHits.hasSearchHits()) {break;}for (SearchHit 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 //_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 searchScrollHits = elasticsearchRestTemplate.searchScrollStart(scrollTimeOut, query, Book.class, IndexCoordinates.of("ss.index.book"));while (searchScrollHits.hasSearchHits()) {System.out.println(total += searchScrollHits.getSearchHits().size());for (SearchHit 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 searchScrollHits = elasticsearchRestTemplate.searchScrollStart(scrollTimeOut, query, Book.class, IndexCoordinates.of("ss.index.book"));//记录新请求SearchScrollHits newHits;while (searchScrollHits.hasSearchHits()) {System.out.println(total += searchScrollHits.getSearchHits().size());for (SearchHit 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 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 searchHit : searchScrollHits.getSearchHits()) {Book book = searchHit.getContent();}// 再次查询searchScrollHits = elasticsearchRestTemplate.searchScrollContinue(scrollId, scrollTimeInMillis, Book.class, IndexCoordinates.of("book"));scrollId = searchScrollHits.getScrollId();}List scrollIds = new ArrayList<>();scrollIds.add(scrollId);// 清除 scrollelasticsearchRestTemplate.searchScrollClear(scrollIds);
}

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

相关内容

热门资讯

安卓双系统添加应用,轻松实现多... 你有没有想过,你的安卓手机里可以同时运行两个系统呢?听起来是不是很酷?想象一边是熟悉的安卓系统,一边...
pipo安卓进系统慢,探究pi... 最近是不是发现你的Pipo安卓系统更新或者运行起来特别慢?别急,今天就来给你好好分析分析这个问题,让...
怎样使用安卓手机系统,安卓手机... 你有没有发现,安卓手机已经成为我们生活中不可或缺的一部分呢?从早晨闹钟响起,到晚上睡前刷剧,安卓手机...
双系统安卓安装caj,轻松实现... 你有没有想过,你的安卓手机里装上双系统,是不是就能同时享受安卓和Windows系统的乐趣呢?没错,这...
安卓使用ios系统教程,安卓用... 你是不是也和我一样,对安卓手机上的iOS系统充满了好奇?想要体验一下苹果的优雅和流畅?别急,今天我就...
安卓系统gps快速定位,畅享便... 你有没有遇到过这样的情况:手机里装了各种地图导航软件,但每次出门前都要等上好几分钟才能定位成功,急得...
安卓手机系统更新原理,原理与流... 你有没有发现,你的安卓手机最近是不是总在提醒你更新系统呢?别急,别急,让我来给你揭秘一下安卓手机系统...
安卓系统通知管理,全面解析与优... 你有没有发现,手机里的通知就像是一群调皮的小精灵,时不时地跳出来和你互动?没错,说的就是安卓系统的通...
安卓系统手机哪买,揭秘哪里购买... 你有没有想过,拥有一部安卓系统手机是多么酷的事情呢?想象你可以自由安装各种应用,不受限制地探索各种功...
安卓系统 ipv4,基于安卓系... 你知道吗?在智能手机的世界里,有一个系统可是无人不知、无人不晓,那就是安卓系统。而在这个庞大的安卓家...
目前安卓是什么系统,探索安卓系... 亲爱的读者,你是否曾好奇过,如今安卓系统究竟是什么模样?在这个科技飞速发展的时代,操作系统如同人体的...
安卓6.0系统比5.0,从5.... 你有没有发现,自从手机更新了安卓6.0系统,感觉整个人都清爽了不少呢?没错,今天咱们就来聊聊这个话题...
安卓2.36系统升级,功能革新... 你知道吗?最近安卓系统又来了一次大变身,那就是安卓2.36系统升级!这可不是一个小打小闹的更新,而是...
安卓系统源码怎么打开,并可能需... 你有没有想过,安卓系统的源码就像是一扇神秘的门,隐藏着无数的技术秘密?想要打开这扇门,你得掌握一些小...
安卓8.0系统体验视频,智能革... 你有没有听说安卓8.0系统最近可是火得一塌糊涂啊!作为一个紧跟科技潮流的数码达人,我当然要来给你好好...
宣传系统漫画app安卓,探索安... 亲爱的读者们,你是否曾在某个午后,百无聊赖地打开手机,想要寻找一些轻松愉悦的读物?今天,我要给你介绍...
鸿蒙替换安卓系统吗,开启智能生... 你知道吗?最近科技圈里可是炸开了锅,因为华为的新操作系统鸿蒙系统,据说要大举进军手机市场,替换掉安卓...
手机安卓系统深度清理,解锁手机... 手机里的东西是不是越来越多,感觉就像一个装满了杂物的储物柜?别急,今天就来教你一招——手机安卓系统深...
安卓上的windows系统,融... 你有没有想过,在安卓手机上也能体验到Windows系统的魅力呢?没错,这就是今天我要跟你分享的神奇故...
安卓系统焦点变化事件,Andr... 你知道吗?在安卓系统的世界里,最近发生了一件超级有趣的事情——焦点变化事件。这可不是什么小打小闹,它...