专栏首页小勇DW3Elasticsearch深分页以及排序查询问题

Elasticsearch深分页以及排序查询问题

Elasticsearch深分页以及排序查询问题

1.简介

ES为了避免深分页,不允许使用分页(from&size)查询10000条以后的数据,因此如果要查询第10000条以后的数据,要使用ES提供的 scroll(游标) 来查询

假设取的页数较大时(深分页),如请求第20页,Elasticsearch不得不取出所有分片上的第1页到第20页的所有文档,并做排序,最终再取出from后的size条结果作爲最终的返回值

假设你有16个分片,则需要在coordinate node彙总到 shards* (from+size)条记录,即需要16*(20+10)记录后做一次全局排序

所以,当索引非常非常大(千万或亿),是无法使用from + size 做深分页的,分页越深则越容易OOM,即便不OOM,也很消耗CPU和内存资源

因此ES使用index.max_result_window:10000作爲保护措施 ,即默认 from + size 不能超过10000,虽然这个参数可以动态修改,也可以在配置文件配置,但是最好不要这麽做,应该改用ES游标来取得数据

2.scroll游标原理

可以把 scroll 理解爲关系型数据库里的 cursor,因此,scroll 并不适合用来做实时搜索,而更适用于后台批处理任务,比如群发

scroll 具体分爲初始化和遍历两步

初始化时将所有符合搜索条件的搜索结果缓存起来,可以想象成快照

在遍历时,从这个快照里取数据

也就是说,在初始化后对索引插入、删除、更新数据都不会影响遍历结果

游标可以增加性能的原因,是因为如果做深分页,每次搜索都必须重新排序,非常浪费,使用scroll就是一次把要用的数据都排完了,分批取出,因此比使用from+size还好

3.具体实例

初始化

请求

注意要在URL中的search后加上scroll=1m,不能写在request body中,其中1m表示这个游标要保持开启1分钟

可以指定size大小,就是每次回传几笔数据,当回传到没有数据时,仍会返回200成功,只是hits裡的hits会是空list

在初始化时除了回传_scroll_id,也会回传前100笔(假设size=100)的数据

request body和一般搜索一样,因此可以说在初始化的过程中,除了加上scroll设置游标开启时间之外,其他的都跟一般的搜寻没有两样 (要设置查询条件,也会回传前size笔的数据)

总结:

问题

在分页处理时,我们要确定两个参数,start & size,如果一个分页查询start值很大,那么这就是一个深度分页查询。

深度分页是很有问题的,用sql举例:select * from user order by id limit 10000,10 ,表面上看起来只取10条数据,而实际上它是个大查询,因为查询过程中,数据库要确定前10010条数据,然后才能拿出最后10条。

显而易见,一方面人为深度分页是个伪需求,没有谁会一直狂翻,或者直接跳第100页看数据。另一方面,深度分页对系统的稳定性有潜在威胁。

解决办法

mysql并没有限制深度分页,而Es专门搞了一个 max_result_window 的东西 – 最大结果窗口,默认值是10000,它不仅限制了用户在一次查询中最多数据条数是1w条,并且限制了start+size 必须小于1w,也就是说,你想取第9999条,往后的2条数据是不可以的,因为 9999+2 > 10000。如此一来,一石二鸟,同时防止了一次取太多和深度分页两个问题。

好,那么问题就来了,那怎么取第1万条以后的数据?要导数据怎么办?为此,es 提供了一种数据遍历的接口 — scroll,如果对数据不要求排序,可以用scroll+scan,速度更快。当使用scroll提取数据时,es 会为这个查询做快照,然后给用户提供一个游标来顺序访问快照。 

1. 普通请求

假设我们想一次返回大量数据,下面代码中一次请求58000条数据:

       /**
        *  普通搜索
        * @param client
        */
       public static void search(Client client) {
           String index = "simple-index";
           String type = "simple-type";
           // 搜索条件
           SearchRequestBuilder searchRequestBuilder = client.prepareSearch();
           searchRequestBuilder.setIndices(index);
           searchRequestBuilder.setTypes(type);
           searchRequestBuilder.setSize(58000);
           // 执行
           SearchResponse searchResponse = searchRequestBuilder.get();
           // 搜索结果
           SearchHit[] searchHits = searchResponse.getHits().getHits();
           for (SearchHit searchHit : searchHits) {
               String source = searchHit.getSource().toString();
               logger.info("--------- searchByScroll source {}", source);
           } // for
       }

返回如下报错:

Caused by: QueryPhaseExecutionException[Result window is too large, from + size must be less than or equal to: [10000] but was [58000]. 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 parameter.]
    at org.elasticsearch.search.internal.DefaultSearchContext.preProcess(DefaultSearchContext.java:212)
    at org.elasticsearch.search.query.QueryPhase.preProcess(QueryPhase.java:103)
    at org.elasticsearch.search.SearchService.createContext(SearchService.java:676)
    at org.elasticsearch.search.SearchService.createAndPutContext(SearchService.java:620)
    at org.elasticsearch.search.SearchService.executeQueryPhase(SearchService.java:371)
    at org.elasticsearch.search.action.SearchServiceTransportAction$SearchQueryTransportHandler.messageReceived(SearchServiceTransportAction.java:368)
    at org.elasticsearch.search.action.SearchServiceTransportAction$SearchQueryTransportHandler.messageReceived(SearchServiceTransportAction.java:365)
    at org.elasticsearch.transport.TransportRequestHandler.messageReceived(TransportRequestHandler.java:33)
    at org.elasticsearch.transport.RequestHandlerRegistry.processMessageReceived(RequestHandlerRegistry.java:75)
    at org.elasticsearch.transport.TransportService$4.doRun(TransportService.java:376)
    at org.elasticsearch.common.util.concurrent.AbstractRunnable.run(AbstractRunnable.java:37)
    ... 3 more

2. 使用scroll方式:

package com.smk.es.servicce;

import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.TransportAddress;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.transport.client.PreBuiltTransportClient;

import java.net.InetAddress;
import java.util.HashMap;
import java.util.Map;

public class TestEs {


    private String clusterName ="es-smk-sit";

    private String clusterNode = "192.168.23.10";

    private String clusterPort ="9301";

    private String poolSize = "10";

    private boolean snf = true;

    private String index = "smk-label";

    private String type = "label";

    public TransportClient transportClient() {
        TransportClient transportClient = null;
        try {
            Settings esSetting = Settings.builder()
                    .put("cluster.name", clusterName) //集群名字
                    .put("client.transport.sniff", snf)//增加嗅探机制,找到ES集群
                    .put("thread_pool.search.size", Integer.parseInt(poolSize))//增加线程池个数,暂时设为5
                    .build();
            //配置信息Settings自定义
            transportClient = new PreBuiltTransportClient(esSetting);
            TransportAddress transportAddress = new TransportAddress(InetAddress.getByName(clusterNode), Integer.valueOf(clusterPort));
            transportClient.addTransportAddresses(transportAddress);
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("elasticsearch TransportClient create error!!");
        }
        System.out.println("es客户端创建成功");
        return transportClient;
    }

    public static  String scrollId = "";

    /**
     * 第一次查询的方式
     * @param client
     * @return
     */
    private Map<String,Object> my(TransportClient client){
        BoolQueryBuilder mustQuery = QueryBuilders.boolQuery();
        //设置查询条件
        mustQuery.must(QueryBuilders.matchQuery("sex","男"));
        mustQuery.must(QueryBuilders.matchQuery("city","杭州市"));
        SearchResponse rep =  client.prepareSearch()
                .setIndices(index) // 索引
                .setTypes(type)  //类型
                .setQuery(mustQuery)
                .setScroll(TimeValue.timeValueMinutes(2))  //设置游标有效期
                .setSize(100)  //每页的大小
                .execute()
                .actionGet();
        Map<String,Object> m = new HashMap<String,Object>();
        m.put("scrollId",rep.getScrollId());//获取返回的 游标值
        m.put("id",  (rep.getHits().getHits())[0].getId());
        return m;
    }


    private  Map<String,Object>  my2(String scrollId,TransportClient client){
            SearchResponse rep1 = client.prepareSearchScroll(scrollId)  //设置游标
                    .setScroll(TimeValue.timeValueMinutes(2))  //设置游标有效期
                    .execute()
                    .actionGet();
        Map<String,Object> m = new HashMap<String,Object>();
        m.put("scrollId",rep1.getScrollId());
        SearchHit[] s = rep1.getHits().getHits();
        if(s == null || s.length == 0){
            return null;
        }
        m.put("id",  (rep1.getHits().getHits())[0].getId());
        return m;
    }


    public static void main(String[] args) {
        TestEs t =  new TestEs();
        TransportClient client =  t.transportClient();
        Map<String,Object> m1 = t.my(client);
        System.out.println("first:"+m1.get("id"));
        String s = m1.get("scrollId").toString();
        System.out.println("first:"+s);


        int i = 0;
        while (true){
            i++;
            Map<String,Object> m2 = t.my2(s,client);
            // 查询不到数据了,就表示查询完了
            if(m2 == null){
                break;
            }
            System.out.println("insert  to mysql");
        }
        System.out.println("总次数:"+i);
        System.out.println("end");
    }

}
本文参与 腾讯云自媒体分享计划 ,欢迎热爱写作的你一起参与!
本文分享自作者个人站点/博客:http://www.cnblogs.com/gxyandwmm复制
如有侵权,请联系 yunjia_community@tencent.com 删除。
登录 后参与评论
0 条评论

相关文章

  • SQL之美 - 分页查询的排序问题

    编辑手记:前面我们分享过分页查询的基础知识,其目的就是控制输出结果集大小,将结果尽快的返回。主要有两种方式,一种是嵌套的查询方式,一种是通过范围控制分页的最大值...

    数据和云
  • ElasticSearch Scroll游标搜索

    在ElasticSearch 分页搜索一文中,我们了解到分布式系统中深度分页。在这里我们再具体的了解一下深度分页,可能带来的问题,以及 ElasticSearc...

    smartsi
  • 干货 | 全方位深度解读 Elasticsearch 分页查询

    第一:非常适合小型数据集或者大数据集返回 Top N(N <= 10000)结果集的业务场景。

    铭毅天下
  • SpringBoot连接Elasticsearch实战总结

    第一次使用elasticsearch,于是从网上找轮子复制粘贴。早好轮子测试完毕,上线。可是几天下来发现接口响应时间一直都偏高(默认的超时时间是500ms),所...

    小森啦啦啦
  • Elasticsearch大文件检索性能提升20倍实践(干货)

    少废话,直接开始。 1、大文件是多大? ES建立索引完成全文检索的前提是将待检索的信息导入Elaticsearch。 项目中,有时候需要将一些扫描件、PDF文档...

    铭毅天下
  • 2021年大数据ELK(十):使用VSCode操作猎聘网职位搜索案例

    本次案例,要实现一个类似于猎聘网的案例,用户通过搜索相关的职位关键字,就可以搜索到相关的工作岗位。我们已经提前准备好了一些数据,这些数据是通过爬虫爬取的数据,这...

    Lanson
  • 内存吞金兽(Elasticsearch)的那些事儿 -- 常见问题痛点及解决方案

    查询的流程:往 ES 里写的数据,实际上都写到磁盘文件里去了,查询的时候,操作系统会将磁盘文件里的数据自动缓存到 Filesystem Cache 里面去

    房上的猫
  • Go Elasticsearch 查询快速入门

    注意:查询不存在的 ID,会报elastic: Error 404 (Not Found)错误。

    Dabelv
  • ElasticSearch 深度分页总结

    我们的应用是采用NLPchina开源的elasticsearch-sql插件来进行查询分页和导出,由于ElasticSearch的max_result_wind...

    叨叨软件测试
  • ElasticSearch 分页搜索

    之前的文章ElasticSearch 空搜索与多索引多类型搜索我们知道,我们的空搜索匹配到集群中的13个文档。 但是,命中数组中只有10个文档(文章只显示了2条...

    smartsi
  • Kibana(一张图片胜过千万行日志)

    Kibana是一个开源的分析和可视化平台,设计用于和Elasticsearch一起工作。

    java架构师
  • .NET 5.0 快速开发框架 千万级数据处理 解决方案

    源码github:https://github.com/linbin524/yc.boilerplate

    郑子铭
  • 【ElasticSearch面试】10道不得不会的ElasticSearch面试题

    以下是 ElasticSearch 面试题,相信大家都会有种及眼熟又陌生的感觉、看过可能在短暂的面试后又马上忘记了。**JavaPub**在这里整理这些容易忘记...

    JavaPub
  • ES开发指南|如何快速上手ElasticSearch

    ElasticSearch不只是全文检索引擎的领头羊,现在也是各个大厂标配的大数据平台之一,被广泛用于搜索加速,用户标签、画像系统、向量搜索等领域,它不是传统的...

    浅羽技术
  • ES开发指南|如何快速上手ElasticSearch

    ElasticSearch不只是全文检索引擎的领头羊,现在也是各个大厂标配的大数据平台之一,被广泛用于搜索加速,用户标签、画像系统、向量搜索等领域,它不是传统的...

    故里
  • ElasticSearch入门之彼行我释(四)

    我是攻城师
  • .NET Core接入ElasticSearch 7.5

    最近一段时间,团队在升级ElasticSearch(以下简称ES),从ES 2.2升级到ES 7.5。也是这段时间,我从零开始,逐步的了解了ES,中间也踩了不少...

    Edison.Ma
  • Elasticsearch 6.x版本全文检索学习之Search的运行机制

      答:Search的运行机制,Search执行的时候实际分两个步骤运作的,分别是Query阶段、Fetch阶段。称为Query-Then-Fetch。

    别先生
  • 如何在elasticsearch里面使用深度分页功能

    我是攻城师

扫码关注云+社区

领取腾讯云代金券