一个场景,需要频繁获取引擎里面命中的所有数据。 还要分页。一般来说,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());
}