1、基本介绍
1.1、为什么需要集群
单台 Elasticsearch 服务器提供服务,往往都有最大的负载能力,超过这个阈值,服务器性能就会大大降低甚至不可用,所以生产环境中,ES 一般都是运行在指定服务器集群中。
除了负载能力,单点服务器也存在其他问题:
1)单台机器存储容量有限
2)单服务器容易出现单点故障,无法实现高可用
3)单服务的并发处理能力有限
配置服务器集群时,集群中节点数量没有限制,大于等于 2 个节点就可以看做是集群了。一般出于高性能及高可用方面来考虑集群中节点数量都是 3 个以上。
1.2、集群 Cluster
一个集群就是由一个或多个服务器节点组织在一起,共同持有整个的数据,并一起提供索引和搜索功能。一个 Elasticsearch 集群有一个唯一的名字标识,这个名字默认就是”elasticsearch”。这个名字是重要的,因为一个节点只能通过指定某个集群的名字,来加入这个集群。
1.3、节点 Node
集群中包含很多服务器,一般来说,一台服务器上只部署一个节点(当然实际上一台服务器上也是可以部署多个节点的)。节点作为集群的一部分,它存储数据,参与集群的索引和搜索功能。
一个节点也是由一个名字来标识的,默认情况下,这个名字是一个随机的漫威漫画角色的名字,这个名字会在启动的时候赋予节点。这个名字对于管理工作来说挺重要的,因为在这个管理过程中,你会去确定网络中的哪些服务器对应于 Elasticsearch 集群中的哪些节点。
一个节点可以通过配置集群名称的方式来加入一个指定的集群。默认情况下,每个节点都会被安排加入到一个叫做“elasticsearch”的集群中,这意味着,如果你在你的网络中启动了若干个节点,并假定它们能够相互发现彼此,它们将会自动地形成并加入到一个叫做“elasticsearch”的集群中。
在一个集群里,只要你想,可以拥有任意多个节点。而且,如果当前你的网络中没有运行任何 Elasticsearch 节点,这时启动一个节点,会默认创建并加入一个叫做“elasticsearch”的集群。
1.4、节点角色
一个节点是逻辑上独立的ES实例,表现为一个ES进程,它是集群的一部分。节点可分为多种角色,如主节点、数据节点、协调节点等。
1.4.1、主节点
负责集群状态管理,分片分配管理
1.4.2、数据节点
负责保存数据,执行数据增、删、改、查等操作。
1.4.3、协调节点
接收客户端请求,然后转发这些请求,收集数据并返回给客户端的节点。
2、window系统部署ES集群
2.1、安装启动
下载window版es软件并解压,复制三份,如下:
修改集群文件目录中每个节点的 config/elasticsearch.yml 配置文件node-1001 节点
node-1001 节点
#节点 1 的配置信息:
#集群名称,节点之间要保持一致
cluster.name: my-elasticsearch
#节点名称,集群内要唯一
node.name: node-1001
node.master: true
node.data: true
#ip 地址
network.host: localhost
#http 端口
http.port: 1001
#tcp 监听端口
transport.tcp.port: 9301
#discovery.seed_hosts: ["localhost:9301", "localhost:9302","localhost:9303"]
#discovery.zen.fd.ping_timeout: 1m
#discovery.zen.fd.ping_retries: 5
#集群内的可以被选为主节点的节点列表
#cluster.initial_master_nodes: ["node-1", "node-2","node-3"]
#跨域配置
#action.destructive_requires_name: true
http.cors.enabled: true
http.cors.allow-origin: "*"
- node-1002 节点
#节点 2 的配置信息:
#集群名称,节点之间要保持一致
cluster.name: my-elasticsearch
#节点名称,集群内要唯一
node.name: node-1002
node.master: true
node.data: true
#ip 地址
network.host: localhost
#http 端口
http.port: 1002
#tcp 监听端口
transport.tcp.port: 9302
discovery.seed_hosts: ["localhost:9301"]
discovery.zen.fd.ping_timeout: 1m
discovery.zen.fd.ping_retries: 5
#集群内的可以被选为主节点的节点列表
#cluster.initial_master_nodes: ["node-1", "node-2","node-3"]
#跨域配置
#action.destructive_requires_name: true
http.cors.enabled: true
http.cors.allow-origin: "*"
- node-1003 节点
#节点 3 的配置信息:
#集群名称,节点之间要保持一致
cluster.name: my-elasticsearch
#节点名称,集群内要唯一
node.name: node-1003
node.master: true
node.data: true
#ip 地址
network.host: localhost
#http 端口
http.port: 1003
#tcp 监听端口
transport.tcp.port: 9303
#候选主节点的地址,在开启服务后可以被选为主节点
discovery.seed_hosts: ["localhost:9301", "localhost:9302"]
discovery.zen.fd.ping_timeout: 1m
discovery.zen.fd.ping_retries: 5
#集群内的可以被选为主节点的节点列表
#cluster.initial_master_nodes: ["node-1", "node-2","node-3"]
#跨域配置
#action.destructive_requires_name: true
http.cors.enabled: true
http.cors.allow-origin: "*"
然后分别启动 node-1001、node-1002、node-1003 节点,启动前先删除每个节点中的 data 目录中所有内容(如果存在),启动后,节点会自动加入指定名称的集群。
2.2、查看集群健康状态
可通过 get 请求 http://IP:节点端口号/_cluster/health 查看集群状态,如:http://127.0.0.1:1002/_cluster/health。
当启动 1、2个节点时输出分别如下:
status字段指示着当前集群在总体上是否工作正常,它返回的三种颜色含义如下:
1)green:所有的主分片和副本分片都正常运行。
2)yellow:所有的主分片都正常运行,但不是所有的副本分片都正常运行。
3)red:有主分片没能正常运行。
3、Linux系统部署ES集群
Linux 系统上部署 es 集群跟单节点部署没什么差别,只是配置文件有点不一样而已。
3.1、安装启动
先下载 Linux 的 7.8.0 版本使用。下载地址:Elasticsearch 7.8.0 | Elastic
将下载的软件放到分别放到两台服务中并解压缩:tar -zxvf elasticsearch-7.8.0-linux-x86_64.tar.gz -C /opt/es-cluster
因为安全问题,Elasticsearch 不允许 root 用户直接运行,需使用其他用户启动 es,这里我们创建新用户 es。在 root 用户中创建新用户如下:
useradd es #新增 es 用户
passwd es #为 es 用户设置密码
userdel -r es #如果错了,可以删除再加
chown -R es:es /opt/es-cluster/elasticsearch-7.8.0 #文件夹所有者
修改完目录所有者后可以看到目录的拥有者发生了变化,如下:
修改/opt/es-cluster/elasticsearch-7.8.0/config/elasticsearch.yml 文件,如下:
192.168.118.130 主节点配置:
# 加入如下配置
#集群名称
cluster.name: cluster-es
#节点名称,每个节点的名称不能重复
node.name: node-1
#ip 地址,每个节点的地址不能重复
network.host: 192.168.118.130
#是不是有资格主节点
node.master: true
node.data: true
http.port: 9200
# head 插件需要这打开这两个配置
http.cors.allow-origin: "*"
http.cors.enabled: true
http.max_content_length: 200mb
#es7.x 之后新增的配置,初始化一个新的集群时需要此配置来选举 master
cluster.initial_master_nodes: ["node-1"]
#es7.x 之后新增的配置,节点发现
discovery.seed_hosts: ["192.168.118.130:9300","192.168.118.131:9300"]
gateway.recover_after_nodes: 2
network.tcp.keep_alive: true
network.tcp.no_delay: true
transport.tcp.compress: true
#集群内同时启动的数据任务个数,默认是 2 个
cluster.routing.allocation.cluster_concurrent_rebalance: 16
#添加或删除节点及负载均衡时并发恢复的线程个数,默认 4 个
cluster.routing.allocation.node_concurrent_recoveries: 16
#初始化数据恢复时,并发恢复线程的个数,默认 4 个
cluster.routing.allocation.node_initial_primaries_recoveries: 16
192.168.118.131 节点配置:
# 加入如下配置
#集群名称
cluster.name: cluster-es
#节点名称,每个节点的名称不能重复
node.name: node-2
#ip 地址,每个节点的地址不能重复
network.host: 192.168.118.131
#是不是有资格主节点
node.master: true
node.data: true
http.port: 9200
# head 插件需要这打开这两个配置
http.cors.allow-origin: "*"
http.cors.enabled: true
http.max_content_length: 200mb
#es7.x 之后新增的配置,初始化一个新的集群时需要此配置来选举 master
cluster.initial_master_nodes: ["node-1"]
#es7.x 之后新增的配置,节点发现
discovery.seed_hosts: ["192.168.118.130:9300","192.168.118.131:9300"]
gateway.recover_after_nodes: 2
network.tcp.keep_alive: true
network.tcp.no_delay: true
transport.tcp.compress: true
#集群内同时启动的数据任务个数,默认是 2 个
cluster.routing.allocation.cluster_concurrent_rebalance: 16
#添加或删除节点及负载均衡时并发恢复的线程个数,默认 4 个
cluster.routing.allocation.node_concurrent_recoveries: 16
#初始化数据恢复时,并发恢复线程的个数,默认 4 个
cluster.routing.allocation.node_initial_primaries_recoveries: 16
修改Linux系统的 /etc/security/limits.conf 文件
# 在文件末尾中增加下面内容
# 每个进程可以打开的文件数的限制
es soft nofile 65536
es hard nofile 65536
修改/etc/security/limits.d/20-nproc.conf
# 在文件末尾中增加下面内容
# 每个进程可以打开的文件数的限制
es soft nofile 65536
es hard nofile 65536
# 操作系统级别对每个用户创建的进程数的限制
* hard nproc 4096
# 注:* 带表 Linux 所有用户名称
修改/etc/sysctl.conf
# 在文件中增加下面内容
# 一个进程可以拥有的 VMA(虚拟内存区域)的数量,默认值为 65536
vm.max_map_count=655360
重新加载
sysctl -p
使用 ES 用户分别依次启动主节点和其他节点
#启动
/opt/elasticsearch/elasticsearch-7.8.0/bin/elasticsearch
#后台启动
/opt/elasticsearch/elasticsearch-7.8.0/bin/elasticsearch -d
显示如下则表示启动成功:
启动后可以在浏览器中输入地址:http://linux1:9200/ 访问,如下:(如果访问不通可能是端口未开通访问权限,可以临时关闭 Linux的防火墙:systemctl stop firewalld)
或者可以访问 http://linux1:9200/_cat/nodes 请求查询节点数量,如下:
4、集群分片示例
这里是以上面 window 系统部署的集群为示例。
4.1、单节点集群
启动一个单节点集群,在集群内创建名为 users 的索引,为了演示目的,我们将分配 3个主分片和一份副本(每个主分片拥有一个副本分片)。
#PUT http://127.0.0.1:1001/users
{"settings" : {"number_of_shards" : 3,"number_of_replicas" : 1}
}
创建成功后,通过 elasticsearch-head 插件(一个Chrome插件,安装参考:https://www.cnblogs.com/wenxuehai/p/18153752)可以看到该集群现在是拥有一个索引的单节点集群,所有 3 个主分片都被分配在 node-1 。如下图:
集群健康值:yellow( 3 of 6 ):表示当前集群的全部主分片都正常运行,但是副本分片没有全部处在正常状态。
3 个主分片正常。
3 个副本分片都是 Unassigned,它们都没有被分配到任何节点。 在同 一个节点上既保存原始数据又保存副本是没有意义的,因为一旦失去了那个节点,我们也将丢失该节点 上的所有副本数据。
当前集群是正常运行的,但存在丢失数据的风险。
4.2、故障转移(部署多节点集群)
当集群中只有一个节点在运行时,意味着会有一个单点故障问题——没有冗余。 幸运的是,我们只需再启动一个节点即可防止数据丢失。当你在同一台机器上启动了第二个节点时,只要它和第一个节点有同样的 cluster.name 配置,它就会自动发现集群并加入到其中。但是在不同机器上启动节点的时候,为了加入到同一集群,你需要配置一个可连接到的单播主机列表。之所以配置为使用单播发现,以防止节点无意中加入集群。只有在同一台机器上运行的节点才会自动组成集群。
如果启动了第二个节点,我们的集群将会拥有两个节点的集群,所有主分片和副本分片会被自动分配。
下面我们再启动一个节点 1002,启动成功后elasticsearch-head 插件查看集群情况如下:
集群健康值:green( 6 of 6 ): 表示所有 6 个分片(包括 3 个主分片和 3 个副本分片)都在正常运行。
3 个主分片正常
当第二个节点加入到集群后,3 个副本分片将会分配到这个节点上(每个主分片对应一个副本分片)。这意味着当集群内其中一个节点出现问题时,我们的数据都会完好无损。所有新近被索引的文档都将会保存在主分片上,然后被并行的复制到对应的副本分片上。这就保证了我们既可以从主分片又可以从副本分片上获得文档。
4.3、水平扩容
当启动了第三个节点,我们的集群将会拥有三个节点的集群,ES 为了分散负载会自动对分片进行重新分配。
集群健康值:green( 6 of 6 ):表示所有 6 个分片(包括 3 个主分片和 3 个副本分片)都在正常运行。
Node 1 和 Node 2 上各有一个分片被迁移到了新的 Node 3 节点,现在每个节点上都拥有 2 个分片,而不是之前的 3 个。 这表示每个节点的硬件资源(CPU, RAM, I/O)将被更少的分片所共享,每个分片的性能将会得到提升。
分片是一个功能完整的搜索引擎,它拥有使用一个节点上的所有资源的能力。我们这个拥有 6 个分片(3 个主分片和 3 个副本分片)的索引可以最大扩容到 6 个节点,每个节点上存在一个分片,并且每个分片拥有所在节点的全部资源。
索引的主分片的数目在该索引创建时就已经确定了下来。实际上,主分片数确定了该索引能够存储的最大数据量(当然,实际大小还取决于你的数据、硬件和使用场景)。
读操作(搜索和返回数据)可以同时被主分片或副本分片所处理,所以当你拥有越多的副本分片 时,也将拥有越高的吞吐量。但是,如果只是在相同节点数目的集群上增加更多的副本分片并不能提高性能,因为每个分片从节点上获得的资源会变少。 你需要增加更多的硬件资源来提升吞吐量。
在运行中的集群上是可以动态调整副本分片数目的,我们可以按需伸缩集群。如下,将副本数从默认的 1 增加到 2:
#PUT http://127.0.0.1:1001/users/_settings
{"number_of_replicas": 2
}
4.4、集群发生故障时的表现
假设我们关闭 node-1001 主节点,集群必须拥有一个主节点来保证正常工作,所以发生的第一件事情就是选举一个新的主节点: Node 2 。在我们关闭 Node 1 的同时也失去了主分片 0 ,如果此时此时刚好来检查集群的状况,我们会看到的状态将会为 red :不是所有主分片都在正常工作。但是,在其它节点上存在着这个主分片的完整副本, 所以新的主节点会立即将这些分片在 Node 2 和 Node 3 上的某一副本分片提升为主分片, 此时集群的状态将会为yellow。这个提升主分片的过程是瞬间发生的,如同按下一个开关一般。
虽然我们拥有所有的三个主分片,但是同时设置了每个主分片需要对应 2 份副本分片,而此时只存在一份副本分片,所以此时我们集群状态是 yellow 而不是 green 。如果我们同样关闭了 Node 2 ,我们的程序依然可以保持在不丢任何数据的情况下运行,因为Node 3 也为每一个分片都保留着一份副本。
如果想启动 node-1001 节点回复原来的样子,需要往 Node-1001 的配置文件添加如下配置,然后再启动:
discovery.seed_hosts: ["localhost:9302", "localhost:9303"]
启动后如下:
5、原理
5.1、路由计算(判断文档存储位置)
Elasticsearch 如何知道一个文档应该存放到哪个分片中呢?当我们创建文档时,它如何决定这个文档应当被存储在主分片 1 还是主分片 2 中呢(数据是首先存到主分片,然后再从主分片复制到副本分片中的)?首先这肯定不会是随机的,否则将来要获取文档的时候我们就不知道从何处寻找了。实际上,这个过程是根据下面这个公式决定的:
shard = hash(routing) % number_of_primary_shards
routing 是一个可变值,默认是文档的 _id ,也可以设置成一个自定义的值。
routing 通过hash 函数生成一个数字,然后这个数字再除以 number_of_primary_shards (主分片的数量)后得到余数 。这个分布在 0 到 number_of_primary_shards-1 之间的余数(如有3个主节点,则值只能0、1、2),就是我们所寻求的文档所在分片的位置。
这里只是确定存放到哪个主分片中,然后数据会复制到对应的副本分片。在请求检索文档时,协调节点会通过轮询所有的副本分片(即不一定从主节点检索数据)来达到负载均衡。
(上图中 P 代表主分片,R 代表副本分片,如 两个R0 都是 P0 的副本 )
这就解释了为什么我们要在创建索引的时候就确定好主分片的数量并且永远不会改变这个数量,因为如果数量变化了,那么所有之前路由的值都会无效,文档也再也找不到了。
所有的文档API ( get . index . delete 、 bulk , update以及 mget )都接受一个叫做routing 的路由参数,通过这个参数我们可以自定义文档到分片的映射。一个自定义的路由参数可以用来确保所有相关的文档(例如所有属于同一个用户的文档)都被存储到同一个分片中。
5.2、分片控制(每个节点都有能力处理任意请求)
我们可以发送请求到集群中的任一节点,每个节点都有能力处理任意请求,每个节点都知道集群中任一文档位置,所以可以直接将请求转发到需要的节点上。
在下面的例子中,如果将所有的请求发送到Node 1001,我们将其称为协调节点coordinating node。(实际上,协调节点不一定从自身节点返回数据,也可能会将请求转发至其他节点。)
当发送请求的时候, 为了负载均衡和,更好的做法是轮询集群中所有的节点,而不是一直请求同一节点。
5.3、数据写流程
新建、索引和删除请求都是写操作, 必须在主分片上面完成之后才能被复制到相关的副本分片。
在客户端获取反馈即收到成功响应时,此时文档变更已经在主分片和所有副本分片执行完成,变更是安全的。
有一些可选的请求参数允许影响上述这个过程,可能以数据安全为代价提升性能,这些选项很少使用,因为 Elasticsearch 已经很快。如下:
1、consistency(一致性)
consistency 参数的值可以设为:
- one :只要主分片状态 ok 就允许执行写操作。
- all:必须要主分片和所有副本分片的状态没问题才允许执行写操作。
- quorum:默认值,即大多数的分片副本状态没问题就允许执行写操作。
在默认设置下,即使仅仅是在试图执行一个写操作之前,主分片都会要求必须要有规定数量 quorum(其实就是要求必须要有大多数)的分片副本处于活跃可用状态,才会去执行写操作(其中分片副本 可以是主分片或者副本分片)。这是为了避免在发生网络分区故障(network partition)的时候进行写操作,进而导致数据不一致。 规定数量即: int((primary + number_of_replicas) / 2 ) + 1
注意,规定数量的计算公式中number_of_replicas指的是在索引设置中的设定副本分片数,而不是指当前处理活动状态的副本分片数。如果你的索引设置中指定了当前索引拥有3个副本分片,那规定数量的计算结果即:int((1 primary + 3 replicas) / 2) + 1 = 3,如果此时你只启动两个节点,那么处于活跃状态的分片副本数量就达不到规定数量,也因此您将无法索引和删除任何文档。
2、timeout
如果没有足够的副本分片会发生什么?Elasticsearch 会等待,希望更多的分片出现。默认情况下,它最多等待 1 分钟。 如果你需要,你可以使用timeout参数使它更早终止:100是100 毫秒,30s是30秒。
(新索引默认有1个副本分片,这意味着为满足规定数量应该需要两个活动的分片副本。 但是,这些默认的设置会阻止我们在单一节点上做任何事情。为了避免这个问题,要求只有当number_of_replicas 大于1的时候,规定数量才会执行。)
7、Elasticsearch 集群脑裂问题
脑裂问题(split-brain problem)是指在一个分布式系统中,当网络分裂(network partition)发生时,导致系统中的两个或多个节点同时认为自己仍然与其他节点连接,并且各自独立地开始进行资源的管理或者状态的变更。这可能会导致数据不一致或其他不期望的行为。
在Elasticsearch(ES)集群中,脑裂现象就是:因主节点节点访问阻塞或者网络不可用导致出现分区,不同分区选举出不同的主节点的现象,即有多个主节点。脑裂问题可能会导致数据丢失、不一致或其他问题。
7.1、脑裂问题的原因
“脑裂”问题可能的成因:
- 网络问题:集群间的网络延迟导致一些节点访问不到 master,认为 master 挂掉了从而选举出新的master,并对 master 上的分片和副本标红,分配新的主分片
- 节点负载:主节点的角色既为 master 又为 data,访问量较大时可能会导致 ES 停止响应造成大面积延迟,此时其他节点得不到主节点的响应认为主节点挂掉了,会重新选取主节点。
- 内存回收:data 节点上的 ES 进程占用的内存较大,引发 JVM 的大规模内存回收,造成 ES 进程失去响应。
7.2、如何解决脑裂问题
脑裂问题解决方案:
- 减少误判:discovery.zen.ping_timeout 节点状态的响应时间,默认为 3s,可以适当调大,如果 master 在该响应时间的范围内没有做出响应应答,判断该节点已经挂掉了。调大参数(如 6s,discovery.zen.ping_timeout:6),可适当减少误判。
- 选举触发: discovery.zen.minimum_master_nodes:1。该参数是用于控制选举行为发生的最小集群主节点数量。当备选主节点的个数大于等于该参数的值,且备选主节点中有该参数个节点认为主节点挂了,进行选举。官方建议为(n/2)+1,n 为主节点个数(即有资格成为主节点的节点个数)
- 角色分离:即 master 节点与 data 节点分离,限制角色。主节点配置为:node.master: true node.data: false 从节点配置为:node.master: false node.data: true