ElasticSearch第5天 es实现分页查询的几种方式

今日目标

es实现分页查询,在ES中有三种方式可以实现分页:from+size、scroll、search_after

1.from+size 分页

public function list(Request $request)
    {
        $params = [
            'size' => $request->limit,
            'from' => ($request->page - 1) * $request->limit,
            'index' => $this->index,
            'type' => $this->type,
        ];

        $response = app('es')->search($params);

        return $response['hits']['hits'];
    }
  • 在使用过程中,有一些典型的使用场景,比如分页、遍历等。在使用关系型数据库中,我们被告知要注意甚至被明确禁止使用深度分页,同理,在 Elasticsearch 中,也应该尽量避免使用深度分页。es为了性能,限制了我们分页的深度,es目前支持的最大的 max_result_window = 10000;from+size二者之和不能超过1w,也就是说我们不能分页到1w条数据以上。
  • from+size分页原理很简单,比如需要查询10条数据,es则需要执行from+size条数据然后根据偏移量截断前N条处理后返回。随着偏移量的增大这个时间会呈几何式增长。

如何解决from+size 分页带来的性能问题

  • 1.在业务逻辑上禁止深度分页,比如不允许查询100页以后的数据
  • 2.更换分页方式,采用游标 scroll的方式

2.游标 scroll 分页

  • Scroll往往是应用于后台批处理任务中,不能用于实时搜索,因为这个scroll相当于维护了一份当前索引段的快照信息,这个快照信息是你执行这个scroll查询时的快照。在这个查询后的任何新索引进来的数据,都不会在这个快照中查询到。但是它相对于from和size,不是查询所有数据然后剔除不要的部分,而是记录一个读取的位置,保证下一次快速继续读取。查询时会自动返回一个_scroll_id,通过这个id可以继续查询
public function list(Request $request)
    {
        if (!isset($request->scroll_id)) {
            $params = [
                'scroll' => '30s',  //快照存活的时间  1m ->一分钟
                'size' => $request->limit,
                'index' => $this->index,
                'type' => $this->type,
            ];
            $response = app('es')->search($params);
        } else {
            $response = app('es')->scroll([
                'scroll_id' => $request->scroll_id,  //...using our previously obtained _scroll_id
                'scroll' => '30s',           // and the same timeout window
                ]
            );
        }

        return [
            'scroll_id' => $response['_scroll_id'],
            'data' => $response['hits']['hits'],
        ];
    }

游标 scroll 带来的问题

这种分页方式虽然查询变快了,但滚动上下文代价很高,每一个 scroll_id 不仅会占用大量的资源(特别是排序的请求),而且是生成的历史快照,对于数据的变更不会反映到快照上,那么在实时情况下如果处理深度分页的问题呢?es 给出了 search_after 的方式,这是在 >= 5.0 版本才提供的功能。

3.search_after分页

searchAfter的方式通过维护一个实时游标来避免scroll的缺点,它可以用于实时请求和高并发场景。
search_after的理念是,=在不同分片上(假设有5个分片),先按照指定顺序排好,根据我们传的search_after值 ,然后仅取这个值之后的size个文档。这 5*size 个文档拿到Es内存中排序后,返回前size个文档即可。避免了浅分页导致的内存爆炸情况,经实际使用性能良好,ES空闲状态下查询耗时稳定在50ms以内,平均10~20ms。

    public function list(Request $request)
    {
        if (!isset($request->search_after)) {
            $params = [
                'size' => $request->limit,
                'index' => $this->index,
                'type' => $this->type,
                'body' => [
                    'sort' => [
                        [
                            '_id' => 'desc',
                        ],
                    ],
                ],
            ];
        } else {
            $params = [
                'size' => $request->limit,
                'index' => $this->index,
                'type' => $this->type,
                'body' => [
                    'sort' => [
                        [
                            '_id' => 'desc',
                        ],
                    ],
                    'search_after' => [$request->search_after],
                ],
            ];
        }
        $response = app('es')->search($params);

        return [
            //项目中需优化数组溢出,此处仅为简单演示
            'search_after' => $response['hits']['hits'][count($response['hits']['hits']) - 1]['sort'][0], 
            'data' => $response['hits']['hits'],
        ];
  • 注意:
    • 当我们使用search_after时,from值必须设置为0或者-1(当然你也可以不设置这个from参数)。
    • 当存在search_after参数时,不允许出现scroll参数

search_after 带来的问题

ElasticSearch之Search_After的注意事项

1.搜索时,需要指定sort,并且保证值是唯一的(可以通过加入_id或者文档body中的业务唯一值来保证);
2.再次查询时,使用上一次最后一个文档的sort值作为search_after的值来进行查询;
3.不能使用随机跳页,只能是下一页或者小范围的跳页(一次查询出小范围内各个页数,利用缓存等技术,来实现小范围分页,比较麻烦,比如从第一页调到第五页,则依次查询出2,3,4页的数据,利用每一次最后一个文档的sort值进行下一轮查询,客户端或服务端都可以进行,如果跳的比较多,则可能该方法并不适用)
它与滚动API非常相似,但与它不同,search_after参数是无状态的,它始终针对最新版本的搜索器进行解析。因此,排序顺序可能会在步行期间发生变化,具体取决于索引的更新和删除

总结

from+ size 分页,如果数据量不大或者from、size不大的情况下,效率还是蛮高的。但是在深度分页的情况下,这种使用方式效率是非常低的,并发一旦过大,还有可能直接拖垮整个ElasticSearch的集群。
scroll 分页通常不会用在客户端,因为每一个 scroll_id 都会占用大量的资源,一般是后台用于全量读取数据使用
search_after通过维护一个实时游标来避免scroll的缺点,它可以用于实时请求和高并发场景,一般用于客户端的分页查询
大体而言就是在这三种分页方式中,from + size不适合数据量很大的场景,scroll不适合实时场景,而search after在es5.x版本之后应运而生,较好的解决了这个问题。

你可能感兴趣的:(ElasticSearch第5天 es实现分页查询的几种方式)