elasticsearch(五)---分布式搜索

在继续以前,咱们将讲一下搜索是如何在分布式环境中执行的。html

它比咱们以前讲的基础的增删改查(create-read-update-delete ,CRUD)请求要复杂一些。node

一个CRUD操做只处理一个单独的文档。文档的惟一性由_index, _typerouting-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
}
复制代码

这个查询的过程被描述在图分布式搜索查询阶段中。session

图1 分布式搜索查询阶段分布式

查询阶段包含如下三步:fetch

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

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

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

当一个搜索请求被发送到一个节点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。若是须要,还会根据元数据丰富结果和高亮搜索片段。一旦协调节点收到全部结果,会将它们聚集到单一的回答响应里,这个响应将会返回给客户端。

深分页

查询而后取回过程虽然支持经过使用fromsize参数进行分页,可是要在有限范围内(within limited)。

还记得每一个分片必须构造一个长度为from+size的优先队列吧,全部这些都要传回协调节点。这意味着协调节点要经过对分片数量 * (from + size)个document进行排序来找到正确的size个document。

根据document的数量,分片的数量以及所使用的硬件,对10,000到50,000条结果(1,000到5,000页)深分页是可行的。可是对于足够大的from值,排序过程将会变得很是繁重,会使用巨大量的CPU,内存和带宽。所以,强烈不建议使用深分页。

在实际中,“深分页者”也是不多的一部人。通常人会在翻了两三页后就中止翻页,并会更改搜索标准。那些不正常状况一般是机器人或者网络爬虫的行为。它们会持续不断地一页接着一页地获取页面直到服务器到崩溃的边缘。

若是你确实须要从集群里获取大量documents,你能够经过设置搜索类型scan禁用排序,来高效地作这件事。

搜索选项

一些查询字符串(query-string)可选参数可以影响搜索过程。

preference(偏心)

preference参数容许你控制使用哪一个分片或节点来处理搜索请求。

她接受以下一些参数 _primary_primary_first_local_only_node:xyz_prefer_node:xyz_shards:2,3

然而一般最有用的值是一些随机字符串,它们能够避免结果震荡问题(the bouncing results problem)。

结果震荡(Bouncing Results)

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

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

timeout(超时)

一般,协调节点会等待接收全部分片的回答。若是有一个节点遇到问题,它会拖慢整个搜索请求。

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

搜索请求的返回将会指出这个搜索是否超时,以及有多少分片成功答复了:

...
    "timed_out":     true,  (1)
    "_shards": {
       "total":      5,
       "successful": 4,
       "failed":     1     (2)
    },
    ...
复制代码

(1) 搜索请求超时。

(2) 五个分片中有一个没在超时时间内答复。

若是一个分片的全部副本都由于其余缘由失败了——也许是由于硬件故障——这个也一样会反映在该答复的_shards部分里。

routing(路由选择)

在搜索时,你能够指定一个或多个routing 值来限制只搜索那些分片而不是搜索index里的所有分片:

GET /_search?routing=user_1,user2
复制代码

search_type(搜索类型)

虽然query_then_fetch是默认的搜索类型,但也能够根据特定目的指定其它的搜索类型,例如:

GET /_search?search_type=count
复制代码

1.count(计数)

count(计数)搜索类型只有一个query(查询)的阶段。当不须要搜索结果只须要知道知足查询的document的数量时,可使用这个查询类型。

2.query_and_fetch(查询而且取回)

query_and_fetch(查询而且取回)搜索类型将查询和取回阶段合并成一个步骤。这是一个内部优化选项,当搜索请求的目标只是一个分片时可使用,例如指定了routing(路由选择)值时。虽然你能够手动选择使用这个搜索类型,可是这么作基本上不会有什么效果。

3.dfs_query_then_fetch 和 dfs_query_and_fetch

dfs搜索类型有一个预查询的阶段,它会从所有相关的分片里取回项目频数来计算全局的项目频数。

4.scan(扫描)

scan(扫描)搜索类型是和scroll(滚屏)API连在一块儿使用的,能够高效地取回巨大数量的结果。它是经过禁用排序来实现的。

扫描和滚屏

scan(扫描)搜索类型是和scroll(滚屏)API一块儿使用来从Elasticsearch里高效地取回巨大数量的结果而不须要付出深分页的代价。

scroll(滚屏)

一个滚屏搜索容许咱们作一个初始阶段搜索而且持续批量从Elasticsearch里拉取结果直到没有结果剩下。这有点像传统数据库里的cursors(游标)。

传统数据库游标:游标(cursor)是系统为用户开设的一个数据缓冲区,存放SQL语句的执行结果。
每一个游标区都有一个名字,用户能够用SQL语句逐一从游标中获取记录,并赋给主变量,交由主语言进一步处理。
就本质而言,游标其实是一种能从包括多条数据记录的结果集中每次提取一条记录的机制。
游标是一段私有的SQL工做区,也就是一段内存区域,用于暂时存放受SQL语句影响到的数据。
通俗理解就是将受影响的数据暂时放到了一个内存区域的虚表中,而这个虚表就是游标。
复制代码

滚屏搜索会及时制做快照。这个快照不会包含任何在初始阶段搜索请求后对index作的修改。它经过将旧的数据文件保存在手边,因此能够保护index的样子看起来像搜索开始时的样子。

为何使用Elasticsearch Scroll?
当Elasticsearch响应请求时,它必须肯定docs的顺序,排列响应结果。
若是请求的页数较少(假设每页20个docs), Elasticsearch不会有什么问题,可是若是页数较大时,好比请求第20页,
Elasticsearch不得不取出第1页到第20页的全部docs,再去除第1页到第19页的docs,获得第20页的docs。
解决的方法就是使用Scroll。由于Elasticsearch要作一些操做(肯定以前页数的docs)为每一次请求.
因此,咱们可让Elasticsearch储存这些信息为以后的查询请求。
这样作的缺点是,咱们不能永远的储存这些信息,由于存储资源是有限的。
因此Elasticsearch中能够设定咱们须要存储这些信息的时长。
复制代码

scan(扫描)

深度分页代价最高的部分是对结果的全局排序,但若是禁用排序,就能以很低的代价得到所有返回结果。为达成这个目的,能够采用scan(扫描)搜索模式。扫描模式让Elasticsearch不排序,只要分片里还有结果能够返回,就返回一批结果。

为了使用scan-and-scroll(扫描和滚屏),须要执行一个搜索请求,将search_type 设置成scan,而且传递一个scroll参数来告诉Elasticsearch滚屏应该持续多长时间。

GET /old_index/_search?search_type=scan&scroll=1m (1)
{
    "query": { "match_all": {}},
    "size":  1000
}
复制代码

(1)保持滚屏开启1分钟。

这个请求的应答没有包含任何命中的结果,可是包含了一个Base-64编码的_scroll_id(滚屏id)字符串。如今咱们能够将_scroll_id传递给_search/scroll末端来获取第一批结果:

GET /_search/scroll?scroll=1m      (1)
c2Nhbjs1OzExODpRNV9aY1VyUVM4U0NMd2pjWlJ3YWlBOzExOTpRNV9aY1VyUVM4U0 <2>
NMd2pjWlJ3YWlBOzExNjpRNV9aY1VyUVM4U0NMd2pjWlJ3YWlBOzExNzpRNV9aY1Vy
UVM4U0NMd2pjWlJ3YWlBOzEyMDpRNV9aY1VyUVM4U0NMd2pjWlJ3YWlBOzE7dG90YW
xfaGl0czoxOw==
复制代码

(1) 保持滚屏开启另外一分钟。

(2) _scroll_id能够在body或者URL里传递,也能够被当作查询参数传递。

注意,要再次指定?scroll=1m。滚屏的终止时间会在咱们每次执行滚屏请求时刷新,因此他只须要给咱们足够的时间来处理当前批次的结果而不是全部的匹配查询的document。

这个滚屏请求的应答包含了第一批次的结果。虽然指定了一个1000的size ,可是得到了更多的document。当扫描时,size被应用到每个分片上,因此咱们在每一个批次里最多或得到size * number_of_primary_shards(size*主分片数)个document。

注意:

滚屏请求也会返回一个新的_scroll_id。每次作下一个滚屏请求时,必须传递前一次请求返回的_scroll_id。
复制代码

若是没有更多的命中结果返回,就处理完了全部的命中匹配的document。

一些Elasticsearch官方客户端提供扫描和滚屏的小助手。小助手提供了一个对这个功能的简单封装。
复制代码

参考:es权威指南

相关文章
相关标签/搜索