Elasticsearch分布式搜索架构原理 | Elasticsearch权威指南(中文版)
分布式搜索的执行方式
在继续之前,我们将绕道讲一下搜索是如何在分布式环境中执行的。它比我们之前讲的基础的增删改查(create-read-update-delete,CRUD)请求要复杂一些。
注意:
本章的信息只是出于兴趣阅读,使用Elasticsearch并不需要理解和记住这里的所有细节。
阅读这一章只是增加对系统如何工作的了解,并让你知道这些信息以备以后参考,所以别淹没在细节里。
一个CRUD操作只处理一个单独的文档。文档的唯一性由_index
,_type
和routing-value
(通常默认是该文档的_id
)的组合来确定。这意味着我们可以准确知道集群中的哪个分片持有这个文档。
由于不知道哪个文档会匹配查询(文档可能存放在集群中的任意分片上),所以搜索需要一个更复杂的模型。一个搜索不得不通过查询每一个我们感兴趣的索引的分片副本,来看是否含有任何匹配的文档。
但是,找到所有匹配的文档只完成了这件事的一半。在搜索(search
)API返回一页结果前,来自多个分片的结果必须被组合放到一个有序列表中。因此,搜索的执行过程分两个阶段,称为查询然后取回(query then fetch)。
查询阶段
在初始化查询阶段(query phase),查询被向索引中的每个分片副本(原本或副本)广播。每个分片在本地执行搜索并且建立了匹配document的优先队列(priority queue)。
优先队列
一个优先队列(priority queue is)只是一个存有前n个(top-n)匹配document的有序列表。这个优先队列的大小由分页参数from和size决定。例如,下面这个例子中的搜索请求要求优先队列要能够容纳100个document
GET /_search
{
"from": 90,
"size": 10
}
这个查询的过程被描述在图分布式搜索查询阶段中。
图1 分布式搜索查询阶段
查询阶段包含以下三步:
1.客户端发送一个search(搜索)
请求给Node 3
,Node 3
创建了一个长度为from+size
的空优先级队列。 2.Node 3
转发这个搜索请求到索引中每个分片的原本或副本。每个分片在本地执行这个查询并且结果将结果到一个大小为from+size
的有序本地优先队列里去。 3.每个分片返回document的ID和它优先队列里的所有document的排序值给协调节点Node 3
。Node 3
把这些值合并到自己的优先队列里产生全局排序结果。
当一个搜索请求被发送到一个节点Node,这个节点就变成了协调节点。这个节点的工作是向所有相关的分片广播搜索请求并且把它们的响应整合成一个全局的有序结果集。这个结果集会被返回给客户端。
第一步是向索引里的每个节点的分片副本广播请求。就像document的GET
请求一样,搜索请求可以被每个分片的原本或任意副本处理。这就是更多的副本(当结合更多的硬件时)如何提高搜索的吞吐量的方法。对于后续请求,协调节点会轮询所有的分片副本以分摊负载。
每一个分片在本地执行查询和建立一个长度为from+size
的有序优先队列——这个长度意味着它自己的结果数量就足够满足全局的请求要求。分片返回一个轻量级的结果列表给协调节点。只包含documentID值和排序需要用到的值,例如_score
。
协调节点将这些分片级的结果合并到自己的有序优先队列里。这个就代表了最终的全局有序结果集。到这里,查询阶段结束。
整个过程类似于归并排序算法,先分组排序再归并到一起,对于这种分布式场景非常适用。
注意
一个索引可以由一个或多个原始分片组成,所以一个对于单个索引的搜索请求也需要能够把来自多个分片的结果组合起来。一个对于 多(multiple)或全部(all)索引的搜索的工作机制和这完全一致——仅仅是多了一些分片而已。
取回阶段
查询阶段辨别出那些满足搜索请求的document,但我们仍然需要取回那些document本身。这就是取回阶段的工作,如图分布式搜索的取回阶段所示。
图2 分布式搜索取回阶段
分发阶段由以下步骤构成:
1.协调节点辨别出哪个document需要取回,并且向相关分片发出GET
请求。
2.每个分片加载document并且根据需要丰富(enrich)它们,然后再将document返回协调节点。
3.一旦所有的document都被取回,协调节点会将结果返回给客户端。
协调节点先决定哪些document是实际(actually)需要取回的。例如,我们指定查询{ "from": 90, "size": 10 }
,那么前90条将会被丢弃,只有之后的10条会需要取回。这些document可能来自与原始查询请求相关的某个、某些或者全部分片。
协调节点为每个持有相关document的分片建立多点get请求然后发送请求到处理查询阶段的分片副本。
分片加载document主体——_source
field。如果需要,还会根据元数据丰富结果和高亮搜索片断。一旦协调节点收到所有结果,会将它们汇集到单一的回答响应里,这个响应将会返回给客户端。
深分页
查询然后取回过程虽然支持通过使用from
和size
参数进行分页,但是要在有限范围内(within limited)。还记得每个分片必须构造一个长度为from+size
的优先队列吧,所有这些都要传回协调节点。这意味着协调节点要通过对分片数量 * (from + size)
个document进行排序来找到正确的size
个document。
根据document的数量,分片的数量以及所使用的硬件,对10,000到50,000条结果(1,000到5,000页)深分页是可行的。但是对于足够大的from
值,排序过程将会变得非常繁重,会使用巨大量的CPU,内存和带宽。因此,强烈不建议使用深分页。
在实际中,“深分页者”也是很少的一部人。一般人会在翻了两三页后就停止翻页,并会更改搜索标准。那些不正常情况通常是机器人或者网络爬虫的行为。它们会持续不断地一页接着一页地获取页面直到服务器到崩溃的边缘。
如果你确实需要从集群里获取大量documents,你可以通过设置搜索类型scan
禁用排序,来高效地做这件事。这一点将在后面的章节讨论。