elasticsearnch分布式搜索

两个阶段:查询+取回

一. 查询阶段

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

GET /_search
{
    "from": 90,
    "size": 10
}

查询阶段包含以下三步

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

二. 取回阶段

步骤:

  1. 协调节点辨别出哪个document需要取回,并且向相关分片发出GET请求。
  2. 每个分片加载document并且根据需要丰富(enrich)它们,然后再将document返回协调节点。
  3. 一旦所有的document都被取回,协调节点会将结果返回给客户端。

协调节点先决定哪些document是实际(actually)需要取回的

{ "from": 90, "size": 10 }

前90条将会被丢弃,只有之后的10条会需要取回
协调节点为每个持有相关document的分片建立多点get请求然后发送请求到处理查询阶段的分片副本。

深分页
协调节点要通过对分片数量 * (from + size)个document进行排序来找到正确的size个document
如果from的值非常大,排序过程会变得非常繁重;
实际中,深度分页很少使用一般人会在翻了两三页后就停止翻页,并会更改搜索标准;
不正常的情况是爬虫,会持续不断地一页接着一页地获取页面直到服务器到崩溃的边缘;
如果需要从集群获取大量的ducuments,可以设置搜索类型为scan;

三. 搜索选项

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

_primary
_primary_first
_local
_only_node:xyz
_prefer_node:xyz
_shards:2,3

通常最有用的值是一些随机字符串,它们可以避免结果震荡(Bouncing Results)问题
比如按照timestamp字段排序,timestamp字段内容相同,每次搜索的结果不同。
避免这个问题方法是对于同一个用户总是使用同一个分片,方法就是使用一个随机字符串例如用户的会话ID(session ID)来设置preference参数。

timeout
协调节点等待所有的分片,如果有一个节点遇到问题,它会拖慢整个搜索请求;
timeout超时之后,将等待的这个分片放弃,结果会指出是否超时,多少个分片失败

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

如果一个分片的所有副本都失败了,可能是机器故障,也会反映在结果里

routing
建立索引是指定routing,可以保证所有相关的document存放在一个单独的分片中;
搜索时可以指定一个或多个routing值只搜索哪些分片,而不是搜索index里的全部分片

GET /_search?routing=user_1,user2

这个技术在设计非常大的搜索系统时就会派上用场了

search_type
query_then_fetch是默认的搜索类型,也可以指定其他的搜索类型,例如:

GET /_search?search_type=count

count(计数)
count(计数)搜索类型只有一个query(查询)的阶段。当不需要搜索结果只需要知道满足查询的document的数量时,可以使用这个查询类型。
query_and_fetch(查询并且取回)
query_and_fetch(查询并且取回)搜索类型将查询和取回阶段合并成一个步骤。这是一个内部优化选项,当搜索请求的目标只是一个分片时可以使用,例如指定了routing(路由选择)值时。
dfs_query_then_fetch 和 dfs_query_and_fetch
dfs搜索类型有一个预查询的阶段,它会从全部相关的分片里取回项目频数来计算全局的项目频数。我们将在relevance-is-broken(相关性被破坏)里进一步讨论这个。
scan(扫描)
scan(扫描)搜索类型是和scroll(滚屏)API连在一起使用的,可以高效地取回巨大数量的结果。它是通过禁用排序来实现的。我们将在下一节scan-and-scroll(扫描和滚屏)里讨论它。

四. 扫描和滚屏

scan和scroll一起使用,可以从Elasticsearch中高效的取回大量结果,不需要深分页;
scroll
一个滚屏搜索允许我们做一个初始阶段搜索并且持续批量从Elasticsearch里拉取结果直到没有结果剩下
scan
深度分页代价最高的部分是对结果的全局排序,但如果禁用排序,就能以很低的代价获得全部返回结果。
使用scan-and-scroll,需要执行一个搜索请求,将search_type 设置成scan,并且传递一个scroll参数来告诉Elasticsearch滚屏应该持续多长时间。

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

保持滚屏开启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.

posted @ 2016-09-26 17:30  zhangshihai1232  阅读(115)  评论(0)    收藏  举报