Es采用scroll全量查询数据的一个坑

一个场景,需要频繁获取引擎里面命中的所有数据。 还要分页。一般来说,es分页有两种

  • A.from和size决定的浅分页
  • B.scroll实现的深分页

A的话,语法上类似于mysql的offset分页,但是实际上是每次查询到所有数据,然后在内存里面给你手动截断,100页数据就是妥妥的100次全量捞。B的话,语法上略微蛋疼,但是原理上才是关系型数据库的分页。


scroll语法

首先

curl -XPOST 'http://xxxx/your_index/your_type/_search?search_type=scan&scroll=600000&size=100' -d  
'  
    {
        "query": { "match_all": {}}
    }
'

# 返回结果

{
   "took": 1,
   "error_code": 0,
   "error_msg": "success",
   "scroll_id": "c2NhbjswOzE7dG90YWxfaGl0czoxOw==",
   "time_out": false,
   "tr": null,
   "hit_cache": false,
   "_shards": {
      "total": 0,
      "successful": 0,
      "failed": 0
   },
   "hits": {
      "total": 1,
      "hits": []
   }
}

这里有一个scroll_id,接下来,用这个scroll_id才能实现真正的scroll查询。代码里面大概有一个do while循环,发出如下请求

curl -XPOST 'http://xxx/your_index/your_type/_search?scroll=600000' -d  
'    {"scroll_id":"c2NhbjswOzE7dG90YWxfaGl0czo5OTE7"}  
'  

顺便说一下,scroll是不支持排序的,所以推荐search_type用scan。速度很快。

这里记scroll的问题

  • scroll优势在不排序时更加明显
  • scroll的size是不准的,比如es有三个分片,那么数据量足够大的话,会返回size*3条数据,相当于分页的pageSize是没用的
  • scroll实现游标,就是因为保存了上次查询的索引段位置,也就是scroll_id,存活时间是自己设置的scroll=xxx,xxx时间。如果有太多存活的scroll,是要付出代价的。

上面几点也只是使用时注意的问题罢了,还不算是坑。我说的坑是在java里面,我用了searchbox来封装客户端,scroll的请求返回结果因为可以没有排序字段,所以不符合普通搜索结果的格式,io.searchbox.core.SearchResult使用getHints的时候,会报json解析错误。所以要自己单独撸一个解析方法。

    public static <E> List<E> fuckHints(Class<E> a, SearchResult searchResult) {
        if (searchResult == null || searchResult.getJsonObject().get("hits") == null) {
            return null;
        }
        List<E> result = new ArrayList<>();
        JsonArray jsonElements = searchResult.getJsonObject().get("hits").getAsJsonObject().get("hits").getAsJsonArray();
        for (JsonElement jsonObject : jsonElements) {
            if (jsonObject == null ||
                    jsonObject.getAsJsonObject().get("_source") == null) {
                continue;
            }
            JsonElement source=jsonObject.getAsJsonObject().get("_source");
            E one = JSON.toJavaObject(JSON.parseObject(source.toString()), a);
            result.add(one);
        }
        return result;
    }

还有,就是searchbox原声的request对象也不支持

curl -XPOST 'http://xx/your_index/your_type/_search?scroll=600000' -d  
'    {"scroll_id":"c2NhbjswOzE7dG90YWxfaGl0czo5OTE7"}  
'  

这种查询方式,他们的scrollRequest,写死了,是用的url/_scroll?这种语法,公司代理层不支持这种,坑。所以也要自己拼接字符串来构造请求,简直心酸

  /*** 根据_scroll_id获取所有匹配数据 ***/
        do {
            Map<String,String> scrollQuery=new HashMap<>();
            scrollQuery.put(Parameters.SCROLL_ID,scrollId);
            Search scroll = new Search.Builder(JSON.toJSONString(scrollQuery))
                    .addIndex(EsConstant.INDEX)
                    .addType(EsConstant.TYPE)
                    //sb searchbox,批量塞参数方法被废弃了,只能一个一个来
                    .setParameter(Parameters.SCROLL, SCROLL)
                    .setParameter(Parameters.SIZE, MAX_SCROLL_SIZE)
                    .build();
            LOGGER.info("scroll Request:{}", scroll.toString());
            scrollResult = crmJestClient.executeSearch(scroll);
            LOGGER.info("scroll response:{}", scrollResult.getJsonObject().toString());

            scrollId=scrollResult.getJsonObject().get(Parameters.SCROLL_ID).getAsString();
            scrollTeamES=EsUtils.fuckHints(TeamES.class,scrollResult);
            if (CollectionUtils.isEmpty(scrollTeamES)) {
                break;
            }
            teamESList.addAll(scrollTeamES);
            Integer total = scrollResult.getTotal();
            //终止条件先写在这里
            if (total == null
                    || total < MAX_SCROLL_SIZE
                    || scrollTeamES.size() < MAX_SCROLL_SIZE) {
                break;
            }
        } while (scrollResult != null
                && scrollResult.isSucceeded());

        return new TeamResult(teamESList, teamESList.size());
    }

刘摸鱼

退堂鼓表演艺术家

杭州