Elasticsearch 分片和副本策略

10,874 阅读25分钟

Elasticsearch 基本概念

Cluster
集群,一个ES集群是由多个节点(Node)组成的,每个集群都有一个cluster name 作为标识
`cluster.name: 【elastic search cluster name】`
在同一网段下的Elastic search实例会通过cluster name 决定加入哪个集群下。
node
节点,一个ES实例就是一个node,一个机器可以有多个实例,所以并不是说一台机器就是一个node,大多数情况下,每个node运行在一个独立的环境或者虚拟机上。
index
索引,即一系列documents的集合
shard
1.分片,ES是分布式搜索引擎,每个索引有一个或多个分片,索引的数据被分配到各个分片上,相当于一桶水   用了N个杯子装
2.分片有助于横向扩展,N个分片会被尽可能平均地(rebalance)分配在不同的节点上(例如你有2个节点,4个主分片(不考虑备份),那么每个节点会分到2个分片,后来你增加了2个节点,那么你这4个节点上都会有1个分片,这个过程叫relocation,ES感知后自动完成)
3.分片是独立的,对于一个Search Request的行为,每个分片都会执行这个Request.另外
4.每个分片都是一个Lucene Index,所以一个分片只能存放 Integer.MAX_VALUE - 128 = 2,147,483,519个docs。
replica
1.复制,可以理解为备份分片,相应地有primary shard(主分片)
2.主分片和备分片不会出现在同一个节点上(防止单点故障),默认情况下一个索引创建5个分片一个备份(即5primary+5replica=10个分片)
3.如果你只有一个节点,那么5个replica都无法分配(unassigned),此时cluster status会变成Yellow。replica的作用主要包括:

分片策略

1.1分布式架构的透明隐藏属性

Elasticsearch是一个分布式架构,隐藏了复杂的处理机制
分片机制
我们不用关心数据是按照什么机制分片的,最后放到哪个分片中
分片的副本
为了提升访问压力过大是单机无法处理所有请求的问题,Elasticsearch集群引入了副本策略replica。副本策略对index中的每个分片创建冗余的副本,处理查询时可以把这些副本当做主分片来对待(primary shard),此外副本策略提供了高可用和数据安全的保障,当分片所在的机器宕机,Elasticsearch可以使用其副本进行恢复,从而避免数据丢失。
集群发现机制(cluster discovery)
比如当前我们启动了一个es进程,当启动了第二个es进程是,这个进程作为一个node自动就发现了集群,并加入进去。

Shard负载均衡:比如现在有10个shard,集群中有3个节点,es会均衡的进行分配,以保证每个节点均衡的负载请求,

请求路由:当索引一个文档的时候,文档会被存储到一个主分片中。 Elasticsearch 如何知道一个文档应该存放到哪个分片中呢?当我们创建文档时,它如何决定这个文档应当被存储在分片 1 还是分片 2 中呢?

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

`shard = hash(routing) % number_of_primary_shards`

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

扩容机制

垂直扩容
采购更强大的服务器
水平扩容
采购越来越多的普通服务器,性能比较一般,但是很多普通服务器组织在一起,就能构成强大的计算和存储能力

如何选择扩容方式
es一般采用方式二水平扩容的方式进行扩容。从成本上来说,内存容量小,并且性能相对较低的服务器相比较与内存容量大,性能好的服务器,在价格上的差距不是一个量级的。
从另一方面来说,elasticSearch是一套分布式的系统,分布式的存在也是为存放大量的数据。讲到elasticSearch的扩容,自然就会想到shard和replica shard。
elasticSearch拥有cluster discovery(集群发现机制),当我们启动一个新的节点,该节点会发现集群并且自动加入到集群中。并且es集群会自动进行各个shard之间的数据均衡处理。
并且当节减少时,es集群也会自动将减少的节点中的数据移到其他正在运行的节点中。
所以elasticSearch一般选择水平扩容的方式。
超出扩容极限进行扩容
现在有6个shard,3个primary shard 3个replica shard ,6个shard存放在6台服务器中,如何进行扩容,扩容到9台服务器中?

因为primary shard在索引创建后就无法进行修改,所以需要将6台服务器扩容到9台服务器只能对replica shard进行增加,可以修改索引配置,将replica shard的数量修改为2,此时replica shard的数量变为6个,加上3个 primary shard 就是9个 shard
Rebalance:自动均衡:
增加节点时会自动均衡
创建一个index
`PUT 60.205.176.135:9200/product
{
"settings" : {
    "index" : {
        "number_of_shards" : 5, 
        "number_of_replicas" : 1 
    }
}

} ` 当只有一个节点时:

再启动一个节点:
启动第三个节点:

Master节点
(1)创建或删除索引
(2)增加或删除节点
主节点的主要职责是和集群操作相关的内容,如创建或删除索引,跟踪哪些节点是集群的一部分,并决定哪些分片分配给相关的节点,稳定的主节点对集群的健康是非常重要的
节点对等
(1)节点对等,每个节点都能接收所有的请求
(2)自动请求路由
(3)响应收集
每个节点都能接收请求,每个节点接收到请求后都能把该请求路由到有相关数据的其他节点上,接收原始请求的节点负责采集数据并返回给客户端
分片和副本机制
1.一个index中包含多个shard
2.每个shard都是一个最小工作单元,承载部分数据;每个shard都是一个Lucene实例,有完整的建立索引和处理请求的能力
3.增减节点时,shard会自动在nodes中负载均衡
4.primary shard 和 relica shard,每个document只会存在于某一个primary shard以及其对应的replica shard中,不可能存在于多个primary shard中
5.replica shard 是primary shard 的副本,负责容错,以及承担读请求的负载均衡
6.primary shard在创建索引的时候就固定了,relica shard的数量可以随时更改
7.primary shard 和 它对应的replica shard 不能放在同一台机器上,不然起不了容错的作用

shard&replica机制梳理总结

1. index包含多个shard
2. 每个shard都是一个最小工作单元,承载部分数据,lucene实例,完整的建立索引和处理请求的能力
3. 增减节点时,shard会自动在nodes中负载均衡
4. primary shard和replica shard,每个document肯定只存在于某一个primary shard以及其对应的replica   shard中,不可能存在于多个primary shard
5. replica shard是primary shard的副本,负责容错,以及承担读请求负载
6. primary shard的数量在创建索引的时候就固定了,replica shard的数量可以随时修改
7. primary shard的默认数量是5,replica默认是1,默认有10个shard,5个primary shard,5个replica shard
8. primary shard不能和自己的replica shard放在同一个节点上(否则节点宕机,primary shard和副本都丢失,起不到容错的作用),但是可以和其他primary shard的replica shard放在同一个节点上,因此,这个index是由6个shard组成的

多shard 下是如何进行查询

一个 CRUD 操作只对单个文档进行处理,文档的唯一性由 _index, _type, 和 routing values (通常默认是该文档的 _id )的组合来确定。 这表示我们确切的知道集群中哪个分片含有此文档。

搜索需要一种更加复杂的执行模型因为我们不知道查询会命中哪些文档: 这些文档有可能在集群的任何分片上。 一个搜索请求必须询问我们关注的索引(index or indices)的所有分片的某个副本来确定它们是否含有任何匹配的文档。

但是找到所有的匹配文档仅仅完成事情的一半。 在 search 接口返回一个 page 结果之前,多分片中的结果必须组合成单个排序列表。 为此,搜索被执行成一个两阶段过程,我们称之为 query then fetch 
查询阶段
查询阶段包含以下三个步骤:

客户端发送一个 search 请求到 Node 3 , Node 3 会创建一个大小为 from + size 的空优先队列。
Node 3 将查询请求转发到索引的每个主分片或副本分片中。每个分片在本地执行查询并添加结果到大小为 from + size 的本地有序优先队列中。
每个分片返回各自优先队列中所有文档的 ID 和排序值给协调节点,也就是 Node 3 ,它合并这些值到自己的优先队列中来产生一个全局排序后的结果列表。
当一个搜索请求被发送到某个节点时,这个节点就变成了协调节点。 这个节点的任务是广播查询请求到所有相关分片并将它们的响应整合成全局排序后的结果集合,这个结果集合会返回给客户端。

第一步是广播请求到索引中每一个节点的分片拷贝。就像 document GET requests 所描述的, 查询请求可以被某个主分片或某个副本分片处理, 这就是为什么更多的副本(当结合更多的硬件)能够增加搜索吞吐率。 协调节点将在之后的请求中轮询所有的分片拷贝来分摊负载。

每个分片在本地执行查询请求并且创建一个长度为 from + size 的优先队列—也就是说,每个分片创建的结果集足够大,均可以满足全局的搜索请求。 分片返回一个轻量级的结果列表到协调节点,它仅包含文档 ID 集合以及任何排序需要用到的值,例如 _score 。

协调节点将这些分片级的结果合并到自己的有序优先队列里,它代表了全局排序结果集合。至此查询过程结束。
获取结果阶段

分布式阶段由以下步骤构成:
协调节点辨别出哪些文档需要被取回并向相关的分片提交多个 GET 请求
每个分片加载并 丰富 文档,如果有需要的话,接着返回文档给协调节点。
一旦所有的文档都被取回了,协调节点返回结果给客户端。
协调节点首先决定哪些文档 确实 需要被取回。例如,如果我们的查询指定了 { "from": 90, "size": 10 } ,最初的90个结果会被丢弃,只有从第91个开始的10个结果需要被取回。这些文档可能来自和最初搜索请求有关的一个、多个甚至全部分片。

协调节点给持有相关文档的每个分片创建一个 multi-get request ,并发送请求给同样处理查询阶段的分片副本。

分片加载文档体-- _source 字段--如果有需要,用元数据和 search snippet highlighting 丰富结果文档。 一旦协调节点接收到所有的结果文档,它就组装这些结果为单个响应返回给客户端。

单节点创建index

  1. 单node环境下,创建一个index,有3个primary shard,3个replica shard
  2. 集群status是yellow
  3. 这个时候,只会将3个primary shard分配到仅有的一个node上去,另外3个replica shard是无法分配的
  4. 集群可以正常工作,但是一旦出现节点宕机,数据全部丢失,而且集群不可用,无法承接任何请求

PUT /test_index { "settings" : { "number_of_shards" : 3, "number_of_replicas" : 1 } }

2个node创建index

  1. replica shard分配:3个primary shard,3个replica shard,1 node
  2. primary ---> replica同步

副本策略

副本原理

es为了更好地稳定性和容灾,可以对分片进行复制和数据同步形成副本,副本的添加可以更好地维持集群数据完整性。副本分片与主分片做着相同的工作。

在索引写入时,副本分片做着与主分片相同的工作。新文档首先被索引进主分片然后再同步到其它所有的副本分片。增加副本数并不会增加索引容量,但是可以通过副本的增加引入新的硬件能力,提升查询能力。

副本的作用

  • 故障转移/集群恢复

    如果持有主分片的节点挂了,一个副本分片就会晋升为主分片 在索引写入时,副本分片做着与主分片相同的工作。新文档首先被索引进主分片然后再同步到其它所有的副本分片。增加副本数并不会增加索引容量。

  • 通过副本进行负载均衡

    搜索性能取决于最慢的节点的响应时间,所以尝试均衡所有节点的负载是一个好想法。 如果我们只是增加一个节点而不是两个,最终我们会有两个节点各持有一个分片,而另一个持有两个分片做着两倍的工作。

我们可以通过调整副本数量来平衡这些。通过分配两份副本而不是一个,最终我们会拥有六个分片,刚好可以平均分给三个节点

主副本之间的区别

Master节点的特殊性 ES中有一项工作是Master独有的:维护集群状态。集群状态信息,只由Master节点进行维护,并且同步到集群中所有节点,其他节点只负责接收从Master同步过来的集群信息而没有维护的权利。集群状态包括以下信息:

  • 集群层面的配置
  • 集群内有哪些节点
  • 各索引的设置,映射,分析器和别名等
  • 索引内各分片所在的节点位置

【思维拓展】ES集群中的每个节点都会存储集群状态,知道索引内各分片所在的节点位置,因此在整个集群中的任意节点都可以知道一条数据该往哪个节点分片上存储。反之也知道该去哪个分片读。所以,Elasticsearch不需要将读写请求发送到Master节点,任何节点都可以作为数据读写的切入点对请求进行响应。这样进一步减轻了Master节点的网络压力,同时提高了集群的整体路由性能。

副本数量的选定原则

分布式模式

模式 代表组件 优点 缺点
主从模式 ES/HDFS/HBase 简化系统设计,Master作为权威节点,负责维护集群原信息。 Master节点存在单点故障,需要解决在被问题,并且集群规模会受限于Master节点的管理能力。
无主模式 Cassandra 分布式哈希表(DHT),支持每小时数千个节点的离开和加入。集群没有master的概念,所有节点都是同样的角色,彻底避免了整个系统的单点问题导致的不稳定性。 多个节点可能操作同一条数据,数据一致性上可能比较难以保证。

为什么使用 Master

Elasticsearch的典型场景中的另一个简化是集群中没有那么多节点。 通常,节点的数量远远小于单个节点能够维护的连接数,并且网格环境不必经常处理节点加入和离开。 这就是为什么领导者的做法更适合Elasticsearch。

流程概述

  1. 每个节点计算最低的已知节点ID,并向该节点发送领导投票。
  2. 如果一个节点收到足够多的票数,并且该节点也为自己投票,那么它将扮演领导者的角色,开始发布集群状态。
  3. 所有节点都会参数选举,并参与投票,但是,只有有资格成为 master 的节点的投票才有效。

整体流程可以概括为:选举临时Master,如果本节点当选,等待确立Master,如果其他节点当选,等待加入Master,然后启动节点失效探测器。

选主详情

  1. 触发选主:进入选举临时的Master之前,参选的节点数需要达到法定人数。
  2. 决定Master:选出临时的Master之后,得票数需要达到法定人数,才确认选主成功。
  3. gateway选举元信息:向有Master资格的节点发起请求,获取元数据,获取的响应数量必须达到法定人数,也就是参与元信息选举的节点数。
  4. Master发布集群状态:成功向节点发布集群状态信息的数量要达到法定人数。
  5. NodesFaultDetection事件中是否触发rejoin:当发现有节点连不上时,会执行removeNode。接着审视此时的法定人数是否达标(discovery.zen.minimum_master_nodes),不达标就主动放弃Master身份执行rejoin以避免脑裂。 Master扩容场景:目前有3个master_eligible_nodes,可以配置quorum为2。如果将master_eligible_nodes扩容到4个,那么quorum就要提高到3。此时需要先把discovery.zen.minimum_master_nodes配置设置为3,再扩容Master节点。这个配置可以动态设置: PUT /_cluster/settings { “persistent”: { “discovery.zen.minimum_master_nodes”: 3 } } Master减容场景:缩容与扩容是完全相反的流程,需要先缩减Master节点,再把quorum数降低。 修改Master以及集群相关的配置一定要非常谨慎!配置错误很有可能导致脑裂,甚至数据写坏、数据丢失等场景。 注意:最新版本ES 7已经移除minimum_master_nodes配置,让Elasticsearch自己选择可以形成仲裁的节点。
  • 防止脑裂、防止数据丢失的极其重要的参数: discovery.zen.minimum_master_nodes=(master_eligible_nodes)/2+1

脑裂是一个集群分裂成两个集群的现象,出现了两个Master。这里如果要避免脑裂要配置discovery.zen.minimum_master_nodes=N/2+1。这个配置的意思是说在选举Master的过程中,需要多少个节点通信,说白点就是票数。如果达不到N/2+1,就是超过半数,就会选举失败,重新选举。 如下图:

节点失效检测

选主流程已执行完毕,Master身份已确认,非Master节 点已加入集群。 节点失效检测会监控节点是否离线,然后处理其中的异常。失效检 测是选主流程之后不可或缺的步骤,不执行失效检测可能会产生脑裂 (双主或多主)。

我们需要启动两种失效探测器:

  • 在Master节点,启动NodesFaultDetection,简称NodesFD。定期探 测加入集群的节点是否活跃。
  • 在非Master节点启动MasterFaultDetection,简称MasterFD。定期探 测Master节点是否活跃。

NodesFaultDetection和MasterFaultDetection都是通过定期(默认为1秒)发送的ping请求探测节点是否正常的,当失败达到一定次数(默认 为3次),或者收到来自底层连接模块的节点离线通知时,开始处理节点离开事件。

NodesFaultDetection

检查当前集群总节点数是否达到法定节点数(过半),如果不 足,则会放弃 Master 身份,重新加入集群。为什么要这么做?设想下 面的场景,如下图所示。

假设有5台机器组成的集群产生网络分区,2台组成一组,另外3台 组成一组,产生分区前,原Master为Node1。此时3台一组的节点会重新 选举并成功选取Noded3作为Master,会不会产生双主? NodesFaultDetection就是为了避免上述场景下产生双主。

主节点在探测到节点离线的事件处理中,如果发现当前集群节点数 量不足法定人数,则放弃Master身份,从而避免产生双主

MasterFaultDetection

如果master节点网络失联,尝试再次连接。

如何触发选主

  • 集群启动
  • Master 失效 非 Master 节点运行的 MasterFaultDetection 检测到 Master 失效,在其注册的 listener 中执行 handleMasterGone,执行 rejoin 操作,重新选主.注意,即使一个节点认为 Master 失效也会进入选主流程

主分片数据同步到副本的过程描述

新建、索号| (这里的索 引是动词,指写入操作,将文档添加到 Lucene 的过程称为索引 一个 文档)和删除请求都是写操作 。写操作必须先在主分片执行成功后才能复制到相关的副分片 。

写单个文档的流程(图片来自官网)如下图所示。

以下是写单个文档所需的步骤: (1 )客户端向 NODE I 发送写请求。

(2) NODEI 使用文档 ID 来确定文档属于分片 0 ,通过集群状态中的内容路由表信息获知

分片 0 的主分片位于 NODE3 ,因此请求被转发到 NODE3 上。 (3 ) NODE3 上的主分片执行写操作 。 如果写入成功,则它将请求并行转发到 NODE I 和 NODE2 的副分片上,等待返回结果 。当所有的副分片都报告成功, NODE3 将向协调节点报告 成功,协调节点再向客户端报告成功 。

在客户端收到成功响应时 ,意味着写操作已经在主分片和所有副分片都执行完成。

写一致性的默认策略是 quorum,即多数的分片(其中分片副本可以是主分片或副分片)在 写入操作时处于可用状态。

1.写一致性原理

我们在发送任何一个增删改操作的时候,比如说 put /index/type/id,都可以带上一个consistency参数,指明我们想要的写一致性是什么。

PUT /index/type/id?consistency=quorum

one:要求我们这个写操作,只要primary shard是active活跃可用的,就可以执行写操作

all:要求我们这个写操作,必须所有的shard都是活跃的,才能执行写操作

quorum:也是consistency默认值,要求所有的shard中,必须是大部分的shard都是活跃的,才能执行写操作

2.quorum机制,写之前必须确保大多数shard都可用,int((primary + number_of_replica)/2) + 1,当number_of_replica>1时才生效

举例说明:

3个primary shard,number_of_replica=1,总共有3+3*1 = 6个shard

quorum = (int(3+1)/2 ) + 1 = 3 ,所以要求6个shard中必须至少有3个shard是active状态的时候,才能执行写操作。

3.如果节点数小于quorum数量,可能导致quorum不齐全,进而导致无法执行任何写操作

举例:3个primary shard,replica=1,要求至少有3个shard是active状态的,3个shard按照之前的学习,shard&replica机制,必须在不同的节点上,如果说只有2台机器的话,是不是有可能出现,3个shard都没法分配齐全,此时就可能会出现写操作无法执行的情况。

es提供了一种特殊的场景,就是说当number_of_replicas>1时才生效,因为假如说,你就一个primary shard,replica=1,此时就2个shard

(1+1)/2+1 = 2,要求必须有2个shard是活跃的,但是可能就1个node,此时就1个shard是活跃的,如果你不特殊处理的话,单只我们的单节点集群就无法工作

补充说明:

1个primary shard,replica=3,quorum=((1+3)/2)+1 = 3,要求1个primary shard + 3个replica shard = 4个shard,其中必须有3个shard是要处于active状态的,如果这个时候只有2台机器的话,会出现什么情况呢?

此时P0,R0-0,R0-1,R0-2,并且只有两个node节点时,因为primary shard和replica shard不能在一个节点上,所以最多只能分配两个shard到两个node上,

此时的shard数小于quorum的要求,所以不满足条件,es默认等待,等待新的shard增加,直到timeout

4.quorum不齐全时,wait,默认1分钟,timeout,100,30s

如果quorum不齐全,es默认等待,期望活跃的shard数量可以增加,最后实在不行,就会timeout

我们其实可以在写操作的时候,加一个timeout参数,比如说 put /index/type/id?timeout=30,这个参数就是说自己去设定quorum不齐全的时候,es的timeout时长,可以缩短,也可以增长。timeout的单位默认为ms,如果希望为s,那么写成?timeout=30s

索引恢复

副分片恢复的核心思想是从主分片拉取 Lucene 分段和 translog 进行恢复 。 按数据传递的方向,主分片节点称为 Source ,副分片节点称为 Target。

为什么需要拉取主分片 的 translog ?因为在副分片恢复期间允许新的 写操作,从复制 Lucene

分段的那 一刻开始,所恢 复 的副分片数据不包括新增的内容,而这 些内容存在于主分片的 translog 中,因此副分片需要从主分片节点拉取 translog 进行重放,以获取新增内容 。 这就需要 主分片节 点 的 translog 不被清理。

在 2.0 版本之前,副分片恢复要经历三个阶段 。 phase 1: 将主分片的 Lucene 做快照,发送到 target 。期间不阻塞索引操作,新增数据 写到主分片的 translog 。

phase2 :将主分片 translog 做快照,发送到 target 重放,期间不阻塞索引操作 。

phase3 :为主分片加写锁,将剩余的 translog 发送到 target。 此时数据量很小,写入过程的阻塞很短 。 从理论上来说,只要流程上允许将写操作阻塞一段时间,实现主副一致是比较容易的 。

但是后来(从 2.0 版本开始),也就是引入 translog.view 概念的同时, phase3 被删除 。 phase3 被删除,这个阶段是重放操作( operations ),同时防止新的写入 Engine 。 这是不必 要的,因为自恢复开始,标准的 index 操作会发送所有的操作到正在恢复中的分片。 重放恢复 开始时获取的 view 中的所有操作足够保证不丢失任何操作 。

阻塞写操作的 phase3 被删除,恢复期间没有任何写 阻塞过程 。 接下来需要处理的就是解决 phase !和 phase2 之间的写操作与 phase2 重放操作之间的时序和冲突问题。在副分片节点, phase! 结束后,假如新增索引操作和 translog 重放操作并发执行,因为时序的关系会出现新老数据交 替 。 如何实现主副分片一致呢?

假设在第一阶段执行期间,有客户端索引操作要求将 docA 的内容写为 l , 主分片执行了这个操作,而副分片由于尚未就绪所以没有执行 。 第二阶段期间客户端索引操作要求写 docA 的 内容为 2 ,此时副分片已经就绪,先执行将 docA 写为 2 的新增请求,然后又收到了从主分片所 在节点发送过来的 translog 重复写 docA 为 1 的请求该如何处理?具体流程如下图所示 。

答案是在写流程中做异常处理 , 通过版本号来过滤掉过期操作 。 写操作有三种类型 : 索引 新文档 、 更新、删除 。 索引新文挡不存在冲突问题,更新和删除操作采用相同的处理机制。每 个操作都有一个版本号,这个版本号就是预期 doc 版本,它必须大于当前 Lucene 中的 doc 版本 号 , 否则 就放弃本次操作。对于更新操作来说, 预期版本号是 Lucene doc 版本号十1 。主分片节 点写成功后新数据的版本号会放到写副本的请求中 , 这个请求中的版本号就是预期版本号。

这样,时序上存在错误的操作被忽略,对于特定 doc ,只有最新一次操作生效,保证了 主 副分片一致 。

我们分别看一下写操作三种类型的处理机制。

  1. 索引新文档

不存在冲突 问题,不需要处理 。

  1. 更新 判断本次操作的版本号是否小于 Lucene 中 doc 的版本号,如果小于,则放弃本次操作 。

  2. 删除 判断本次操作 中的版本号是否 小于 Lucene 中 doc 的版本号 ,如果小于, 则放弃本次操作 。