Elasticsearch Result window is too large 问题解决和 JPA 游标分页【转】

转自assember学习自用,原文链接:https://blog.csdn.net/assember/article/details/116057368

问题描述

使用HighClient查询ES数据时, 当查询数据超过了前10000条,系统会报错:

Elasticsearch exception [type=illegal_argument_exception, reason=Result window is too large, from + size must be less than or equal to: [10000] but was [12000].

问题原因

当索引非常非常大(千万或亿),是无法按照from + size做深分页的,因为分页越深则越容易OOM,即便不OOM,也是很消耗CPU和内存资源的。官方在2.x版本中已增加限定 index.max_result_window:10000作为保护措施,即默认 from + size 不能超过1万。(from表示从第几行开始,size表示查询多少条数据,from默认为0,size默认为10。)

解决方案

方案一:通过设置index 的设置参数max_result_window的值

curl -XPUT http://es-ip:9200/_settings -d '{ "index" : { "max_result_window" : 100000000}}

设置后只对已经存在的索引生效,新建的索引需重新设置

或者在config/elasticsearch.yml文件中的最后加上

index.max_result_window: 100000000

方案二:创建索引时设置

"settings":{
	"index":{
		"max_result_window":1000000
   } 
}

方案三: 使用scoll游标查询 或者 seach after查询

scoll 是使用了缓存,可以设置本次查询的缓存1分钟,不存在丢弃问题
search after 是需要首先排序,然后按照排序后的结果,记录每次查询的最后一个结果,作为下一次的开始。

方案选择

方案一和方案而二,会占用服务器更多的内存和 CPU 资源, 一般不建议使用。

真的需要查询全部数据或者需要深分页,可以采用scoll游标或者 seach after的方式,也是官方推荐的方式

Scoll 游标解决方案

采用scoll游标的方式,滚动查询。原理就是记住上次查询结束的位置,下次从这个位置再继续查询
抽取公共方法之后使用更加方便:
ElasticsearchUtil.class

    /**
     * 获取第一页,保留 scroll id
     * @param restHighLevelClient  restHighLevelClient
     * @param clazz 返回数据类型
     * @param indexName 索引名称
     * @param size 每页大小
     * @throws IOException IOException
     */
    public static ScrollResultDTO findFirstScroll(RestHighLevelClient restHighLevelClient, Class<?> clazz, String indexName, int size) throws IOException {

        final Scroll scroll = new Scroll(TimeValue.timeValueMinutes(1L));
        SearchRequest searchRequest = new SearchRequest(indexName);
        searchRequest.scroll(scroll);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(QueryBuilders.matchAllQuery());
        searchRequest.source(searchSourceBuilder);
        searchSourceBuilder.size(size);

        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        log.info("Scroll starts: index={}, cache time={}, size={}, total counts={}",indexName, scroll.toString(), size, searchResponse.getHits().getTotalHits());
        ScrollResultDTO scrollResultDTO = new ScrollResultDTO();
        scrollResultDTO.setScrollId(searchResponse.getScrollId());
        scrollResultDTO.setResultList(ElasticsearchUtil.getSearchResult(searchResponse, clazz));
        return scrollResultDTO;
    }

    /**
     * 滚动索引缓存时间
     */
    private static final Scroll SCROLL = new Scroll(TimeValue.timeValueMinutes(1L));

    /**
     * 
     * @param restHighLevelClient restHighLevelClient
     * @param clazz 返回数据类型
     * @param scrollId 游标id
     * @throws IOException IOException
     */
    public static ScrollResultDTO continueScroll(RestHighLevelClient restHighLevelClient, Class<?> clazz, String scrollId) throws IOException {
        SearchScrollRequest scrollRequest = new SearchScrollRequest();
        scrollRequest.scroll(SCROLL);
        scrollRequest.scrollId(scrollId);
        SearchResponse searchResponse = restHighLevelClient.scroll(scrollRequest, RequestOptions.DEFAULT);
        SearchHit[] searchHits = searchResponse.getHits().getHits();

        if (searchHits == null || searchHits.length <= 0) {
            ClearScrollRequest clearScrollRequest = new ClearScrollRequest();
            clearScrollRequest.addScrollId(searchResponse.getScrollId());
            ClearScrollResponse clearScrollResponse = restHighLevelClient.clearScroll(clearScrollRequest, RequestOptions.DEFAULT);
            if (clearScrollResponse.isSucceeded()){
                return null;
            } else {
                throw new GeneralException("Scroll failed");
            }
        }
        ScrollResultDTO scrollResultDTO = new ScrollResultDTO();
        scrollResultDTO.setScrollId(searchResponse.getScrollId());
        scrollResultDTO.setResultList(ElasticsearchUtil.getSearchResult(searchResponse, clazz));
        return scrollResultDTO;
    }

scrollResultDTO.java: 封装返回对象

/**
 * ES 滚动查询 结果返回封装参数
 *
 * @author hycao
 */
@Data
public class ScrollResultDTO {

    /**
     * 滚动id
     */
    private String scrollId;

    /**
     * 返回结果
     */
    private List<?> resultList;
}

如何使用

Resource: 获取数据,处理数据

// 获取第一页数据
  ScrollResultDTO allVillage = villageRepository.findFirstScroll();
        while (allVillage != null) {
            // 获取游标分页数据
            List<Organization> villageList = allVillage.getResultList().stream().parallel()
                  .map(this::villageToOrganization).collect(Collectors.toList());
            // 处理数据
            organizationRepository.saveAllNotReplace(villageList);
            // 获取下一页数据
            allVillage = villageRepository.continueScroll(allVillage.getScrollId());
        }

Service: 直接调用工具类,传入,实体类,索引名称,分页大小即可

    @Override
    public ScrollResultDTO findFirstScroll() throws IOException {
        return ElasticsearchUtil.findFirstScroll(restHighLevelClient, Village.class, IndexConstants.ES_INDEX_VILLAGE, 500);
    }

    @Override
    public ScrollResultDTO continueScroll(String scrollId) throws IOException {
       return ElasticsearchUtil.continueScroll(restHighLevelClient, Village.class, scrollId);
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值