当前位置: > > > Elasticsearch - 分片查询方式详解(偏好查询)

Elasticsearch - 分片查询方式详解(偏好查询)

    ES 中的索引数据最终都是存储在分片里面的,分片有多个,并且分片还分为主分片和副本分片。ES 在查询索引库中的数据时,具体是到哪些分片里面查询数据呢?本文将通过样例进行详细说明。

一、查询过程分析

1,分布式查询

    下图表示是一个 3 个节点的 ES 集群,集群内有一个索引库,索引库里面有 P0P1 两个主分片,这两个主分片分别都有 2 个副本分片,R0R1

2,查询步骤

(1)客户端发送一个查询请求到 Node 3Node 3 会创建一个空优先队列,主要为了存储结果数据。

(2)Node 3 将查询请求转发到索引的主分片或副本分片中。每个分片在本地执行查询并将查询到的结果添加到本地的有序优先队列中。
  • 具体这里面 Node 3 将查询请求转发到索引的哪个分片中,可以是随机的,也可以由我们程序员来控制。
  • 默认是 randomize across shards:表示随机从分片中取数据。

(3)每个分片返回各自优先队列中所有文档的 ID 和排序值给到 Node 3Node 3 合并这些值到自己的优先队列中来产生一个全局排序后的结果列表。
注意:当客户端的一个搜索请求被发送到某个节点时,这个节点就变成了协调节点。 这个节点的任务是广播查询请求到所有相关分片,并将它们查询到的结果整合成全局排序后的结果集合,这个结果集合会返回给客户端。 这里面的 Node3 节点其实就是协调节点。

二、设置分发规则

1,准备工作

(1)接下来我们来具体分析一下如何控制查询请求到分片之间的分发规则。我们先创建一个具有 5 个分片,0 个副本的索引库,分片太少不好验证效果。
curl -H "Content-Type: application/json" \
     -XPUT 'http://node1:9200/pre/' \
     -d '{
          "settings": {
              "number_of_shards": 5,
              "number_of_replicas": 0
          }
      }'

(2)在索引库中初始化一批测试数据:
curl -H "Content-Type: application/json" -XPOST 'http://node1:9200/pre/_doc/1' -d'{"name":"tom","age":18}'
curl -H "Content-Type: application/json" -XPOST 'http://node1:9200/pre/_doc/2' -d'{"name":"jack","age":29}'
curl -H "Content-Type: application/json" -XPOST 'http://node1:9200/pre/_doc/3' -d'{"name":"jessica","age":18}'
curl -H "Content-Type: application/json" -XPOST 'http://node1:9200/pre/_doc/4' -d'{"name":"dave","age":19}'
curl -H "Content-Type: application/json" -XPOST 'http://node1:9200/pre/_doc/5' -d'{"name":"lilei","age":18}'
curl -H "Content-Type: application/json" -XPOST 'http://node1:9200/pre/_doc/6' -d'{"name":"lili","age":29}'

(3)这些测试数据在索引库的分片中的分布情况是这样的:



(4)最后,在代码层面通过 preference(...) 来设置具体的分片查询方式:
//指定分片查询方式
searchRequest.preference();//默认随机

2,_local

(1)表示查询操作会优先在本地节点(协调节点)的分片中查询,没有的话再到其它节点中查询。
  • 这种方式可以提高查询性能,假设一个索引库有 5 个分片,这 5 个分片都在 Node3 节点里面,客户端的查询请求正好也分配到了 Node3 节点上,这样在查询这 5 个分片的数据就都在 Node3 节点上进行查询了,每个分片返回结果数据的时候就不需要跨网络传输数据了,可以节省网络传输的时间。
  • 但是这种方式也会有弊端,如果这个节点在某一时刻接收到的查询请求比较多的时候,会对当前节点造成比较大的压力,因为这些查询请求都会优先查询这个节点上的分片数据。

(2)下面代码我们使用该方式查询数据:
searchRequest.preference("_local");

(3)此时是可以查到所有数据的。
数据总数:6
{"name":"jessica","age":18}
{"name":"lilei","age":18}
{"name":"dave","age":19}
{"name":"jack","age":29}
{"name":"lili","age":29}
{"name":"tom","age":18}

3,_only_local

(1)_only_local 表示查询只会在本地节点的分片中查询。
searchRequest.preference("_only_local");

(2)这种方式只会在查询请求所在的节点上进行查询,查询速度比较快,但是数据可能不完整,因为我们无法保证索引库的分片正好都在这一个节点上。
数据总数:2
{"name":"dave","age":19}
{"name":"tom","age":18}

4,_only_nodes

(1)only_nodes 表示只在指定的节点中查询。可以控制只在指定的节点中查询某一个索引库的分片信息。
注意:
  • 这里指定的节点列表里面,必须包含指定索引库的所有分片,如果从这些节点列表中获取到的索引库的分片个数不完整,程序会报错。
  • 这种情况适用于在某种特殊情况下,集群中的个别节点压力比较大,短时间内又无法恢复,那么我们在查询的时候可以规避掉这些节点,只选择一些正常的节点进行查询。
  • 前提是索引库的分片有副本,如果没有副本,只有一个主分片,就算主分片的节点压力比较大,那也只能查询这个节点了。

(2)我们可以通过 ES 中针对节点信息的 RestAPI 可以来获取节点 ID。返回的信息有点多,我们可以在浏览器中访问 http://节点地址:9200/_nodes?pretty
  • 最终可以获取到三个节点的 ID。注意这个节点 ID 是集群随机生成的一个字符串,所以每个人的集群节点 ID 都是不一样的。
bigdata01: l7H4B3pdRm6ckBWd7ODS5Q
bigdata02: nS_RptvTQDuRYTplia24WA
bigdata03: KzjZauWGRt6hJRKTgZ7BcA
  • 目前这个索引库的分片分布在 3 个节点上:

(3)在这里可以指定一个或者多个节点 ID,多个节点 ID 之间使用逗号分割即可。首先指定 bigdata01 节点的 ID
searchRequest.preference("_only_nodes:KzjZauWGRt6hJRKTgZ7BcA");
  • 执行这个查询会报错,错误提示找不到 pre 索引库的 2 号分片的数据,2 号分片是在 bigdata03 节点上。
{"error":{"root_cause":[{"type":"illegal_argument_exception","reason":"no data nodes with criteria [l7H4B3pdRm6ckBWd7ODS5Q] found for shard: [pre][2]"}],"type":"illegal_argument_exception","reason":"no data nodes with criteria [l7H4B3pdRm6ckBWd7ODS5Q] found for shard: [pre][2]"},"status":400}

(4)再把 bigdata03 的节点 ID 添加到里面:
searchRequest.preference("_only_nodes:l7H4B3pdRm6ckBWd7ODS5Q,KzjZauWGRt6hJRKTgZ7BcA");
  • 执行发现还是会报错,提示找不到 pre 索引库的 3 号分片,3 号分片是在 bigdata02 节点上的。
{"error":{"root_cause":[{"type":"illegal_argument_exception","reason":"no data nodes with criterion [l7H4B3pdRm6ckBWd7ODS5Q,KzjZauWGRt6hJRKTgZ7BcA] found for shard: [pre][3]"}],"type":"illegal_argument_exception","reason":"no data nodes with criterion [l7H4B3pdRm6ckBWd7ODS5Q,KzjZauWGRt6hJRKTgZ7BcA] found for shard: [pre][3]"},"status":400}

(5)再把 bigdata02 的节点 ID 添加到里面
searchRequest.preference("_only_nodes:l7H4B3pdRm6ckBWd7ODS5Q,KzjZauWGRt6hJRKTgZ7BcA,nS_RptvTQDuRYTplia24WA");
  • 此时执行就可以正常执行了:
数据总数:6
{"name":"jessica","age":18}
{"name":"lilei","age":18}
{"name":"dave","age":19}
{"name":"jack","age":29}
{"name":"lili","age":29}
{"name":"tom","age":18}
注意:在 ES7.x 版本之前,_only_nodes 后面可以只指定某一个索引库部分分片所在的节点信息,如果不完整,不会报错,只是返回的数据是不完整的。

5,_prefer_nodes

(1)_prefer_nodes 表示优先在指定的节点上查询索引库分片中的数据。如果某个节点比较空闲,尽可能的多在这个节点上查询,减轻集群中其他节点的压力,尽可能实现负载均衡。

(2)这里可以指定一个或者多个节点:
searchRequest.preference("_prefer_nodes:l7H4B3pdRm6ckBWd7ODS5Q");

(3)最终可以查询到完整的结果:
最终可以查询到完整的结果:数据总数:6
{"name":"jessica","age":18}
{"name":"lilei","age":18}
{"name":"dave","age":19}
{"name":"jack","age":29}
{"name":"lili","age":29}
{"name":"tom","age":18}

6,_shards

(1)_shards 在查询的时候可以指定只查询索引库中指定分片中的数据,其实有点类似于 Hive 中的分区表的特性。
  • 如果我们提前已经知道需要查询的数据都在这个索引库的哪些分片里面,在这里提前指定对应分片编号,这样查询请求就只会到这些分片里面进行查询,这样可以提高查询效率,减轻集群压力。

(2)可以指定一个或者多个分片编号,分片编号是从 0 开始的。
searchRequest.preference("_shards:0,1");

(3)最终可以看到这两个分区里面的数据:
数据总数:3
{"name":"jessica","age":18}
{"name":"lilei","age":18}
{"name":"dave","age":19}

7,custom-string

(1)这种方式可以自定义一个参数,不能以下划线(_)开头。
  • 有时候我们希望多次查询使用索引库中相同的分片,因为分片会有副本,正常情况下如果不做控制,那么两次查询的时候使用的分片可能会不一样,第一次查询可能使用的是主分片,第二次查询可能使用的是副本分片。
  • 大家可能会有疑问,不管是主分片,还是副本分片,这些分片里面的数据是完全一样的,就算两次查询使用的不是相同分片又会有什么问题吗?
  • 会有问题的!如果 searchType 使用的是 QUERY_THEN_FETCH,此时分片里面的数据在计算打分依据的时候是根据当前节点里面的词频和文档频率,两次查询使用的分片不是同一个,这样就会导致在计算打分依据的时候使用的样本不一致,最终导致两次相同的查询条件返回的结果不一样。
  • 当然了,如果你使用的是 DFS_QUERY_THEN_FETCH 就不会有这个问题了,但是 DFS_QUERY_THEN_FETCH 对性能损耗会大一些,所以并不是所有情况下都使用这种 searchType
  • 通过自定义参数的设置,只要两次查询使用的自定义参数是同一个,这样就可以保证这两次查询使用的分片是一样的,那么这两次查询的结果肯定是一样的。

(2)下面命令我使用自定义参数 abc 进行查询:
searchRequest.preference("abc");//自定义参数

(3)查询到的结果还是完整的。
数据总数:6
{"name":"jessica","age":18}
{"name":"lilei","age":18}
{"name":"dave","age":19}
{"name":"jack","age":29}
{"name":"lili","age":29}
{"name":"tom","age":18}
评论0