(五)ElasticSearch 分片深入研究

序言

  分片是 ES 中的一个重要概念,围绕着分片,我们会有很多疑问:

  • 文档是什么?
  • 分片是什么?
  • 文档与分片关系如何?
  • 分片的作用?
  • 文档存储在哪个分片?
  • 主副分片间如何交互?

  本文将通过一些说明来解答这些疑问。

分片与文档

  对 Elasticsearch 而言,其索引中的数据是被存储在分片(shards)上的。
  那么,什么是分片?
  分片,其实是数据(文档集合)拆分后的一部分。若拿关系型数据库来类比的话,则每个文档对应数据表中的一行,因此分片可类比为数据表的一行或多行。

  索引只是一个把一个(或多个)分片汇总在一起的逻辑空间,换而言之,一个索引是一系列分片的集合。

为什么需要分片?

  在 ES 中,N 个分片会被尽可能平均地分配在不同的节点上。

  分片有助于横向扩展,提高查询性能,副本分片可以带来高可用。

分片的动态调整

  若有 2 个节点,6 个主分片,那么每个节点会分到 3 个分片。若后续再增加 1 个节点,经过再平均分配后,3 个节点上都会有 2 个主分片。

文档存储到分片的过程

  当索引一个文档的时候,文档会被存储到一个主分片中。
  那么,Elasticsearch 如何知道一个文档应该存放到哪个分片中的呢?

  首先这肯定不会是随机的,否则将来要获取文档的时候就不知道从何处寻找了。
  实际上,这个过程是根据下面这个公式决定的:

1
shard = hash(routing) % number_of_primary_shards

  routing 是一个可变值,默认是文档的 _id ,值可自定义。
  routing 通过 hash 函数生成一个数字,然后此数字会再除以 number_of_primary_shards (主分片的数量)后得到 余数
  这个分布在 0number_of_primary_shards - 1 之间的余数,就定位了文档所要存储的分片。

  这就解释了为什么我们要在创建索引的时候就确定好主分片的数量并且永远不会改变这个数量:因为如果数量变化了,那么所有之前路由的值都会无效,文档也再也找不到了

决定存储分片的参数

  所有的文档 API( getindexdeletebulkupdate 以及 mget )都接受一个叫做 routing 的路由参数 ,通过这个参数我们可以自定义文档到具体存储分片的映射。

  一个自定义的路由参数可以用来确保所有相关的文档——例如所有属于同一个用户的文档——都被存储到同一个分片中。

主副分片间的交互

  我们知道,在 ElasticSearch 集群中,一般都将设置主分片和副本分片。
  那么,主分片和副本之间是如何进行交互的呢?
  为了说明目的,我们假设现有一个三个节点组成集群, 它:

  • 包含一个叫 blogs 的索引
  • 存在两个主分片,每个主分片有两个副本分片

  由于相同分片的副本不会放在同一节点,因此集群看起来如下图:

有三个节点和一个索引的集群

  ElasticSearch 集群中的任一节点都可以去接收发送来的请求,因为每个节点都有能力处理任意请求。
  为什么每个节点都有这种能力呢?
  由于每个节点都知道任一文档在集群中的位置,所以可以直接将请求转发到需要的节点上。
  对于接收到请求的节点,其角色作用在于协调,因此我们称其为协调节点(coordinating node)
  下面,我们来看看协调节点是如何协调处理分片的。

写操作下的分片交互

  在 ES 中,新建、索引和删除请求都是写操作, 必须在主分片上面完成写操作之后才能被复制到相关的副本分片,此过程如下图所示 :

新建、索引和删除单个文档

  写操作过程步骤说明如下:

  1. 客户端向 Node 1 发送新建、索引或者删除请求
  2. 节点使用文档的 _id 经路由计算确定文档属于分片 0 。请求会被转发到 Node 3,因为分片 0 的主分片目前被分配在 Node 3
  3. Node 3 首先在主分片上面执行请求。如果成功了,它会将请求并行转发Node 1Node 2 的副本分片上。一旦所有的副本分片都报告成功, Node 3 将向协调节点报告成功,协调节点向客户端报告成功

参数的影响

  有一些可选的请求参数允许可影响写操作过程,可能以数据安全为代价提升性能:

  • consistency:即一致性。在默认设置下,即使仅仅是在试图执行一个写操作之前,主分片都会要求必须要有规定数量(quorum)的分片副本处于活跃可用状态,才会去执行写操作(其中分片副本可以是主分片或者副本分片)。这是为了避免在发生网络分区故障(network partition)的时候进行写操作,进而导致数据不一致。consistency 参数的值可以设为:
    • one:只要主分片状态 ok 就允许执行写操作
    • all:必须要主分片和所有副本分片的状态没问题才允许执行写操作
    • quorum:默认值 , 即规定数量的分片副本状态没问题就允许执行写操作。注意,规定数量的计算公式(即:int( (primary + number_of_replicas) / 2 ) + 1)中 number_of_replicas 指的是在索引设置中的设定副本分片数,而不是指当前处理活动状态的副本分片数。若索引设置中指定了当前索引拥有三个副本分片,计算公式为:int( (primary + 3 replicas) / 2 ) + 1 = 3。如果此时只启动两个节点,那么处于活跃状态的分片副本数量就达不到规定数量,导致无法索引和删除任何文档。
  • timeout:若没有足够的副本分片 Elasticsearch 会等待,希望更多的分片出现。默认情况下此时间为 1 分钟, 若设置则规则为100 代表 100 毫秒,30s 代表 30 秒

  新索引默认有 1 个副本分片,这意味着为满足规定数量应该需要两个活动的分片副本。 但是,这些默认的设置会阻止我们在单一节点上做任何事情。为了避免这个问题,要求只有当 number_of_replicas 大于 1 的时候,规定数量才会执行。

读操作下的分片交互

  在 ES 中,可以从主分片或者从其它任意副本分片检索文档 ,如下图所示:
取回单个文档

  读操作过程步骤:

  1. 客户端向 Node 1 发送获取请求
  2. 节点使用文档的 _id 来确定文档属于分片 0 。分片 0 的副本分片存在于所有的三个节点上。 在这种情况下,它将请求转发到 Node 2 (协调结点在每次请求的时候都会通过轮询所有的副本分片来达到负载均衡)
  3. Node 2 将文档返回给 Node 1 ,然后将文档返回给客户端

局部更新文档时的分片交互

  update API 结合了先前说明的读取和写入模式,如下图所示:

局部更新文档

  以下是部分更新一个文档的步骤:

  1. 客户端向 Node 1 发送更新请求
  2. 它将请求转发到主分片所在的 Node 3
  3. Node 3 从主分片检索文档,修改 _source 字段中的 JSON ,并且尝试重新索引主分片的文档:
    • 若文档已被另一个进程修改,它会重试步骤 3 ,超过 retry_on_conflict 次后放弃
    • Node 3成功更新文档,它将新版本的文档并行转发到 Node 1Node 2 上的副本分片,重新建立索引。 一旦所有副本分片都返回成功, Node 3 向协调节点也返回成功,最后,协调节点向客户端返回成功。

  除此以外,update API 还接受在前面介绍的 routingreplicationconsistencytimeout 参数。

多文档模式下的分片交互

  mgetbulk API 的模式类似于单文档模式。区别在于协调节点知道每个文档存在于哪个分片中。 它将整个多文档请求分解成每个分片的多文档请求,并且将这些请求并行转发到每个参与节点。

  协调节点一旦收到来自每个节点的应答,就将每个节点的响应收集整理成单个响应,返回给客户端,如下图所示。

使用 `mget` 取回多个文档

  以下是使用单个 mget 请求取回多个文档所需的步骤顺序:

  1. 客户端向 Node 1 发送 mget 请求。
  2. Node 1 为每个分片构建多文档获取请求,然后并行转发这些请求到托管在每个所需的主分片或者副本分片的节点上。一旦收到所有答复, Node 1 构建响应并将其返回给客户端。

  bulk API, 如下图所示, 允许在单个批量请求中执行多个创建、索引、删除和更新请求。

“使用 `bulk` 修改多个文档”

  bulk API 按如下步骤顺序执行:

  1. 客户端向 Node 1 发送 bulk 请求。
  2. Node 1 为每个节点创建一个批量请求,并将这些请求并行转发到每个包含主分片的节点主机。
  3. 主分片一个接一个按顺序执行每个操作。当每个操作成功时,主分片并行转发新文档(或删除)到副本分片,然后执行下一个操作。 一旦所有的副本分片报告所有操作成功,该节点将向协调节点报告成功,协调节点将这些响应收集整理并返回给客户端。

  bulk API 还可以在整个批量请求的最顶层使用 consistency 参数,以及在每个请求中的元数据中使用 routing 参数。

参考

0%