Elasticsearch分布式检索的两阶段流程
在说明ES分布式检索流程前,我们先看下内部组件的概念架构:
Elasticsearch概念架构
ES分布式检索的两阶段
如果是对单个文档进行CURD操作,通常从路由计算routing values(默认是该文档的 _id)就能确定是集群中哪个分片含有此文档。而检索不同,因为ES一个索引有多个分片,检索的信息可能分布在每个分片上,这就需要一种更加复杂的执行模型。
query then fetch两阶段执行模型:
简单概括:
-
query:协调节点让每个分片去自查,每个分片上有优先队列,把符合这次排序的结果(注意这个结果是doc id集合以及任何排序需要用到的值,例如 _score)返回
-
fetch:协调节点归并排序完拿到最终需要的doc id,再去各分片mget拿到需要的结果
二阶段流程细化流程:
query阶段
在初始 查询阶段 时, 查询会广播到索引中每一个分片拷贝(主分片或者副本分片)。 每个分片在本地执行搜索并构建一个匹配文档的优先队列。
优先队列
一个 优先队列 仅仅是一个存有 top-n 匹配文档的有序列表。优先队列的大小取决于分页参数 from 和 size 。例如,如下搜索请求将需要足够大的优先队列来放入100条文档。
GET /_search
{
"from": 90,
"size": 10
}
如下集群部署结构:三个节点,一个索引,两个分片,每个分片三个副本。
query阶段包含以下3个步骤:
- 客户端发送一个 search 请求到 Node 3 , Node 3 会创建一个大小为from + size 的 空优先队列。
- Node 3 将查询请求转发到索引的每个主分片或副本分片中。每个分片在本地执行查询并添加结果到大小为 from + size 的本地有序优先队列中。
- 每个分片返回各自优先队列中所有文档的 ID 和排序值给协调节点,也就是 Node 3 ,它合并这些值到自己的优先队列中来产生一个全局排序后的结果列表。
每个分片在本地执行查询请求并且创建一个长度为 from + size 的优先队列—也就是说,每个分片创建的结果集足够大,均可以满足全局的搜索请求。 分片返回一个轻量级的结果列表到协调节点,它仅包含文档 ID 集合以及任何排序需要用到的值,例如 _score 。
协调节点将这些分片级的结果合并到自己的有序优先队列里,它代表了全局排序结果集合。至此查询过程结束。
协调节点作用
Node 3 就是本次请求的协调节点:当一个搜索请求被发送到某个节点时,这个节点就变成了协调节点。
- 协调节点,知道集群部署结构,知道这个索引有哪些分片,在哪些节点上,协调节点轮询所有的分片来分摊负载。
- 拿到所有分片返回的值,再做归并整体有序,再用docId去请求各分片拿结果
- 拿到所有结果后,组装返给客户端。
fetch阶段
fetch阶段包含以下3个步骤:
- 协调节点辨别出哪些文档需要被取回并向相关的分片提交多个 GET 请求。
- 每个分片加载并 丰富 文档,接着返回文档给协调节点。
- 一旦所有的文档都被取回了,协调节点返回结果给客户端。
协调节点首先决定哪些文档确实需要被取回。例如,如果我们的查询指定了 { “from”: 90, “size”: 10 } ,最终只有10个结果需要被取回,这些文档可能来自搜索请求有关的一个、多个甚至全部分片。
协调节点给持有相关文档的每个分片创建一个 multi-get request ,并发送请求给同样处理查询阶段的分片副本。
深分页
先查后取的过程支持用 from 和 size 参数分页,但有限制。 要记住需要传递信息给协调节点的每个分片必须先创建一个 from + size 长度的队列,协调节点需要根据 number_of_shards * (from + size) 排序文档,来找到被包含在 size 里的文档。
from+size的深分页,要取决于文档的大小,分片的数量和你使用的硬件,给 10,000 到 50,000 的结果文档深分页( 1,000 到 5,000 页)是完全可行的。但是使用足够大的 from 值,排序过程可能会变得非常沉重,使用大量的CPU、内存和带宽。因此尽量避免这样使用。
如果确实需要取回大量的文档,可以通过 scroll 查询禁用排序使这个取回行为更有效率。
scroll 做了一个有效期的快照视图,并用的_doc排序,仅仅从还有结果的分片返回下一批结果,不需要深分页的结果集全局排序的消耗。