一:简介   

  Elasticsearch滚动查询也叫游标查询

   适合那种需要一次性或分批拉出大量数据做离线处理、迁移等。可以提升点效率。

 

二:实践中我使用到滚动的场景
  需求需要从几个不同的es数据源拉取、截取数据,合到一个新的业务数据源中。
  每天夜里有定时任务需要拉取某天的索引数据,根据某个字段去重后拿去做离线业务处理。
  注意:scroll不适合支持那种实时的和用户交互的前端分页工作,实时分页查询可以使用from-size方式。但同时from-size也不适用上述离线大数据量处理业务场景。

 

三:from-size分页的缺点
   es客户端实时分页一般使用from-size。如果有100条数据,按size=10共分10页,那么当用户查询第n页的时候,实际上es是把前n页的数据全部找出来,再去除前n-1页最后得到需要的数据返回,查最后一页就相当于全扫描。其中利弊大家自行思考。所以离线大批量数据的处理业务或迁移不适合使用from-size方式查询。

GET /{index_name}/_search
{
"from":0,
"size":10
}

  java:

SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.from((page.getPageNum() - 1) * page.getPageSize());
searchSourceBuilder.size(page.getPageSize());

 

四:scrool

   我们可以给初始化查询传递参数scroll=5m ,es会返回一个_scroll_id,这是一个base64编码的长字符串,用于下次查询时传入。5m表示_scroll_id缓存5分钟,之后自动过期,可以根据需要配置。size可以指定每次滚动拉取多少数据。不过如果你做了分片,查询结果可能超过指定的 size 大小。
  第一次查询

GET /sms/_search?scroll=5m
{
  "size": 20,
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "userId": "9d9984eca213bf"
          }
        }
      ]
    }
  }
}

  之后我们把上一次得到的_scroll_id拿到按以下查询即可得到下一轮的数据。

GET /_search/scroll/
{
  "scroll":"1m",
  "scroll_id":"DnF1ZXJ5VGhlbkZldGNoBgAAAAAATJH1FlFTYzlSZ0VNVGdlM2o0T0dTX2tVUncAAAAAAE0-zBZQUVp6Sy04X1J1NjJCaVZfQUhHWjFnAAAAAABMkfYWUVNjOVJnRU1UZ2UzajRPR1Nfa1VSdwAAAAAATXVxFk83UWRhNGg3UmxTQnpXTEUzd0dreXcAAAAAAEyR9xZRU2M5UmdFTVRnZTNqNE9HU19rVVJ3AAAAAABNPs0WUFFaekstOF9SdTYyQmlWX0FIR1oxZw=="
}

 

  java逻辑:

  • 我们可以使用一个循环去查询,第一次查询的时候按需要的查询条件处理,加上参数scroll即可,
  • 之后的查询均使用GET /_search/scroll/ 传递_scroll_id查询,如果返回数据为空则终止循环。

 

  简约代码:

    public <T> List<T> scroll(JestClient jestClient, EsQuery<T> query) {
        List<T> all = new ArrayList<>();
        try {
            String index = query.getIndex();
            if (StringUtils.isBlank(index)) {
                throw new RuntimeException("索引不能为空");
            }
            SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
            BoolQueryBuilder bool = QueryBuilders.boolQuery();
            Map<String, Object> params = query.getParams();
            for (Map.Entry<String, Object> entry : params.entrySet()) {
                String name = entry.getKey();
                Object value = entry.getValue();
                if (value instanceof List || value instanceof Set) {
                    bool.filter(QueryBuilders.termsQuery(name, (Collection<?>) value));
                } else {
                    bool.filter(QueryBuilders.termQuery(entry.getKey(), entry.getValue()));
                }
            }
            searchSourceBuilder.query(bool);
            //查询
            Search search = new Search.Builder(searchSourceBuilder.toString())
                    .addIndex(index)
                    .addType("_doc")
                    .setParameter(Parameters.SIZE, 5000)
                    .setParameter(Parameters.SCROLL, "5m")
                    .build();
            JestResult result = jestClient.execute(search);

            List<T> sourceAsObjectList = result.getSourceAsObjectList(query.getClazz(), true);
            all.addAll(sourceAsObjectList);
            String scrollId = result.getJsonObject().get("_scroll_id").getAsString();
            while (sourceAsObjectList.size() > 0) {
                SearchScroll scroll = new SearchScroll.Builder(scrollId, "1m").build();
                result = jestClient.execute(scroll);
                scrollId = result.getJsonObject().get("_scroll_id").getAsString();
                sourceAsObjectList = result.getSourceAsObjectList(query.getClazz(), true);
                all.addAll(sourceAsObjectList);
            }
            return all;
        } catch (Exception e) {
            log.error("查询es异常", e);
            return all;
        }
    }

 

 posted on 2023-03-25 17:32  曹军  阅读(954)  评论(0编辑  收藏  举报