Elsaticsearch-Scroll查询详解

一、Scroll简介

官网地址Scroll

scroll 查询 可以用来对 Elasticsearch 有效地执行大批量的文档查询,而又不用付出深度分页那种代价。

游标查询允许我们 先做查询初始化,然后再批量地拉取结果。 这有点儿像传统数据库中的 cursor 。

游标查询会取某个时间点的快照数据。 查询初始化之后索引上的任何变化会被它忽略。 它通过保存旧的数据文件来实现这个特性,结果就像保留初始化时的索引 视图 一样。

深度分页的代价根源是结果集全局排序,如果去掉全局排序的特性的话查询结果的成本就会很低。 游标查询用字段 _doc 来排序。 这个指令让 Elasticsearch 仅仅从还有结果的分片返回下一批结果。

启用游标查询可以通过在查询的时候设置参数 scroll 的值为我们期望的游标查询的过期时间。 游标查询的过期时间会在每次做查询的时候刷新,所以这个时间只需要足够处理当前批的结果就可以了,而不是处理查询结果的所有文档的所需时间。 这个过期时间的参数很重要,因为保持这个游标查询窗口需要消耗资源,所以我们期望如果不再需要维护这种资源就该早点儿释放掉。 设置这个超时能够让 Elasticsearch 在稍后空闲的时候自动释放这部分资源。

GET /old_index/_search?scroll=1m   //保持游标查询窗口一分钟。
{
    "query": { "match_all": {}},
    "sort" : ["_doc"],   //关键字 _doc 是最有效的排序顺序。
    "size":  1000
}

这个查询的返回结果包括一个字段 _scroll_id, 它是一个base64编码的长字符串 。 现在我们能传递字段 _scroll_id 到 _search/scroll 查询接口获取下一批结果:

GET /_search/scroll
{
    "scroll": "1m",   //注意再次设置游标查询过期时间为一分钟。
    "scroll_id" : "cXVlcnlUaGVuRmV0Y2g7NTsxMDk5NDpkUmpiR2FjOFNhNnlCM1ZDMWpWYnRROzEwOTk1OmRSamJHYWM4U2E2eUIzVkMxalZidFE7MTA5OTM6ZFJqYkdhYzhTYTZ5QjNWQzFqVmJ0UTsxMTE5MDpBVUtwN2lxc1FLZV8yRGVjWlI2QUVBOzEwOTk2OmRSamJHYWM4U2E2eUIzVkMxalZidFE7MDs="
}

注意游标查询每次返回一个新字段 _scroll_id。每次我们做下一次游标查询, 我们必须把前一次查询返回的字段 _scroll_id 传递进去。 当没有更多的结果返回的时候,我们就处理完所有匹配的文档了。

这个游标查询返回的下一批结果。 尽管我们指定字段 size 的值为1000,我们有可能取到超过这个值数量的文档。 当查询的时候, 字段 size 作用于单个分片,所以每个批次实际返回的文档数量最大为 size * number_of_primary_shards 。

1. scroll-scan 的高效滚动

分页检索即from-size形式,from指的是从哪里开始拿数据,size是结果集中返回的文档个数。from-size的工作原理是:如size=10&from=100,那么Elasticsearch会从每个分片里取出110条数据,然后汇集到一起再排序,取出101~110序号的文档。由此可见,from-size的效率必然不会很高,特别是分页越深,需要排序的数据越多,其效率就越低。 

这时更为有效的方法是使用Scroll-Scan。Scroll是先做一次初始化搜索把所有符合搜索条件的结果缓存起来生成一个快照,然后持续地、批量地从快照里拉取数据直到没有数据剩下。而这时对索引数据的插入、删除、更新都不会影响遍历结果,因此scroll 并不适合用来做实时搜索。Scan是搜索类型,告诉Elasticsearch不用对结果集进行排序,只要分片里还有结果可以返回,就返回一批结果。scroll- scan使用中不能跳页获取结果,必须一页接着一页获取。

为了使用scroll-scan,需要执行一个初始化搜索请求,将search_type设置成scan,并且传递一个scroll参数来告诉 Elasticsearch缓存应该持续多长时间,在缓存持续时间内初始化搜索请求后对索引的修改不会反应到快照中。每次搜索请求后都会返回一个scrollId,是一个 64 位的字符串编码,后续会使用此scrollId来获取数据。scroll时间指的是本次数据处理所需要的时间,如果超过此时间,继续使用该scrollId搜索数据则会报错。在使用scroll-scan时可以指定返回结果集大小,在 scan 的时候,size 作用在每个分片上,所以将会在每批次中得到最大为 size * 主分片数 个文档。

一般来说,你仅仅想要找到结果,不关心顺序。你可以通过组合 scroll 和 scan 来关闭任何打分或者排序,以最高效的方式返回结果。你需要做的就是将 search_type=scan 加入到查询的字符串中:

POST /twitter/tweet/_search?scroll=1m&search_type=scan
{
   "query": {
       "match" : {
           "title" : "elasticsearch"
       }
   }
}

设置 search_type 为 scan 可以关闭打分,让滚动更加高效。

扫描式的滚动请求和标准的滚动请求有四处不同:

  1. 不算分,关闭排序。结果会按照在索引中出现的顺序返回。
  2. 不支持聚合
  3. 初始 search 请求的响应不会在 hits 数组中包含任何结果。第一批结果就会按照第一个 scroll 请求返回。
  4. 参数 size 控制了每个分片上而非每个请求的结果数目,所以 size 为 10 的情况下,如果命中了 5 个分片,那么每个 scroll 请求最多会返回 50 个结果。

2. 清除 scroll API

搜索上下文当 scroll 超时就会自动移除。但是保持 scroll 存活需要代价,如在前一节讲的那样,所以 scrolls 当scroll不再被使用的时候需要被用 clear-scroll 显式地清除:

DELETE /_search/scroll
{ 
  "scroll_id" : ["c2Nhbjs2OzM0NDg1ODpzRlBLc0FXNlNyNm5JWUc1"]
}

所有搜索上下文可以通过 _all 参数而清除:

DELETE /_search/scroll/_all

scroll_id 也可以使用一个查询字符串的参数或者在请求的body中传递。多个scroll ID 可以使用逗号分隔传入:

DELETE /_search/scroll/DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==,DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAABFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAAAxZrUllkUVlCa1NqNmRMaUhiQlZkMWFBAAAAAAAAAAIWa1JZZFFZQmtTajZkTGlIYkJWZDFhQQAAAAAAAAAFFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAABBZrUllkUVlCa1NqNmRMaUhiQlZkMWFB

3. Sliced Scroll 

对于返回大量文档的滚动查询,可以将滚动分割为多个切片,可以单独使用:


POST ip:port/index/type/_search?scroll=1m
{
    "query": { "match_all": {}},
    "slice": {
        "id": 0,
        "max": 5
    }   
}

二、实例分享

1. scroll滚动查询

/**
 * 滚动查询数据
 * @param indexName
 * @param utime
 */
public List<String> scrollSearchAll(String indexName, String utime) throws IOException{

    BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
    boolQueryBuilder.must(QueryBuilders.rangeQuery("utime").lt(utime).gt("946656000"));//946656000为2000-01-01 00:00:00

    //builder
    SearchSourceBuilder builder = new SearchSourceBuilder()
            .query(boolQueryBuilder)
            .size(500);

    // 构建SearchRequest
    SearchRequest searchRequest = new SearchRequest();
    searchRequest.indices(indexName);
    searchRequest.source(builder);

    Scroll scroll = new Scroll(new TimeValue(600000));
    searchRequest.scroll(scroll);

    SearchResponse searchResponse = restHighLevelClient.search(searchRequest);

    String scrollId = searchResponse.getScrollId();
    SearchHit[] hits = searchResponse.getHits().getHits();

    List<String> resultSearchHit = new ArrayList<>();

    while (ArrayUtils.isNotEmpty(hits)) {
        
        for (SearchHit hit : hits) {
            log.info("准备删除的数据hit:{}", hit);
            resultSearchHit.add(hit.getId());
        }

        // 再次发送请求,并使用上次搜索结果的ScrollId
        SearchScrollRequest searchScrollRequest = new SearchScrollRequest(scrollId);
        searchScrollRequest.scroll(scroll);
        SearchResponse searchScrollResponse = restHighLevelClient.searchScroll(searchScrollRequest);

        scrollId = searchScrollResponse.getScrollId();
        hits = searchScrollResponse.getHits().getHits();
    }
    // 及时清除es快照,释放资源
    ClearScrollRequest clearScrollRequest = new ClearScrollRequest();
    clearScrollRequest.addScrollId(scrollId);
    restHighLevelClient.clearScroll(clearScrollRequest);

    return resultSearchHit;
}
  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值