在使用ES的过程中,无法避免分页查询的使用场景。 ES中提供了三种分页方案。这三种分页方案没有绝对的好坏之分。在使用的时候,我们需要根据自己的使用场景做出合理的选择。
这是ES提供的最常见的寻呼功能。通过在搜索 API 中指定 from
和 size
来实现分页效果。比如现在我们对订单数据进行分页:
GET /order_v3/_search
{
“查询”:{
“match_all”:{}
},
“来自”:0,
“大小”: 10
}
其中from
表示起始位置,size
表示页面大小。这种寻呼方式在寻呼深度浅的时候没有问题,但是这种方式不适合深度寻呼。例如上例中,如果将的值从
改为1000000
,ES将无法处理,并抛出以下错误内容。
结果窗口太大,from + size 必须小于或等于:[10000] 但为 [1000010]。请参阅滚动 api,了解请求大数据集的更有效方法。可以通过更改 [index.max_result_window] 索引级别设置来设置此限制。
从错误内容可以看出,不支持深度分页ES。虽然可以通过设置 index.max_result_window
修改来增加分页深度,但实际上并不推荐这样做。
那么为什么这种方法不适合深度分页呢?例如,这里的order_v3
索引数量为3,副本分片
数量为0。如果当前from
为10000,尺寸
为 10,则该查询将执行如下:
客户端请求被发送到某个节点,暂且称之为A
节点。
A
节点将请求转发到各个分片(shard),然后每个分片根据需要获取10010(from+size)条记录,最后将这些记录发送到A
节点。
A
节点再次对30030(10010*3)条记录进行排序,得到10条符合条件的记录返回给客户端。
从上面可以发现,分页深度越深,需要从每个分片中取出的数据就越多,最终合并时节点需要的内存也会越来越多。所以from+size
的方法不适合深度分页,只适合页数不深的时候使用。
为了应对深度分页,ES推荐使用Search After
来实现数据的深度分页。它的使用也非常简单。当您第一次使用Search API
时,它带有一个排序参数,其中sort的值必须是唯一的排序值。比如前面的order_v3
索引分页查询,我们按照订单创建时间倒序对查询代码进行排序,如下:
GET /order_v3/_search
{
"查询": {
"match_all": {}
},
"排序": [
{"已创建 .key词": "desc"},
{"_id":"desc"}
],
"来自": 0,
"大小": 10
}
前面我们说sort的值必须是唯一的排序值,也就是说,如果我们简单地使用created
,即订单创建时间,作为排序值,显然创建
不是独特的价值。所以我们在这里添加_id
,形成一个唯一的值。
在上述查询结果中,每条记录都会附有以下内容:
? ,如果我们需要获取该查询的下一页数据,那么我们使用Search After
,可以这样写:
GET /order_v3/_search
{
"查询": {
"match_all": {}
},
"排序": [
{"已创建 .key词": "desc"},
{"_id":"desc"}
],
"大小": 10,
"search_after":["2022-02-27 23 :55 :39","mGA-QIQBiLbZg9L-vfT6"]
}
其中search_after
是一个数组,其值为上一次查询的最后一条记录结果中sort
中的值。
通过上面的使用示例,我们可以发现Search After
查询的本质是使用上一页中的一组排序值来检索匹配的下一页页
,但这种方式实现的局限性是使用Search After要求后续请求返回与第一个查询
相同的查询条件和排序条件。
虽然Search After
的性能和效率优于之前的from+szie
解决方案,但它最大的缺点是不能直接跳转到页面,只能分页一页一页。向下滚动。其实这种使用场景在实际业务中也很常见,比如浏览微博或者新闻APP内容。用户总是向下滚动以获取新数据,而不是跳转到页面。
前面提到的Search After
可以处理深度分页,但每次分页仍然需要经过一次查询过程。如果存在要求导出所有数据的场景,使用 Search After
需要多次查询才能导出所有数据。随着次数的增加,效率会变低。还有更好的解决办法吗?怎么处理呢?
ES提供的滚动
非常适合这种场景。 Scroll
就是将查询结果缓存一段时间,比如scroll=3m就是将查询结果缓存存3分钟,响应结果会增加
Scroll_id
返回。下一个查询使用 Scroll_id
检索数据。
比如现在需要导出所有订单数据,那么使用滚动
首先查询所有数据。示例代码如下:
GET /order_v3/_search?scroll=5m
{
“查询”:{
“match_all”:{}
},
“大小”:20
}
响应结果会增加_scroll_id
返回:
后续每个查询均按如下方式执行:
POST _搜索/滚动
{
“滚动”:“5m”,
“scroll_id”:“DnF1ZXJ5VGhlbkZldGNoAwAAAAAAAAAa1FmtWSnFHdDl3UkdDcWFHT2dqNnBj W EEAAAAAAAAAORYwR1M3NkloUlM3T0xqY05sNlVfc2tBAAAAAAAAADoWMedTNzZJaFJTN09MamNObDZVX3NrQQ=="
}
这里的scroll_id
是第一个查询返回的_scroll_id
。接下来我们一直使用上面的查询条件来获取数据,直接获取到了所有的数据。
对于滚动
,将返回第一次请求时的所有文档,并且不会查询文档的后续更改。
对于滚动
方式,适用于导出大量数据或离线数据计算等常见情况,但不适合大并发情况。
一般来说,ES深度分页的选择并不是绝对的,要看使用场景。
from+size
适合常见的查询,比如需要支持页面跳转、实时查询的场景。但是当查询深度太深时,就会出现深度分页的问题。
Search After
适合不需要分页的实时滚动查询,比如浏览微博。
滚动
与Search After
类似,但更适合数据导出场景。