ES系列12、ES的scroll Api及分页实例

1.官方api

1.Scroll概念

Version:6.1javascript

英文原文地址:Scrollhtml

当一个搜索请求返回单页结果时,可使用 scroll API 检索体积大量(甚至所有)结果,这和在传统数据库中使用游标的方式很是类似。java

不要把 scroll 用于实时请求,它主要用于大数据量的场景。例如:将一个索引的内容索引到另外一个不一样配置的新索引中。node

2.Client support for scrolling and reindexing(滚动搜索和索引之间的文档重索引)

一些官方支持的客户端提供了一些辅助类,能够协助滚动搜索和索引之间的文档重索引:web

Perl数据库

​ 参阅 Search::Elasticsearch::Client::5_0::Bulk 和 Search::Elasticsearch::Client::5_0::Scrollapi

Python数组

​ 参阅 elasticsearch.helpers.*缓存

NOTE:从 scroll 请求返回的结果反映了初始搜素请求生成时的索引状态,就像时间快照同样。对文档的更改(索引、更新或者删除)只会影响之后的搜索请求。session

3.基本用法

为了使用 scroll ,初始的搜索请求应该在查询字符串中指定 scroll 参数,这个参数会告诉 Elasticsearch 将 “search context” 保存多久。例如:?scroll=1m

POST /twitter/_search?scroll=1m
{
    "size": 100,
    "query": {
        "match" : {
            "title" : "elasticsearch"
        }
    }
}

 

上面的请求返回的结果里会包含一个 _scroll_id ,咱们须要把这个值传递给 scroll API ,用来取回下一批结果。

  

POST  /_search/scroll 
{
    "scroll" : "1m", 
    "scroll_id" : "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==" 
}

 

(1) GET 或者 POST 均可以

(2) URL 不能包含 index 和 type 名称,原始请求中已经指定了

(3) scroll 参数告诉 Elasticsearch 把搜索上下文再保持一分钟

(4) scroll_id 的值就是上一个请求中返回的 _scroll_id 的值

size 参数容许咱们配置每批结果返回的最大命中数。每次调用 scroll API 都会返回下一批结果,直到再也不有能够返回的结果,即命中数组为空。

IMPORTANT:初始的搜索请求和每一个 scroll 请求都会返回一个新的 _scroll_id ,只有最近的 _scroll_id 是可用的

NOTE:若是请求指定了过滤,就只有初始搜索的响应中包含聚合结果。

NOTE:Scroll 请求对 _doc 排序作了优化。若是要遍历全部的文档,并且不考虑顺序,_doc 是最高效的选项。

GET /_search?scroll=1m
{
  "sort": [
    "_doc"
  ]
}

1.Keeping the search context alive

scroll 参数告诉了 Elasticsearch 应当保持搜索上下文多久。它的值不须要长到可以处理完全部的数据,只要足够处理前一批结果就好了。每一个 scroll 请求都会设置一个新的过时时间。

一般,为了优化索引,后台合并进程会把较小的段合并在一块儿建立出新的更大的段,此时会删除较小的段。这个过程在 scrolling 期间会继续进行,可是一个打开状态的索引上下文能够防止旧段在仍须要使用时被删除。这就解释了 Elasticsearch 为何可以不考虑对文档的后续修改,而返回初始搜索请求的结果。

TIP:使旧段保持活动状态意味着须要更多的文件句柄。请确保你已将节点配置为拥有足够的可用的文件句柄。详情参阅 File Descriptors

你可使用 nodes stats API 查看有多少搜索上下文处于开启状态

GET /_nodes/stats/indices/searchGET /_nodes/stats/indices/search

2.Clear scroll API

当超出了 scroll timeout 时,搜索上下文会被自动删除。可是,保持 scrolls 打开是有成本的,当再也不使用 scroll 时应当使用 clear-scroll API 进行显式清除。

DELETE /_search/scroll
{
    "scroll_id" : "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ=="
}

 

可使用数组传递多个 scroll ID

DELETE /_search/scroll
{
    "scroll_id" : [
      "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==",
      "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAABFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAAAxZrUllkUVlCa1NqNmRMaUhiQlZkMWFBAAAAAAAAAAIWa1JZZFFZQmtTajZkTGlIYkJWZDFhQQAAAAAAAAAFFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAABBZrUllkUVlCa1NqNmRMaUhiQlZkMWFB"
    ]
}

 

使用 _all 参数清除全部的搜索上下文

DELETE /_search/scroll/_allDELETE /_search/scroll/_all

也可使用 query string 参数传递 scroll_id ,多个值使用英文逗号分割

DELETE /_search/scroll/DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==,DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAABFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAAAxZrUllkUVlCa1NqNmRMaUhiQlZkMWFBAAAAAAAAAAIWa1JZZFFZQmtTajZkTGlIYkJWZDFhQQAAAAAAAAAFFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAABBZrUllkUVlCa1NqNmRMaUhiQlZkMWFBDELETE /_search/scroll/DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==,DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAABFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAAAxZrUllkUVlCa1NqNmRMaUhiQlZkMWFBAAAAAAAAAAIWa1JZZFFZQmtTajZkTGlIYkJWZDFhQQAAAAAAAAAFFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAABBZrUllkUVlCa1NqNmRMaUhiQlZkMWFB

3.Sliced Scroll

若是 scroll 查询返回的文档数量过多,能够把它们拆分红多个切片以便独立使用

GET /twitter/_search?scroll=1m
{
    "slice": {
        "id": 0, 
        "max": 2 
    },
    "query": {
        "match" : {
            "title" : "elasticsearch"
        }
    }
}
GET /twitter/_search?scroll=1m
{
    "slice": {
        "id": 1,
        "max": 2
    },
    "query": {
        "match" : {
            "title" : "elasticsearch"
        }
    }
}

 

(1) 切片的 id

(2) 最大切片数量

上面的栗子,第一个请求返回的是第一个切片(id : 0)的文档,第二个请求返回的是第二个切片的文档。由于咱们设置了最大切片数量是 2 ,因此两个请求的结果等价于一次不切片的 scroll 查询结果。默认状况下,先在第一个分片(shard)上作切分,而后使用如下公式:slice(doc) = floorMod(hashCode(doc._uid), max) 在每一个 shard 上执行切分。例如,若是 shard 的数量是 2 ,而且用户请求 4 slices ,那么 id 为 0 和 2 的 slice 会被分配给第一个 shard ,id 为 1 和 3 的 slice 会被分配给第二个 shard 。

每一个 scroll 是独立的,能够像任何 scroll 请求同样进行并行处理。

NOTE:若是 slices 的数量比 shards 的数量大,第一次调用时,slice filter 的速度会很是慢。它的复杂度时 O(n) ,内存开销等于每一个 slice N 位,其中 N 时 shard 中的文档总数。通过几回调用后,筛选器会被缓存,后续的调用会更快。可是仍须要限制并行执行的 sliced 查询的数量,以避免内存激增。

为了彻底避免此成本,可使用另外一个字段的 doc_values 来进行切片,但用户必须确保该字段具备如下属性:

  • 该字段是数字类型
  • 该字段启用了 doc_values
  • 每一个文档应当包含单个值。若是一份文档有指定字段的多个值,则使用第一个值
  • 每一个文档的值在建立文档时设置了以后再也不更新,这能够确保每一个切片得到肯定的结果
  • 字段的基数应当很高,这能够确保每一个切片得到的文档数量大体相同
GET /twitter/_search?scroll=1m
{
    "slice": {
        "field": "date",
        "id": 0,
        "max": 10
    },
    "query": {
        "match" : {
            "title" : "elasticsearch"
        }
    }
}

 

NOTE:默认状况下,每一个 scroll 容许的最大切片数量时 1024。你能够更新索引设置中的 index.max_slices_per_scroll 来绕过此限制。

3.实现分页案例

1.实现分页,每页20条数据,第一次请求返回第一页数据

POST /book1/_search?scroll=10m

{
    "size":20
}
{
    "_scroll_id": "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAL6FlI4dDZSMjBYUXJpdEpCXzVRVlFzdmcAAAAAAAAC_hZSOHQ2UjIwWFFyaXRKQl81UVZRc3ZnAAAAAAAAAvsWUjh0NlIyMFhRcml0SkJfNVFWUXN2ZwAAAAAAAAL8FlI4dDZSMjBYUXJpdEpCXzVRVlFzdmcAAAAAAAAC_RZSOHQ2UjIwWFFyaXRKQl81UVZRc3Zn",
    "took": 1,
    "timed_out": false,
    "_shards": {
        "total": 5,
        "successful": 5,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": 41,
        "max_score": 1,
        "hits": [
            {
                "_index": "book1",
                "_type": "english",
                "_id": "_update",
                "_score": 1,
                "_source": {
                    "scripted_upsert": true,
                    "script": {
                        "id": "my_web_session_summariser",
                        "params": {
                            "pageViewEvent": {
                                "url": "foo.com/bar",
                                "response": 404,
                                "time": "2014-01-01 12:32"
                            }
                        }
                    },
                    "upsert": {}
                }
            },
            {
                "_index": "book1",
                "_type": "english",
                "_id": "79",
                "_score": 1,
                "_source": {
                    "name": "new_name"
                }
            }
。。。。
}

2.使用scroll_id请求后面的几页的数据,每次返回一页

POST /_search/scroll
{
    "scroll":"10m",
    "scroll_id" : "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAL6FlI4dDZSMjBYUXJpdEpCXzVRVlFzdmcAAAAAAAAC_hZSOHQ2UjIwWFFyaXRKQl81UVZRc3ZnAAAAAAAAAvsWUjh0NlIyMFhRcml0SkJfNVFWUXN2ZwAAAAAAAAL8FlI4dDZSMjBYUXJpdEpCXzVRVlFzdmcAAAAAAAAC_RZSOHQ2UjIwWFFyaXRKQl81UVZRc3Zn" 
}

最后一页只有一条:

{
    "_scroll_id": "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAL6FlI4dDZSMjBYUXJpdEpCXzVRVlFzdmcAAAAAAAAC_hZSOHQ2UjIwWFFyaXRKQl81UVZRc3ZnAAAAAAAAAvsWUjh0NlIyMFhRcml0SkJfNVFWUXN2ZwAAAAAAAAL8FlI4dDZSMjBYUXJpdEpCXzVRVlFzdmcAAAAAAAAC_RZSOHQ2UjIwWFFyaXRKQl81UVZRc3Zn",
    "took": 1,
    "timed_out": false,
    "_shards": {
        "total": 5,
        "successful": 5,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": 41,
        "max_score": 1,
        "hits": [
            {
                "_index": "book1",
                "_type": "english",
                "_id": "oAmI_mQBbhSmAk-TGrMQ",
                "_score": 1,
                "_source": {
                    "name": "否sdfdsfds",
                    "age": 13,
                    "class": "dsfdsf",
                    "addr": "中国"
                }
            }
        ]
    }
}

继续执行返回空:

{
    "_scroll_id": "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAL6FlI4dDZSMjBYUXJpdEpCXzVRVlFzdmcAAAAAAAAC_hZSOHQ2UjIwWFFyaXRKQl81UVZRc3ZnAAAAAAAAAvsWUjh0NlIyMFhRcml0SkJfNVFWUXN2ZwAAAAAAAAL8FlI4dDZSMjBYUXJpdEpCXzVRVlFzdmcAAAAAAAAC_RZSOHQ2UjIwWFFyaXRKQl81UVZRc3Zn",
    "took": 1,
    "timed_out": false,
    "_shards": {
        "total": 5,
        "successful": 5,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": 41,
        "max_score": 1,
        "hits": []
    }
}

 

3.异常:earchContextMissingException

SearchContextMissingException[No search context found for id [721283]];缘由:scroll设置的时间太短了。

相关文章
相关标签/搜索