首页 - 信息 - ES查询分页解决方案

ES查询分页解决方案

2023-10-01 04:21

在使用ES的过程中,无法避免分页查询的使用场景。 ES中提供了三种分页方案。这三种分页方案没有绝对的好坏之分。在使用的时候,我们需要根据自己的使用场景做出合理的选择。

尺寸

这是ES提供的最常见的寻呼功能。通过在搜索 API 中指定 fromsize 来实现分页效果。比如现在我们对订单数据进行分页:

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,形成一个唯一的值。

在上述查询结果中,每条记录都会附有以下内容:

? ,
      "mGA-QIQBiLbZg9L -vfT6"
]
}

如果我们需要获取该查询的下一页数据,那么我们使用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返回:

? cWFHT2dqNnBjWEEAAAAAAAAAORYwR1M3NkloUlM3T0xqY05sNlVfc2tBAAAAAAAAADoWMedTNzZJaFJTN09MamNObDZVX3NrQQ=="
}

后续每个查询均按如下方式执行:

POST _搜索/滚动
{
“滚动”:“5m”,
“scroll_id”:“DnF1ZXJ5VGhlbkZldGNoAwAAAAAAAAAa1FmtWSnFHdDl3UkdDcWFHT2dqNnBj W EEAAAAAAAAAORYwR1M3NkloUlM3T0xqY05sNlVfc2tBAAAAAAAAADoWMedTNzZJaFJTN09MamNObDZVX3NrQQ=="
}

这里的scroll_id是第一个查询返回的_scroll_id。接下来我们一直使用上面的查询条件来获取数据,直接获取到了所有的数据。

对于滚动,将返回第一次请求时的所有文档,并且不会查询文档的后续更改。

对于滚动方式,适用于导出大量数据或离线数据计算等常见情况,但不适合大并发情况。

总结

一般来说,ES深度分页的选择并不是绝对的,要看使用场景。

from+size适合常见的查询,比如需要支持页面跳转、实时查询的场景。但是当查询深度太深时,就会出现深度分页的问题。

Search After适合不需要分页的实时滚动查询,比如浏览微博。

滚动Search After类似,但更适合数据导出场景。