1.对已知文档的搜索
如果被搜索的文档(不论是单个文档,还是批量文档)能够从主分片或任意一个副本分片中被检索到,则与索引文档过程相同,对已知文档的搜索也会用到路由算法,Elasticsearch 中的路由算法如下所示:
Shard = hash(routing) % number_of_primary_shards
2.对未知文档的搜索
除了对已知文档的搜索外,大部分请求实际上是不知道查询条件会命中哪些文档的。这些被查询条件命中的文档可能位于 Elasticsearch 集群中的任意位置上。因此,搜索请求的执行不得不去查询每个索引中的每一个分片。
在 Elasticsearch 中,搜索过程分为查询阶段(Query Pahse)和获取阶段(Fetch Phase)。
在查询阶段,查询请求会广播到索引中的每一个主分片和备份中,每一个分片都会在本地执行检索,并在本地各建立一个优先级队列(Priority Queue)。该优先级队列是一份根据文档相关度指标进行排序的列表,列表的长度由 from 和 size 两个分页参数决定。
查询阶段可以再细分成3个小的子阶段:
(1)客户端发送一个检索请求给某个节点A,此时节点A会创建一个空的优先级队列,并配置好分页参数from与size。
(2)节点A将搜索请求发送给该索引中的每一个分片,每个分片在本地执行检索,并将结果添加到本地优先级队列中。
(3)每个分片返回本地优先级序列中所记录的ID与sort值,并发送给节点A。节点A将这些值合并到自己的本地优先级队列中,并做出全局的排序。
在获取阶段,主要是基于上一阶段找到所要搜索文档的具体位置,将文档数据内容取回并返回给客户端。
在Elasticsearch中,默认的搜索类型就是上面介绍的Query then Fetch。上述描述运作方式就是Query then Fetch。Query then Fetch有可能会出现打分偏离的情形,幸好,Elasticsearch还提供了一个称为"DFS Query then Fetch"的搜索方式,它和Query then Fetch基本相同,但是它会执行一个查询来计算整体文档的frequency。其处理过程如下所示:
(1)预查询每个分片,询问Term和Document Frequency等信息。
(2)发送查询请求到每个分片。
(3)找到各个分片中所有匹配的文档,并使用全局的Term/Document Frequency信息进行打分。在执行过程中依然需要对结果构建一个优先队列,如排序等。
(4)返回关于结果的元数据到请求节点。需要指出的是,此时实际文档还没有发送到请求节点,发送的只是分数。
(5)请求节点将来自所有分片的分数合并起来,并在请求节点上进行排序,文档被按照查询要求进行选择。最终,实际文档从它们各自所在的独立的分片上被检索出来,结果被返回给读者。
3.对词条的搜索
具体到一个分片,ELasticsearch是如何按照词条进行进行搜索的呢?
当词条数量较少时,我们可以顺序遍历词条获取结果,但如果词条有成千上万个时,Elasticsearch为了能快速找到某个词条,它对所有的词条都进行了排序,随后使用二分法查找词条,其查找效率为log(N)。这个过程就像查字典一样,因此排序词条的集合也称为Term Dictionary。
为了提高查询性能,Elasticsearch直接通过内存查找词条,而非从磁盘中读取。但当词条太多时,显然Term Dictionary也会很大,此时全部放在内存有些不现实,于是引入了Term Index。
Term Index就像字典中的索引页,其中的内容如字母A开头的有哪些词条,这些词条分别在哪页。通过Term Index,Elasticsearch也可以快速定位到Term Dictionary的某个OffSet(位置偏移),然后从这个位置再往后顺序查找。
前面提及了单个词条的搜索方法,而在实际应用中,更常见的往往是多个词条拼接程的"联合查询"。