京东面试题:ElasticSearch 深度分页解决方案

news/2024/3/29 20:32:02/文章来源:https://blog.csdn.net/Q54665642ljf/article/details/127864333

前言

Elasticsearch 是一个实时的分布式搜索与分析引擎,在使用过程中,有一些典型的使用场景,比如分页、遍历等。

在使用关系型数据库中,我们被告知要注意甚至被明确禁止使用深度分页,同理,在 Elasticsearch 中,也应该尽量避免使用深度分页。

这篇文章主要介绍 Elasticsearch 中分页相关内容!

From/Size 参数

在 ES 中,分页查询默认返回最顶端的 10 条匹配 hits。

如果需要分页,需要使用 from 和 size 参数。

  • from 参数定义了需要跳过的 hits 数,默认为 0;

  • size 参数定义了需要返回的 hits 数目的最大值。

一个基本的 ES 查询语句是这样的:

POST /my_index/my_type/_search{    "query": { "match_all": {}},    "from": 100,    "size":  10}

复制代码

上面的查询表示从搜索结果中取第 100 条开始的 10 条数据。

「那么,这个查询语句在 ES 集群内部是怎么执行的呢?」

在 ES 中,搜索一般包括两个阶段,query 和 fetch 阶段,可以简单的理解,query 阶段确定要取哪些 doc,fetch 阶段取出具体的 doc。

Query 阶段

如上图所示,描述了一次搜索请求的 query 阶段:·

  1. Client 发送一次搜索请求,node1 接收到请求,然后,node1 创建一个大小为from + size的优先级队列用来存结果,我们管 node1 叫 coordinating node。

  2. coordinating node 将请求广播到涉及到的 shards,每个 shard 在内部执行搜索请求,然后,将结果存到内部的大小同样为from + size 的优先级队列里,可以把优先级队列理解为一个包含top N结果的列表。

  3. 每个 shard 把暂存在自身优先级队列里的数据返回给 coordinating node,coordinating node 拿到各个 shards 返回的结果后对结果进行一次合并,产生一个全局的优先级队列,存到自身的优先级队列里。

在上面的例子中,coordinating node 拿到(from + size) * 6条数据,然后合并并排序后选择前面的from + size条数据存到优先级队列,以便 fetch 阶段使用。

另外,各个分片返回给 coordinating node 的数据用于选出前from + size条数据,所以,只需要返回唯一标记 doc 的_id以及用于排序的_score即可,这样也可以保证返回的数据量足够小。

coordinating node 计算好自己的优先级队列后,query 阶段结束,进入 fetch 阶段。

Fetch 阶段

query 阶段知道了要取哪些数据,但是并没有取具体的数据,这就是 fetch 阶段要做的。

上图展示了 fetch 过程:

  1. coordinating node 发送 GET 请求到相关 shards。

  2. shard 根据 doc 的_id取到数据详情,然后返回给 coordinating node。

  3. coordinating node 返回数据给 Client。

coordinating node 的优先级队列里有from + size_doc _id,但是,在 fetch 阶段,并不需要取回所有数据,在上面的例子中,前 100 条数据是不需要取的,只需要取优先级队列里的第 101 到 110 条数据即可。

需要取的数据可能在不同分片,也可能在同一分片,coordinating node 使用 「multi-get」 来避免多次去同一分片取数据,从而提高性能。

「这种方式请求深度分页是有问题的:」

我们可以假设在一个有 5 个主分片的索引中搜索。当我们请求结果的第一页(结果从 1 到 10 ),每一个分片产生前 10 的结果,并且返回给 协调节点 ,协调节点对 50 个结果排序得到全部结果的前 10 个。

现在假设我们请求第 1000 页—结果从 10001 到 10010 。所有都以相同的方式工作除了每个分片不得不产生前 10010 个结果以外。然后协调节点对全部 50050 个结果排序最后丢弃掉这些结果中的 50040 个结果。

「对结果排序的成本随分页的深度成指数上升。」

「注意 1:」

size 的大小不能超过index.max_result_window这个参数的设置,默认为 10000。

如果搜索 size 大于 10000,需要设置index.max_result_window参数

PUT _settings{    "index": {        "max_result_window": "10000000"    }}  

复制代码

「注意 2:」

_doc将在未来的版本移除,详见:

  • https://www.elastic.co/cn/blog/moving-from-types-to-typeless-apis-in-elasticsearch-7-0

  • https://elasticsearch.cn/article/158

深度分页问题

Elasticsearch 的 From/Size 方式提供了分页的功能,同时,也有相应的限制。

举个例子,一个索引,有 10 亿数据,分 10 个 shards,然后,一个搜索请求,from=1000000,size=100,这时候,会带来严重的性能问题:CPU,内存,IO,网络带宽。

在 query 阶段,每个 shards 需要返回 1000100 条数据给 coordinating node,而 coordinating node 需要接收10 * 1000,100 条数据,即使每条数据只有 _doc _id_score,这数据量也很大了?

「在另一方面,我们意识到,这种深度分页的请求并不合理,因为我们是很少人为的看很后面的请求的,在很多的业务场景中,都直接限制分页,比如只能看前 100 页。」

比如,有 1 千万粉丝的微信大 V,要给所有粉丝群发消息,或者给某省粉丝群发,这时候就需要取得所有符合条件的粉丝,而最容易想到的就是利用 from + size 来实现,不过,这个是不现实的,这时,可以采用 Elasticsearch 提供的其他方式来实现遍历。

深度分页问题大致可以分为两类:

  • 「随机深度分页:随机跳转页面」

  • 「滚动深度分页:只能一页一页往下查询」

「下面介绍几个官方提供的深度分页方法」

Scroll

Scroll 遍历数据

我们可以把 scroll 理解为关系型数据库里的 cursor,因此,scroll 并不适合用来做实时搜索,而更适合用于后台批处理任务,比如群发。

这个分页的用法,「不是为了实时查询数据」,而是为了**「一次性查询大量的数据(甚至是全部的数据」**)。

因为这个 scroll 相当于维护了一份当前索引段的快照信息,这个快照信息是你执行这个 scroll 查询时的快照。在这个查询后的任何新索引进来的数据,都不会在这个快照中查询到。

但是它相对于 from 和 size,不是查询所有数据然后剔除不要的部分,而是记录一个读取的位置,保证下一次快速继续读取。

不考虑排序的时候,可以结合SearchType.SCAN使用。

scroll 可以分为初始化和遍历两部,初始化时将**「所有符合搜索条件的搜索结果缓存起来(注意,这里只是缓存的 doc_id,而并不是真的缓存了所有的文档数据,取数据是在 fetch 阶段完成的)」**,可以想象成快照。

在遍历时,从这个快照里取数据,也就是说,在初始化后,对索引插入、删除、更新数据都不会影响遍历结果。

「基本使用」

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

复制代码

初始化指明 index 和 type,然后,加上参数 scroll,表示暂存搜索结果的时间,其它就像一个普通的 search 请求一样。

会返回一个_scroll_id_scroll_id用来下次取数据用。

「遍历」

POST /_search?scroll=1m{    "scroll_id":"XXXXXXXXXXXXXXXXXXXXXXX I am scroll id XXXXXXXXXXXXXXX"}

复制代码

这里的scroll_id即 上一次遍历取回的_scroll_id或者是初始化返回的_scroll_id,同样的,需要带 scroll 参数。

重复这一步骤,直到返回的数据为空,即遍历完成。

「注意,每次都要传参数 scroll,刷新搜索结果的缓存时间」。另外,「不需要指定 index 和 type」

设置 scroll 的时候,需要使搜索结果缓存到下一次遍历完成,「同时,也不能太长,毕竟空间有限。」

「优缺点」

缺点:

  1. 「scroll_id 会占用大量的资源(特别是排序的请求)」

  2. 同样的,scroll 后接超时时间,频繁的发起 scroll 请求,会出现一些列问题。

  3. 「是生成的历史快照,对于数据的变更不会反映到快照上。」

「优点:」

适用于非实时处理大量数据的情况,比如要进行数据迁移或者索引变更之类的。

Scroll Scan

ES 提供了 scroll scan 方式进一步提高遍历性能,但是 scroll scan 不支持排序,因此 scroll scan 适合不需要排序的场景

「基本使用」

Scroll Scan 的遍历与普通 Scroll 一样,初始化存在一点差别。

POST /my_index/my_type/_search?search_type=scan&scroll=1m&size=50{ "query": { "match_all": {}}}

复制代码

需要指明参数:

  • search_type:赋值为 scan,表示采用 Scroll Scan 的方式遍历,同时告诉 Elasticsearch 搜索结果不需要排序。

  • scroll:同上,传时间。

  • size:与普通的 size 不同,这个 size 表示的是每个 shard 返回的 size 数,最终结果最大为 number_of_shards * size

「Scroll Scan 与 Scroll 的区别」

  1. Scroll-Scan 结果**「没有排序」**,按 index 顺序返回,没有排序,可以提高取数据性能。

  2. 初始化时只返回 _scroll_id,没有具体的 hits 结果

  3. size 控制的是每个分片的返回的数据量,而不是整个请求返回的数据量。

Sliced Scroll

如果你数据量很大,用 Scroll 遍历数据那确实是接受不了,现在 Scroll 接口可以并发来进行数据遍历了。

每个 Scroll 请求,可以分成多个 Slice 请求,可以理解为切片,各 Slice 独立并行,比用 Scroll 遍历要快很多倍。

POST /index/type/_search?scroll=1m{    "query": { "match_all": {}},    "slice": {        "id": 0,        "max": 5    }   } POST ip:port/index/type/_search?scroll=1m{    "query": { "match_all": {}},    "slice": {        "id": 1,        "max": 5    }   }

复制代码

上边的示例可以单独请求两块数据,最终五块数据合并的结果与直接 scroll scan 相同。

其中 max 是分块数,id 是第几块。

官方文档中建议 max 的值不要超过 shard 的数量,否则可能会导致内存爆炸。

Search After

Search_after是 ES 5 新引入的一种分页查询机制,其原理几乎就是和 scroll 一样,因此代码也几乎是一样的。

「基本使用:」

第一步:

POST twitter/_search{    "size": 10,    "query": {        "match" : {            "title" : "es"        }    },    "sort": [        {"date": "asc"},        {"_id": "desc"}    ]}

复制代码

返回出的结果信息 :

{      "took" : 29,      "timed_out" : false,      "_shards" : {        "total" : 1,        "successful" : 1,        "skipped" : 0,        "failed" : 0      },      "hits" : {        "total" : {          "value" : 5,          "relation" : "eq"        },        "max_score" : null,        "hits" : [          {            ...            },            "sort" : [              ...            ]          },          {            ...            },            "sort" : [              124648691,              "624812"            ]          }        ]      }    }

复制代码

上面的请求会为每一个文档返回一个包含 sort 排序值的数组。

这些 sort 排序值可以被用于search_after参数里以便抓取下一页的数据。

比如,我们可以使用最后的一个文档的 sort 排序值,将它传递给search_after参数:

GET twitter/_search{    "size": 10,    "query": {        "match" : {            "title" : "es"        }    },    "search_after": [124648691, "624812"],    "sort": [        {"date": "asc"},        {"_id": "desc"}    ]}

复制代码

若我们想接着上次读取的结果进行读取下一页数据,第二次查询在第一次查询时的语句基础上添加search_after,并指明从哪个数据后开始读取。

「基本原理」

es 维护一个实时游标,它以上一次查询的最后一条记录为游标,方便对下一页的查询,它是一个无状态的查询,因此每次查询的都是最新的数据。

由于它采用记录作为游标,因此**「SearchAfter 要求 doc 中至少有一条全局唯一变量(每个文档具有一个唯一值的字段应该用作排序规范)」**

「优缺点」

「优点:」

  1. 无状态查询,可以防止在查询过程中,数据的变更无法及时反映到查询中。

  2. 不需要维护scroll_id,不需要维护快照,因此可以避免消耗大量的资源。

「缺点:」

  1. 由于无状态查询,因此在查询期间的变更可能会导致跨页面的不一值。

  2. 排序顺序可能会在执行期间发生变化,具体取决于索引的更新和删除。

  3. 至少需要制定一个唯一的不重复字段来排序。

  4. 它不适用于大幅度跳页查询,或者全量导出,对第 N 页的跳转查询相当于对 es 不断重复的执行 N 次 search after,而全量导出则是在短时间内执行大量的重复查询。

SEARCH_AFTER不是自由跳转到任意页面的解决方案,而是并行滚动多个查询的解决方案。

总结

ES7 版本变更

参照:https://www.elastic.co/guide/en/elasticsearch/reference/master/paginate-search-results.html#scroll-search-results

7.*版本中,ES 官方不再推荐使用 Scroll 方法来进行深分页,而是推荐使用带 PIT 的search_after来进行查询;

7.*版本开始,您可以使用SEARCH_AFTER参数通过上一页中的一组排序值检索下一页命中。

使用SEARCH_AFTER需要多个具有相同查询和排序值的搜索请求。

如果这些请求之间发生刷新,则结果的顺序可能会更改,从而导致页面之间的结果不一致。

为防止出现这种情况,您可以创建一个时间点(PIT)来在搜索过程中保留当前索引状态。

POST /my-index-000001/_pit?keep_alive=1m
返回一个PIT ID:{  "id": "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA=="}

复制代码

在搜索请求中指定 PIT:

GET /_search{  "size": 10000,  "query": {    "match" : {      "user.id" : "elkbee"    }  },  "pit": {     "id":  "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA==",      "keep_alive": "1m"  },  "sort": [     {"@timestamp": {"order": "asc", "format": "strict_date_optional_time_nanos", "numeric_type" : "date_nanos" }}  ]}

复制代码

性能对比

分别分页获取1 - 1049000 - 4901099000 - 99010范围各 10 条数据(前提 10w 条),性能大致是这样:

向前翻页

对于向前翻页,ES 中没有相应 API,但是根据官方说法(https://github.com/elastic/elasticsearch/issues/29449),ES 中的向前翻页问题可以通过翻转排序方式来实现即:

  1. 对于某一页,正序search_after该页的最后一条数据 id 为下一页,则逆序search_after该页的第一条数据 id 则为上一页。

  2. 国内论坛上,有人使用缓存来解决上一页的问题:https://elasticsearch.cn/question/7711

总结

  1. 如果数据量小(from+size 在 10000 条内),或者只关注结果集的 TopN 数据,可以使用 from/size 分页,简单粗暴

  2. 数据量大,深度翻页,后台批处理任务(数据迁移)之类的任务,使用 scroll 方式

  3. 数据量大,深度翻页,用户实时、高并发查询需求,使用 search after 方式

个人思考

Scroll 和search_after原理基本相同,他们都采用了游标的方式来进行深分页。

这种方式虽然能够一定程度上解决深分页问题。但是,它们并不是深分页问题的终极解决方案,深分页问题**「必须避免!!」**。

对于 Scroll,无可避免的要维护scroll_id和历史快照,并且,还必须保证scroll_id的存活时间,这对服务器是一个巨大的负荷。

对于Search_After,如果允许用户大幅度跳转页面,会导致短时间内频繁的搜索动作,这样的效率非常低下,这也会增加服务器的负荷,同时,在查询过程中,索引的增删改会导致查询数据不一致或者排序变化,造成结果不准确。

Search_After本身就是一种业务折中方案,它不允许指定跳转到页面,而只提供下一页的功能。

Scroll 默认你会在后续将所有符合条件的数据都取出来,所以,它只是搜索到了所有的符合条件的doc_id(这也是为什么官方推荐用doc_id进行排序,因为本身缓存的就是doc_id,如果用其他字段排序会增加查询量),并将它们排序后保存在协调节点(coordinate node),但是并没有将所有数据进行 fetch,而是每次 scroll,读取 size 个文档,并返回此次读取的最后一个文档以及上下文状态,用以告知下一次需要从哪个 shard 的哪个文档之后开始读取。

这也是为什么官方不推荐 scroll 用来给用户进行实时的分页查询,而是适合于大批量的拉取数据,因为它从设计上就不是为了实时读取数据而设计的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_224079.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Python实战】听书就用它了:海量资源随便听,内含几w书源,绝对精品哦~(好消息好消息)

前言 有温度 有深度 有广度 就等你来关注哦~ 所有文章完整的素材源码都在👇👇 粉丝白嫖源码福利,请移步至CSDN社区或文末公众hao即可免费。 哈喽!我是栗子同学,继续更新——今天聊一聊“西马拉雅”。(谐音…

特征选择-sklearn

sklearn特征选择:移除低方差特征:单变量特征选择递归特征消除基于模型的SelectFromModel顺序特征选择特征选择作为pipline的一部分sklearn.feature_selection模块中的类可用于样本集的特征选择/降维,以提高估计器的准确性得分或提高其在非常高维的数据集…

BL200OPC UA分布式IO系统接线方式

BL200OPC UA 数据点 Node Id OPC UA 的 Node Id 默认是 NS1;SI/O 数据点的 Modbus 映射地址(如首个 DO 模 块第一路 DO:NS1;S1000),具体 Modbus 映射地址参考 5.1.4Modbus 寄存器映射, 如果是自定义的 OPC UA 模型 Nod…

(02)Cartographer源码无死角解析-(19) SensorBridge→雷达点云数据预处理(函数重载)

本人讲解关于slam一系列文章汇总链接:史上最全slam从零开始,针对于本栏目讲解(02)Cartographer源码无死角解析-链接如下: (02)Cartographer源码无死角解析- (00)目录_最新无死角讲解:https://blog.csdn.net/weixin_43013761/article/details/127350885 …

3.35 OrCAD中怎么产生Cadence Allegro的第一方网表?OrCAD软件输出Cadence Allegro第一方网表报错时应该怎么处理?

笔者电子信息专业硕士毕业,获得过多次电子设计大赛、大学生智能车、数学建模国奖,现就职于南京某半导体芯片公司,从事硬件研发,电路设计研究。对于学电子的小伙伴,深知入门的不易,特开次博客交流分享经验&a…

cpu与指令集

讨论一下 作为一个java程序员,我们都知道,当我们写完代码,java文件会被编译为class文件,然后交给jvm去执行,那么这个执行过程是啥样的呢?? 一般我们得到的解答都是,class代码会被解…

2、HTML——标题分组、居中、引用标签、水平线标签下划线标签、删除标签、<font>标签、图像标签

目录 一、基本标签 1、标题分组:hgroup 2、居中:center 3、引用标签 3.1 块(长)引用标签:blockquote 3.2 短引用标签:q 4、水平线标签:hr 5、下划线标签:ins 6、删除标…

【论文笔记之 BLMS】Block Implementation of Adaptive Digital Filters

本文对 GREGORY A. CLARK 于 1981 年在 IEEE Transactions on Circuits and Systems 上发表的论文进行简单地翻译。如有表述不当之处欢迎批评指正。欢迎任何形式的转载,但请务必注明出处。 论文链接:https://ieeexplore.ieee.org/abstract/document/108…

安利几个小技巧教会你ppt如何转pdf

作为一名打工人,特别是办公类,经常是要处理大大小小的文件,有时候甚至要做多种文件转换。并且老板都是多变的,经常突然性就让你把辛苦制作一大半的PPT转成PDF格式的文件再给他。那刚入门的职场小白肯定就会选择,老老实…

我终于读懂了适配器模式。。。

文章目录🗾🌆什么是适配器模式?🏯类适配器模式🏰对象适配器模式⛺️接口适配器模式🏭适配器模式在SpringMVC 框架应用的源码剖析🗼适配器模式的注意事项和细节🌆什么是适配器模式&am…

自学软件测试?一般人我还是劝你算了吧...

本人7年测试经验,在学测试之前对电脑的认知也就只限于上个网,玩个办公软件。这里不能跑题,我为啥说:自学软件测试,一般人我还是劝你算了吧?因为我就是那个一般人! 软件测试基础真的很简单&…

C# Socket

一 两个人在两个房间里打电话的图 ① 人通过【电话】可以通信; ② 程序通过【Socket】来通信; ③ *套接字 就是 程序间的电话机; ④ 我和孙权打电话 电话 规定好的语言; ⑤ 电脑和电话进行联系 协议; 二 Socket相关…

JVM(二十三)—— 垃圾回收器(三)G1垃圾回收器

G1垃圾回收器:区域化分代式G1概述G1的特点(优势)G1的缺点G1的参数设置G1的适用场景分区region:化整为零记忆集和写屏障G1回收器垃圾回收过程年轻代GC并发标记过程混合回收G1概述 应用程序所应对的业务越来越庞大,复杂&#xff0c…

【大数据】flink 读取文件数据写入ElasticSearch

前言 es是大数据存储的必备中间件之一,通过flink可以读取来自日志文件,kafka等外部数据源的数据,然后写入到es中,本篇将通过实例演示下完整的操作过程; 一、前置准备 1、提前搭建并开启es服务(本文使用doc…

图像分割 - Hough变换直线检测

目录 1. Hough 直线检测 2. HoughLinesP 函数 1. Hough 直线检测 霍夫变换(Hough 变换):利用对偶原理,把原空间的问题转换到对偶空间去求解 这里涉及到空间转换,将原来的笛卡尔空间(xy空间)…

App安全架构之前端安全防护

近年来,随着互联网、物联网、移动设备、5G通讯等技术的齐头发展,人类的生活和工作越来越离不开软件和互联网,正如人类社会文明发展到一定程度以后,会需要法律等社会规范来保护一样,线上环境也是一样道理。 Gartner 对…

Python学习小组课程-课程大纲与Python开发环境安装

一、前言 注意:此为内部小组学习资料,非售卖品,仅供学习参考。 为提升项目落地的逻辑思维能力,以及通过自我创造工具来提升工作效率,特成立Python学习小组。计划每周花一个小时进行在线会议直播学习,面向…

国内访问Github超级慢?那是你没有用我这个脚本。直接起飞。

导语 之前很多朋友咨询过国内访问Github较慢的问题,然后我一般让他们自己去知乎上找攻略,但今天我才发现网上竟然没有一个一键配置的脚本,一般都需要我们跟着教程一步步地去做才行。这也太麻烦了,于是自己动手写了个脚本&#xf…

ceph浅谈

总谈 ceph简介 用上ceph,多台机器的磁盘空间在一起了,在一台机器上就可以看到使用所有空间。 还可以保存多份安全备份 存储先ceph,自我管理修复,跨机房,节点越多,并行化,论上,节点越…

1-(3-磺酸基)丙基-1-甲基-2-吡咯烷酮三氟甲磺酸盐[C3SO3Hnmp]CF3SO3

1-(3-磺酸基)丙基-1-甲基-2-吡咯烷酮三氟甲磺酸盐[C3SO3Hnmp]CF3SO3 离子液体(IonicLiquids)是完全由离子组成,现在多指在低于100摄氏度时呈液体状态的熔盐。通常由特定的有机阳离子和无机阴离子(或有机阴离子)构成。 离子液体特点 蒸汽压…