首页 教程 服务器/数据库 elasticsearch 查询超10000的解决方案

elasticsearch 查询超10000的解决方案

前言

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

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

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

elasticsearch 查询超10000的解决方案

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

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

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

查看索引的 settings
elasticsearch 查询超10000的解决方案
重新查数据:

elasticsearch 查询超10000的解决方案

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 }

  • 上面操作中通过游标的结果返回
    elasticsearch 查询超10000的解决方案
  • 之后将_scroll_id 复制到窗口,就可以不端通过这个_scroll_id 进行之前设置的页数不断翻页
    以此类推,后面每次滚屏都把前一个的scroll_id复制过来。注意到,后续请求时没有了index信息,size信息等,这些都在初始请求中,只需要使用scroll_id和scroll两个参数即可。
    elasticsearch 查询超10000的解决方案
    注意,此时游标移动了,所以我们可以通过游标的方式不断后移,直到移动到我们想要的 from+size 范围内。再次点击
    elasticsearch 查询超10000的解决方案

java实现

@TestpublicvoidtestScroll(){RestHighLevelClient restHighLevelClient ;BoolQueryBuilder boolQueryBuilder =QueryBuilders.boolQuery(); boolQueryBuilder.mustNot(QueryBuilders.existsQuery("seq"));try{//滚动查询的Scroll,设置请求滚动时间窗口时间Scroll scroll =newScroll(TimeValue.timeValueMillis(180000));SearchSourceBuilder sourceBuilder =newSearchSourceBuilder();//加入query语句 sourceBuilder.query(boolQueryBuilder);//每次滚动的长度 sourceBuilder.size(SIZE);//加入排序字段 sourceBuilder.sort("id",SortOrder.DESC);//构建searchRequest//加入scroll和构造器SearchRequest searchRequest =newSearchRequest().indices("wkl_test").source(sourceBuilder).scroll(scroll);//存储scroll的listList<String> scrollIdList =newArrayList<>();//执行首次检索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 =newBulkRequest();JSONArray esArray =newJSONArray();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(newUpdateRequest(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 =newSearchScrollRequest(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 =newClearScrollRequest(); clearScrollRequest.setScrollIds(scrollIdList); restHighLevelClient.clearScroll(clearScrollRequest,RequestOptions.DEFAULT);}}catch(Exception e){thrownewRuntimeException(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 值数组。
elasticsearch 查询超10000的解决方案

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

elasticsearch 查询超10000的解决方案

Java 实现

@TestpublicvoidtestSearchAfter()throwsIOException{RestHighLevelClient restHighLevelClient = es7UtilApi.getRestHighLevelClient();MatchAllQueryBuilder matchAllQueryBuilder =QueryBuilders.matchAllQuery();SearchSourceBuilder searchSourceBuilder =newSearchSourceBuilder(); searchSourceBuilder.query(matchAllQueryBuilder); searchSourceBuilder.from(0); searchSourceBuilder.size(200); searchSourceBuilder.sort("seq",SortOrder.ASC); searchSourceBuilder.trackTotalHits(true);SearchRequest searchRequest =newSearchRequest().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 =newArrayList<>();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

elasticsearch 查询超10000的解决方案

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

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

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

elasticsearch 查询超10000的解决方案

3:删除PIT

DELETE _pit {"id":"t_yxAwEId2tsX3Rlc3QWU0hzbEJkYWNTVEd0ZGRoN0xsQVVNdwAWUGQtaXJpT0xTa2VUN0RGLXZfTlBvZwAAAAAACHG1fxY1UWNKX1RHOFMybXBaV20zbWx3enp3ARZTSHNsQmRhY1NUR3RkZGg3TGxBVU13AAA="}

elasticsearch 查询超10000的解决方案

总结

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

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

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

评论(0)条

提示:请勿发布广告垃圾评论,否则封号处理!!

    猜你喜欢
    【MySQL】用户管理

    【MySQL】用户管理

     服务器/数据库  2个月前  2.18k

    我们推荐使用普通用户对数据的访问。而root作为管理员可以对普通用户对应的权限进行设置和管理。如给张三和李四这样的普通用户权限设定后。就只能操作给你权限的库了。

    Cursor Rules 让开发效率变成10倍速

    Cursor Rules 让开发效率变成10倍速

     服务器/数据库  2个月前  1.23k

    在AI与编程的交汇点上,awesome-cursorrules项目犹如一座灯塔,指引着开发者们驶向更高效、更智能的编程未来。无论你是经验丰富的老手,还是刚入行的新人,这个项目都能为你的编程之旅增添一抹亮色。这些规则文件就像是你私人定制的AI助手,能够根据你的项目需求和个人偏好,精确地调教AI的行为。突然间,你会发现AI不仅能理解Next.js的最佳实践,还能自动应用TypeScript的类型检查,甚至主动提供Tailwind CSS的类名建议。探索新的应用场景,推动AI辅助编程的边界。

    探索Django 5: 从零开始,打造你的第一个Web应用

    探索Django 5: 从零开始,打造你的第一个Web应用

     服务器/数据库  2个月前  1.16k

    Django 是一个开放源代码的 Web 应用程序框架,由 Python 写成。它遵循 MVT(Model-View-Template)的设计模式,旨在帮助开发者高效地构建复杂且功能丰富的 Web 应用程序。随着每个版本的升级,Django 不断演变,提供更多功能和改进,让开发变得更加便捷。《Django 5 Web应用开发实战》集Django架站基础、项目实践、开发经验于一体,是一本从零基础到精通Django Web企业级开发技术的实战指南《Django 5 Web应用开发实战》内容以。

    MySQL 的mysql_secure_installation安全脚本执行过程介绍

    MySQL 的mysql_secure_installation安全脚本执行过程介绍

     服务器/数据库  2个月前  1.09k

    mysql_secure_installation 是 MySQL 提供的一个安全脚本,用于提高数据库服务器的安全性

    【MySQL基础篇】概述及SQL指令:DDL及DML

    【MySQL基础篇】概述及SQL指令:DDL及DML

     服务器/数据库  2个月前  489

    数据库是长期存储在计算机内的、有组织的、可共享的、统一管理的大量数据的集合。数据库不仅仅是数据的简单堆积,而是遵循一定的规则和模式进行组织和管理的。数据库中的数据可以包括文本、数字、图像、音频等各种类型的信息。

    Redis中的哨兵(Sentinel)

    Redis中的哨兵(Sentinel)

     服务器/数据库  2个月前  315

    ​ 上篇文章我们讲述了Redis中的主从复制(Redis分布式系统中的主从复制-CSDN博客),本篇文章针对主从复制中的问题引出Redis中的哨兵,希望本篇文章会对你有所帮助。