Elasticsearch java API之基于scroll 分页搜索

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游标来取得数据

scroll游标原理

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

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

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

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

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

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

java 实现scroll 分页搜索

public Page scrollPage(Page page, String key, String state, String code, Date begin, Date end) {
        int pageSize = page.getPageSize();
        int currentPageNo = page.getCurrentPageNo();
        TransportClient client = this.elasticsearchTemplate.getTransportClient();
        // 组织查询条件
        BoolQueryBuilder query = setBoolQueryBuilder(state, begin, end, key, code);
        // Scroll查询请求
        // 首次搜索 包含数据
        SearchResponse searchResponse = client.prepareSearch("my_index")
            	.setTypes("my_type")
                // 执行检索的类别
                .setSearchType(SearchType.DEFAULT)
            	// 指定超时时间
                .setScroll(new TimeValue(5000))
            	// 排序
                .addSort("createDate", SortOrder.DESC)
            	// 查询条件
                .setQuery(query)
            	// 大小
                .setSize(page.getPageSize())
                .execute()
                .actionGet();
        // 获取查询总数量
        long totalCount = searchResponse.getHits().getTotalHits();
        // 设置分页数据总数
        page.setTotalCount(totalCount);
        List<EntityEsDto> result = new ArrayList<>(pageSize);
        // 查询总数量为0,直接返回
        if (totalCount == 0) {
            page.setResult(result);
            return page;
        }
        // 计算总页数
        long pageCount = totalCount % (long) pageSize == 0L ? totalCount / (long) pageSize : totalCount / (long) pageSize + 1L;
        // 重新设置当前页数
        if (pageCount == 1 || pageCount <= currentPageNo) {
            currentPageNo = (int) pageCount;
        }
        // 获取currentPageNo的数据,从Scroll快照拿数据,首页数据已经拿到,
        for (int i = 2; i <= currentPageNo; i++) {
            // 从第二页开始,使用上次搜索结果的ScrollId,从Scroll快照拿数据
            searchResponse = client
                    .prepareSearchScroll(searchResponse.getScrollId())
                    .setScroll(new TimeValue(5000))
                    .execute()
                    .actionGet();
        }
        // 获取分页结果
        SearchHits hits = searchResponse.getHits();
        EntityEsDto entityEsDto= null;
        // 遍历转换结果对象
        for (SearchHit searchHit : hits) {
            entityEsDto= JSONObject.parseObject(searchHit.getSourceAsString(), EntityEsDto.class);
            if (Objects.nonNull(entityEsDto)) {
                // 设置主键
                entityEsDto.setId(searchHit.getId());
                result.add(entityEsDto);
            }
        }
        page.setCurrentPageNo(currentPageNo);
        page.setResult(result);
        return page;
    }

    /**
     * <p>
     * <code>setBoolQueryBuilder</code>组织查询条件
     * </p>
     *
     * @param key         关键字
     * @param state       状态
     * @param code 		  标识
     * @param begin       开始时间
     * @param end         结束时间
     * @return
     */
    private BoolQueryBuilder setBoolQueryBuilder(String key, String state, String code, Date begin, Date end) {
        BoolQueryBuilder query = QueryBuilders.boolQuery();
        // 添加 关键字模糊匹配条件 不分词实现sql like模糊匹配效果,使用es wildcard 通配符搜索
        if (StringUtils.isNotBlank(key)) {
            QueryBuilder query1 = QueryBuilders.wildcardQuery("key", "*" + key + "*");
            query.must(query1);
        }
        // 添加 状态条件
        if (StringUtils.isNotBlank(state)) {
            QueryBuilder query2 = QueryBuilders.termQuery("state", state);
            query.must(query2);
        }
        // 添加 标识条件
        if (StringUtils.isNotBlank(currentUser.getCustomerId())) {
            QueryBuilder query3 = QueryBuilders.termQuery("code", code);
            query.must(query3);
        }
        // 添加 开始时间 结束时间范围条件
        if (begin != null && end != null) {
            QueryBuilder query4 = QueryBuilders.rangeQuery("createTime").from(begin.getTime()).to(end.getTime());
            query.must(query4);
        }
        return query;
    }

参考:

https://blog.csdn.net/weixin_40341116/article/details/80821655

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值