ElasticSearch分布式搜索的执行方式

分布式搜索的执行方式

我们知道,一个文档是由index,type,id的组合来确定唯一性的。这意味我们可以准确知道某个文档在集群中的哪个分片上。

但由于不知道哪个文档会被匹配查询,所以搜索需要一个更复杂的模型,一个搜索不得不通过查询每一个我们感兴趣的索引的复制分片,来看是否含有任何匹配的文档。

在找到所有匹配的文档只完成了搜索的一半,搜索的执行过程分为两个阶段。查询阶段和取回阶段。

查询阶段

在初始化 查询阶段 (query phase),查询被向索引中的每个分片副本(原本或副本)广播。每个分片在本地执行搜索并且建立了匹配document的 优先队列 (priority queue)。所谓的优先队列即一个存有 前n个 匹配document的有序列表。这个优先队列的大小由分页参数from和size决定。

查询阶段包括以下三个步骤:

1. 客户端发送一个 search(搜索) 请求给 Node 3 , Node 3 创建了一个长度为 from+size 的空优先级队列(默认长度是10)。

2. Node 3 转发这个搜索请求到索引中每个分片的原本或副本。每个分片在本地执行这个查询并将结果到一个大小为 from+size 的有序本地优先队列里去。

3. 每个分片返回document的ID和它优先队列里的所有document的排序值给协调节点 Node 3 。Node3 把这些值合并到自己的优先队列里产生全局排序结果。

当一个搜索请求被发送到一个节点Node,这个节点就变成了协调节点。这个节点的工作是向所有相关的分片广播搜索请求并且把它们的响应整合成一个全局的有序结果集。这个结果集会被返回给客户端。

第一步是向索引里的每个节点的分片副本广播请求。就像document的 GET 请求一样,搜索请求可以被每个分片的原本或任意副本处理。这就是更多的副本(当结合更多的硬件时)如何提高搜索的吞吐量的方法。对于后续请求,协调节点会轮询所有的分片副本以分摊负载。

每一个分片在本地执行查询和建立一个长度为 from+size 的有序优先队列——这个长度意味着它自己的结果数量就足够满足全局的请求要求。分片返回一个轻量级的结果列表给协调节点。只包含document ID值和排序需要用到的值,例如 _score 。

协调节点将这些分片级的结果合并到自己的有序优先队列里。这个就代表了最终的全局有序结果集。到这里,查询阶段结束。

一个索引可以由一个或多个原始分片组成,所以一个对于单个索引的搜索请求也需要能够把来自多个分片的结果组合起来。一个对于 多(multiple) 或 全部(all) 索引的搜索的工作机制和这完全一致——仅仅是多了一些分片而已。

取回阶段

查询阶段辨别出那些满足搜索请求的document,但我们仍然需要取回那些document本身。这就是取回阶段的工作,如图分布式搜索的取回阶段所示。

取回阶段由以下步骤构成:

1. 协调节点辨别出哪个document需要取回,并且向相关分片发出 GET 请求。

2. 每个分片加载document并且根据需要 丰富(enrich)它们,然后再将document返回协调节点。

3. 一旦所有的document都被取回,协调节点会将结果返回给客户端。

协调节点先决定哪些document是 实际(actually) 需要取回的。然后为每个持有相关document的分片建立多点get请求然后发送请求到处理查询阶段的分片副本。分片加载document主体—— _source field。如果需要,还会根据元数据丰富结果和高亮搜索片断。一旦协调节点收到所有结果,会将它们汇集到单一的回答响应里,这个响应将会返回给客户端。

注:查询然后取回过程虽然支持通过使用 from 和 size 参数进行分页,但是 要在有限范围内进行,对于足够大的 from 值,排序过程将会变得非常繁重,会使用巨大量的CPU,内存和带宽。因

此,强烈不建议使用深分页。

结果震荡(Bouncing Results)

想像一下,你正在按照 timestamp 字段来对你的结果排序,并且有两个document有相同的timestamp。由于搜索请求是在所有有效的分片副本间轮询的,这两个document可能在原始分片里是一种顺序,在副本分片里是另一种顺序。

这就是被称为 结果震荡(bouncing results) 的问题:用户每次刷新页面,结果顺序会发生变化。避免这个问题方法是对于同一个用户总是使用同一个分片。方法就是使用一个随机字符串,例如用户的会话ID(session ID)来设置 preference 参数。

preference(偏好)

preference 参数允许你控制使用哪个分片或节点来处理搜索请求。她接受如下一些参数 :

_primary    操作将只在主分片上执行。

_primary_first   操作将在主分片上执行,如果不可用(故障转移),将在其他分片上执行。

_local   如果可能的话,操作将更倾向于在本地分配的分片上执行。

_only_node:xyz  将操作限制到中指定的节点。

_prefer_node:xyz,abc  在提供节点ID的节点(ABC或XYZ)上执行。

_shards:2,3    将操作限制为指定分片。这种偏好可以与其他偏好相结合但必须首先出现;

timeout(超时)

通常,协调节点会等待接收所有分片的回答。如果有一个节点遇到问题,它会拖慢整个搜索请求。

timeout 参数告诉协调节点最多等待多久,就可以放弃等待而将已有结果返回。返回部分结果总比什么都没有好。

routing(路由选择)

在路由值那节里,我们解释了如何在建立索引时提供一个自定义的 routing 参数来保证所有相关的document(如属于单个用户的document)被存放在一个单独的分片中。在搜索时,你可以指定一个或多个 routing 值来限制只搜索那些分片而不是搜索index里的全部分片:

在此重申一下文档入哪个分片的计算规则,其中routing是一个随机数,默认是指ID。numOfPrmaryShards值创建索引时,指定的主分片数。取余后,shard的值介于0 ~(numOfprimaryShards-1)之间。

shard = hash(routing)%numOfPrimaryShards

search_type(搜索类型)

query_then_fetch 默认的搜索类型,指先查询,后取回。分为查询阶段和取回阶段;

query_and_fetch 该搜索类型将查询和取回阶段合并成一个步骤。该搜索类型是一个内部优化的选项。但该搜索类型仅仅满足搜索请求的目标只是一个分片时才可以使用。例如指定了 routing(路由选择) 值的时候。

scan(扫描)深度分页代价最高的部分是对结果的全局排序,但如果禁用排序,就能以很低的代价获得全部返回结果。为达成这个目的,可以采用 scan(扫描) 搜索模式。扫描模式让Elasticsearch不排序,只要分片里还有结果可以返回,就返回一批结果。scan(扫描) 搜索类型是和 scroll(滚屏) API连在一起使用的,可以高效地取回巨大数量的结果而不需要付出深分页的代价。它是通过禁用排序来实现的。

scroll(游标搜索)scroll 搜索模式 可以用来对 Elasticsearch 有效地执行大批量的文档查询,而又不用付出深度分页那种代价。深度分页的代价根源是结果集全局排序,如果去掉全局排序的特性的话查询结果的成本就会很低。 游标查询用字段 _doc 来排序。 这个指令让 Elasticsearch 仅仅从还有结果的分片返回下一批结果。游标查询允许我们先做查询初始化,然后再批量地拉取结果。

那么超出设定的过期时间后查询结果是什么?如下图:

dfs_query_then_fetch 和 dfs_query_and_fetch   dfs 是指 分布式频率搜索(Distributed Frequency Search),它告诉 ES,先分别获得每个分片本地的 IDF ,然后根据结果再计算整个索引的全局 IDF;这样就可以防止因为各分片的IDF值不一致,导致相关性被破坏的情况出现。

扩展:

前文,我们描述了 Elasticsearch 默认使用的相似度算法,这个算法叫做 词频/逆向文档频率 或 TF/IDF 。词频是计算某个词在当前被查询文档里某个字段中出现的频率,出现的频率越高,文档越相关。 逆向文档频率某个词在索引内所有文档出现的百分数 考虑在内,出现的频率越高,它的权重就越低。

但是由于性能原因, Elasticsearch 不会计算索引内所有文档的 IDF 。 相反,每个分片会根据 该分片 内的所有文档计算一个本地 IDF 。这就导致了同一个检索词,;两次检索的结果不一样,即相关性被破坏。

说明:

不要在生产环境上使用 dfs_query_then_fetch 。完全没有必要。只要有足够的数据就能保证词频是均匀分布的。没有理由给每个查询额外加上 DFS 这步。

猜你喜欢

转载自blog.csdn.net/zhtzh312/article/details/88863203
今日推荐