title: ElasticSearch之深度应用及原理剖析author: Xonitags:
- 搜索引擎
- Elasticsearchcategories:
- 搜索引擎
- Elasticsearchabbrlink: 5a1f6e0b
ES 默认采用的分页方式是 from+ size 的形式,类似于mysql的分页limit。当请求数据量比较大时, Elasticsearch会对分页做出限制,因为此时性能消耗会很大。举个例子,一个索引 分10个 shards,然后,一个搜索请求,from=990,size=10,这时候,会带来严重的性能问题:
- CPU
- 内存
- IO
- 网络带宽
CPU、内存和IO消耗容易理解,网络带宽问题稍难理解一点。在 query 阶段,每个shard需要返回 1000条数据给 coordinating node,而 coordinating node 需要接收 10*1000 条数据,即使每条数据只有 _doc _id 和 _score,这数据量也很大了,而且,这才一个查询请求,那如果再乘以100呢?es中有个设置 index.max_result_window
,默认是10000条数据,如果分页的数据超过第1万条,就拒绝返回结果了。如果你觉得自己的集群还算可以,可以适当的放大这个参数,比如100万。我们意识到,有时这种深度分页的请求并不合理,因为我们是很少人为的看很后面的请求的,在很多的业务场景中,都直接限制分页,比如只能看前100页。不过,这种深度分页确实存在,比如有1千万粉丝的微信大V,要给所有粉丝群发消息,或者给某省粉丝群发,这时候就需要取得所有符合条件的粉丝,而最容易想到的就是利用 from + size 来实现,但这是不现实的,我们需要使用下面的解决方案。
**
- 初始化时将所有符合搜索条件的搜索结果缓存起来,可以想象成快照
- 在遍历时,从这个快照里取数据,也就是说,在初始化后对索引插入、删除、更新数据都不会影响遍历结果。
因此,scroll 并不适合用来做实时搜索,而更适用于后台批处理任务,比如群发。
1)初始化
POST /book/_search?scroll=1m&size=2
{
"query": {
"match_all": {}
}
}
初始化时需要像普通 search 一样,指明 index 和 type (当然,search 是可以不指明 index 和 type 的),然后,加上参数 scroll,表示暂存搜索结果的时间,其它就像一个普通的search请求一样。初始化返回一个 scroll_id,scroll_id 用来下次取数据用
- 遍历
GET /_search/scroll
{
"scroll": "1m",
"scroll_id" : "步骤1中查询出来的值"
}
这里的 scroll_id 即 上一次遍历取回的 _scroll_id 或者是初始化返回的 _scroll_id,同样的,需要带scroll 参数。 重复这一步骤,直到返回的数据为空,即遍历完成。注意,每次都要传参数 scroll,刷新搜索结果的缓存时间。另外,不需要指定 index 和 type。设置scroll的时候,需要使搜索结果缓存到下一次遍历完成,同时,也不能太长,毕竟空间有限。
search after方式满足实时获取下一页的文档信息,search_after 分页的方式是根据上一页的最后一条数据来确定下一页的位置,同时在分页请求的过程中,如果有索引数据的增删改,这些变更也会实时的反映到游标上,这种方式是在es-5.X之后才提供的。为了找到每一页最后一条数据,每个文档的排序字段必须有一个全局唯一值 使用 _id 就可以了。
GET /book/_search
{
"query": {
"match_all": {}
},
"size": 2,
"sort": [
{
"_id": "desc"
}
]
}
GET /book/_search
{
"query": {
"match_all": {}
},
"size": 2,
"search_after": [
3
],
"sort": [
{
"_id": "desc"
}
]
}
下一页的数据依赖上一页的最后一条的信息所以不能跳页。
三种分页方式比较