当前位置:   article > 正文

Java面试题:集群高可用_java 集群面试题

java 集群面试题

集群概述

  • 高可用(High Availability),是当一台服务器停止服务后,对于业务及用户毫无影响。停止服务的原因可能由于网卡、路由器、机房、CPU负载过高、内存溢出、自然灾害等不可预期的原因导致,在很多时候也称单点问题。
  • 突破数据量限制,一台服务器不能储存大量数据,需要多台分担,每个存储一部分,共同存储完整个集群数据。最好能做到互相备份,即使单节点故障,也能在其他节点找到数据。
  • 数据备份容灾,单点故障后,存储的数据仍然可以在别的地方拉起。
  • 压力分担,由于多个服务器都能完成各自一部分工作,所以尽量的避免了单点压力的存在

集群的基础形式

① 主从式

在这里插入图片描述

主从复制,同步方式。

主从调度,控制方式。

② 分片式

在这里插入图片描述

数据分片存储,片区之间备份在这里插入图片描述

③ 选主式
在这里插入图片描述

为了容灾选主,为了调度选主

MySQL集群

以上可以作为企业中常用的数据库解决方案;

常见方案

  • MySQL-MMM 是 Master-Master Replication Manager for MySQL(mysql 主主复制管理器)的简称,是 Google 的开源项目 (Perl 脚本)。MMM 基于 MySQL Replication 做的扩展架构,主要用 来监控 mysql 主主复制并做失败转 移。其原理是将真实数据库节点的 IP(RIP)映射为虚拟 IP(VIP)集。 mysql-mmm 的监管端会提供多个 虚拟 IP(VIP),包括一个可写 VIP, 多个可读 VIP,通过监管的管理,这 些 IP 会绑定在可用 mysql 之上,当 某一台 mysql 宕机时,监管会将 VIP 迁移至其他 mysql。在整个监管过 程中,需要在 mysql 中添加相关授 权用户,以便让 mysql 可以支持监 理机的维护。授权的用户包括mmm_monitor 用户和一个 mmm_agent 用户,如果想使用 mmm 的备份工具则还要添加一个 mmm_tools 用户。

在这里插入图片描述

  • MHA(Master High Availability)目前在 MySQL 高可用方面是一个相对成熟的解决方案, 由日本 DeNA 公司 youshimaton(现就职于 Facebook 公司)开发,是一套优秀的作为MySQL高可用性环境下故障切换和主从提升的高可用软件。在MySQL故障切换过程中,MHA 能做到在 0~30 秒之内自动完成数据库的故障切换操作(以2019 年的眼光来说太慢了),并且在进行故障切换的过程中,MHA 能在最大程度上保证数据的一致性,以达到真正意义上的高可用。
  • InnoDB Cluster 支持自动 Failover、强一致性、读写分离、读库高可用、读请求负载均衡,横向扩展的特性,是比较完备的一套方案。但是部署起来复杂,想要解决router 单点问题好需要新增组件,如没有其他更好的方案可考虑该方案。InnoDB Cluster 主要由 MySQL Shell、MySQL Router 和 MySQL 服务器集群组成,三者协同工作,共同为MySQL 提供完整的高可用性解决方案。MySQL Shell 对管理人员提供管理接口,可以很方便的对集群进行配置和管理,MySQL Router 可以根据部署的集群状况自动的初始化,是客户端连接实例。如果有节点 down 机,集群会自动更新配置。集群包含单点写入和多点写入两种模式。在单主模式下,如果主节点 down 掉,从节点自动替换上来,MySQL Router 会自动探测,并将客户端连接到新节点。
    在这里插入图片描述

Docker 安装模拟 MySQL 主从复制集群

1、下载 mysql 镜像

2、创建 Master 实例并启动

docker run -p 3307:3306 --name mysql-master \ -v /mydata/mysql/master/log:/var/log/mysql \ -v /mydata/mysql/master/data:/var/lib/mysql \ -v /mydata/mysql/master/conf:/etc/mysql \ -e MYSQL_ROOT_PASSWORD=root \ -d mysql:5.7

参数说明
    -p 3307:3306:将容器的 3306 端口映射到主机的 3307 端口
    -v /mydata/mysql/master/conf:/etc/mysql:将配置文件夹挂在到主机
    -v /mydata/mysql/master/log:/var/log/mysql:将日志文件夹挂载到主机
    -v /mydata/mysql/master/data:/var/lib/mysql/:将配置文件夹挂载到主机
    -e MYSQL_ROOT_PASSWORD=root:初始化 root 用户的密码
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
修改 master 基本配置
vim /mydata/mysql/master/conf/my.cnf


[client]
default-character-set=utf8

[mysql]
default-character-set=utf8

[mysqld]
init_connect='SET collation_connection = utf8_unicode_ci' init_connect='SET NAMES utf8' character-set-server=utf8
collation-server=utf8_unicode_ci
skip-character-set-client-handshake
skip-name-resolve
注意:skip-name-resolve 一定要加,不然连接 mysql 会超级慢


添加 master 主从复制部分配置
server_id=1
log-bin=mysql-bin
read-only=0
binlog-do-db=gulimall_ums
binlog-do-db=gulimall_pms
binlog-do-db=gulimall_oms
binlog-do-db=gulimall_sms
binlog-do-db=gulimall_wms
binlog-do-db=gulimall_admin

replicate-ignore-db=mysql
replicate-ignore-db=sys
replicate-ignore-db=information_schema
replicate-ignore-db=performance_schema

重启 master
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

3、创建 Slave 实例并启动

docker run -p 3317:3306 --name mysql-slaver-01 \ 
-v /mydata/mysql/slaver/log:/var/log/mysql \ 
-v /mydata/mysql/slaver/data:/var/lib/mysql \ 
-v /mydata/mysql/slaver/conf:/etc/mysql \ 
-e MYSQL_ROOT_PASSWORD=root \ 
-d mysql:5.7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
修改 slave 基本配置
vim /mydata/mysql/slaver/conf/my.cnf

[client]
default-character-set=utf8

[mysql]
default-character-set=utf8

[mysqld]
init_connect='SET collation_connection = utf8_unicode_ci' init_connect='SET NAMES utf8' character-set-server=utf8
collation-server=utf8_unicode_ci
skip-character-set-client-handshake
skip-name-resolve

添加 master 主从复制部分配置
server_id=2
log-bin=mysql-bin
read-only=1

binlog-do-db=gulimall_ums
binlog-do-db=gulimall_pms
binlog-do-db=gulimall_oms
binlog-do-db=gulimall_sms
binlog-do-db=gulimall_wms
binlog-do-db=gulimall_admin

replicate-ignore-db=mysql
replicate-ignore-db=sys
replicate-ignore-db=information_schema
replicate-ignore-db=performance_schema

重启 slaver
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

4、为 master 授权用户来他的同步数据

1、进入 master 容器
docker exec -it mysql /bin/bash

2、进入 mysql 内部 (mysql –uroot -p)
	1)、授权 root 可以远程访问( 主从无关,为了方便我们远程连接mysql)grant all privileges on *.* to 'root'@'%' identified by 'root' with grant option;
flush privileges;
	2)、添加用来同步的用户
GRANT REPLICATION SLAVE ON *.* to 'backup'@'%' identified by '123456';

3、查看 master 状态
show master status\G;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

5、配置 slaver 同步 master 数据

1、进入 slaver 容器
docker exec -it mysql-slaver-01 /bin/bash

2、进入 mysql 内部(mysql –uroot -p)
	1)、授权 root 可以远程访问( 主从无关,为了方便我们远程连接mysql)
	grant all privileges on *.* to 'root'@'%' identified by 'root' with grant option;
flush privileges;

	2)、设置主库连接
	change master to
master_host='mysql-master.gulimall',master_user='backup',master_password='123456',mas
ter_log_file='mysql-bin.000003',master_log_pos=0,master_port=3306;

	3)、启动从库同步
	start slave;
	
	4)、查看从库状态
	show slave status\G;

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

在这里插入图片描述

至此主从配置完成;

总结:

  1. 主从数据库在自己配置文件中声明需要同步哪个数据库,忽略哪个数据库等信息。并且 server-id 不能一样
  2. 主库授权某个账号密码来同步自己的数据
  3. 从库使用这个账号密码连接主库来同步数据

MyCat 或者 ShardingSphere

shardingSphere: http://shardingsphere.apache.org/index_zh.html

auto_increment_offset: 1 从几开始增长

auto_increment_increment: 2 每次的步长

1、下载安装 Sharding-Proxy

镜像方式
docker pull apache/sharding-proxy

docker run -d -v /mydata/sharding-proxy/conf:/opt/sharding-proxy/conf -v/mydata/sharding-proxy/lib:/opt/sharding-proxy/lib --env PORT=3308 -p13308:3308apache/sharding-proxy:latest
  • 1
  • 2
  • 3
  • 4

2、配置数据分片+读写分离

config-mallshard.yaml

schemaName: sharding_db
dataSources:
  ds0:
    url: jdbc:mysql://192.168.56.10:3307/ds0
    username: root
    password: root
    connectionTimeoutMilliseconds: 30000
    idleTimeoutMilliseconds: 60000
    maxLifetimeMilliseconds: 1800000
    maxPoolSize: 65
  ds0_slave0:
    url: jdbc:mysql://192.168.56.10:3317/ds0
    username: root
    password: root
    connectionTimeoutMilliseconds: 30000
    idleTimeoutMilliseconds: 60000
    maxLifetimeMilliseconds: 1800000
    maxPoolSize: 65
  ds1:
    url: jdbc:mysql://192.168.56.10:3307/ds1
    username: root
    password: root
    connectionTimeoutMilliseconds: 30000
    idleTimeoutMilliseconds: 60000
    maxLifetimeMilliseconds: 1800000
    maxPoolSize: 65
  ds1_slave0:
    url: jdbc:mysql://192.168.56.10:3317/ds1
    username: root
    password: root
    connectionTimeoutMilliseconds: 30000
    idleTimeoutMilliseconds: 60000
    maxLifetimeMilliseconds: 1800000
    maxPoolSize: 65
    
shardingRule:
  tables:
    t_order:
      actualDataNodes: ds${0..1}.t_order${0..1}
      databaseStrategy:
      inline:
        shardingColumn: user_id
        algorithmExpression: ds${user_id % 2}
      tableStrategy:
        inline:
          shardingColumn: order_id
          algorithmExpression: t_order${order_id % 2}
      keyGenerator:
        type: SNOWFLAKE
        column: order_id
    t_order_item:
      actualDataNodes: ds${0..1}.t_order_item${0..1}
      databaseStrategy:
        inline:
          shardingColumn: user_id
          algorithmExpression: ds${user_id % 2}
      tableStrategy:
        inline:
          shardingColumn: order_id
          algorithmExpression: t_order_item${order_id % 2}
      keyGenerator:
        type: SNOWFLAKE
        column: order_item_id
  bindingTables:
    - t_order,t_order_item
  broadcastTables:
    - t_config

  defaultDataSourceName: ds0
  defaultTableStrategy:
    none:
  masterSlaveRules:
    ms_ds0:
      masterDataSourceName: ds0
      slaveDataSourceNames: 
        - ds0_slave0
      loadBalanceAlgorithmType: ROUND_ROBIN
    ms_ds1:
      masterDataSourceName: ds1
      slaveDataSourceNames: 
        - ds1_slave0
      loadBalanceAlgorithmType: ROUND_ROBIN
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82

3、server.yaml 文件

authentication:
  users:
    root:
      password: root
    sharding:
      password: sharding
      authorizedSchemas: sharding_db
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

MySQL集群导致的主从延迟问题

主从延迟最直接的表现是,从库消费中继日志(relog log)的速度,比主库生产的Binlog的速度要慢。

  1. 从库的机器性能比主库要差
  2. 从库的压力大
  3. 大事务的执行

如何减少主从延迟

  • 降低多线程大事务并发的概率,优化业务逻辑
  • 优化SQL,避免慢SQL,减少批量操作,建议写脚本以update-sleep这样的形式完成。
  • 提高从库机器的配置,减少主库写binlog和从库读binlog的效率差。
  • 尽量采用短的链路,也就是主库和从库服务器的距离尽量要短,提升端口带宽,减少binlog传输的网络延时。
  • 实时性要求的业务读强制走主库,从库只做灾备,备份。

MySQL集群导致的一致性问题

解决主从同步中数据不一致的问题, 就是解决主从之间数据复制方式的问题,如果按照数据一致性从弱到强来进行划分,有以下 3 种复制方式:

  • 异步复制
  • 半同步复制
  • 组复制

MySQL主从同步的原理

  1. master服务器将数据的改变记录到二进制binlog日志,当master上的数据发生改变时,则将其改变写入二进制日志中。
  2. slave服务器会在一定时间间隔内对master二进制日志进行探测其是否发生改变,如果发生改变,则开始一个I/O Thread请求二进制事件
  3. 同时主节点为每个I/O线程启动一个dump线程,用于向其发送二进制事件,并保存至从节点本地的relaylog中,从节点将启动SQL线程从relaylog中读取二进制文件,在本地重放,使得其数据和主节点的保持一致,最后I/O Thread和SQL Thread将进入睡眠状态,等待下一次被唤醒。

Redis集群

redis 集群形式

数据分区方案

① 客户端分区

在这里插入图片描述

客户端分区方案的代表为 Redis Sharding,Redis Sharding 是 Redis Cluster 出来之前,业界普遍使用的 Redis 多实例集群方法。Java 的 Redis 客户端驱动库Jedis,支持Redis Sharding 功能,即 ShardedJedis 以及结合缓存池的 ShardedJedisPool。

优点:不使用第三方中间件,分区逻辑可控,配置简单,节点之间无关联,容易线性扩展,灵活性强。

缺点:客户端无法动态增删服务节点,客户端需要自行维护分发逻辑,客户端之间无连接共享,会造成连接浪费。

② 代理分区

代理分区常用方案有 Twemproxy 和 Codis。

在这里插入图片描述

③ redis-cluster

高可用方式

① Sentinel( 哨兵机制)支持高可用

前面介绍了主从机制,但是从运维角度来看,主节点出现了问题我们还需要通过人工干预的方式把从节点设为主节点,还要通知应用程序更新主节点地址,这种方式非常繁琐笨重,而且主节点的读写能力都十分有限,有没有较好的办法解决这两个问题,哨兵机制就是针对第一个问题的有效解决方案,第二个问题则有赖于集群!哨兵的作用就是监控Redis 系统的运行状况,其功能主要是包括以下三个:

  • 监控(Monitoring): 哨兵(sentinel) 会不断地检查你的 Master 和 Slave 是否运作正常。
  • 提醒(Notification): 当被监控的某个 Redis 出现问题时, 哨兵(sentinel) 可以通过API 向管理员或者其他应用程序发送通知。
  • 自动故障迁移(Automatic failover): 当主数据库出现故障时自动将从数据库转换为主数据库。

在这里插入图片描述

哨兵的原理

Redis 哨兵的三个定时任务,Redis 哨兵判定一个 Redis 节点故障不可达主要就是通过三个定时监控任务来完成的:

在这里插入图片描述

  • 每隔 10 秒每个哨兵节点会向主节点和从节点发送"info replication" 命令来获取最新的拓扑结构
  • 每隔 2 秒每个哨兵节点会向 Redis 节点的_sentinel_:hello 频道发送自己对主节点是否故障的判断以及自身的节点信息,并且其他的哨兵节点也会订阅这个频道来了解其他哨兵节点的信息以及对主节点的判断
  • 每隔 1 秒每个哨兵会向主节点、从节点、其他的哨兵节点发送一个“ping” 命令来做心跳检测

如果在定时 Job3 检测不到节点的心跳,会判断为“主观下线”。如果该节点还是主节点那么还会通知到其他的哨兵对该主节点进行心跳检测,这时主观下线的票数超过了数时,那么这个主节点确实就可能是故障不可达了,这时就由原来的主观下线变为了“客观下线”。

故障转移和 Leader 选举 如果主节点被判定为客观下线之后,就要选取一个哨兵节点来完成后面的故障转移工作,选举出一个 leader,这里面采用的选举算法为 Raft。选举出来的哨兵 leader 就要来完成故障转移工作,也就是在从节点中选出一个节点来当新的主节点。

② redis-cluster

详见下章

Redis-Cluster

Redis 的官方多机部署方案,Redis Cluster。一组 Redis Cluster 是由多个Redis 实例组成,官方推荐我们使用 6 实例,其中 3 个为主节点,3 个为从结点。一旦有主节点发生故障的时候,Redis Cluster 可以选举出对应的从结点成为新的主节点,继续对外服务,从而保证服务的高可用性。那么对于客户端来说,知道知道对应的 key 是要路由到哪一个节点呢?Redis Cluster 把所有的数据划分为 16384 个不同的槽位,可以根据机器的性能把不同的槽位分配给不同的 Redis 实例,对于 Redis 实例来说,他们只会存储部分的 Redis 数据,当然,槽的数据是可以迁移的,不同的实例之间,可以通过一定的协议,进行数据迁移。

在这里插入图片描述

Redis 集群的功能限制;Redis 集群相对 单机 在功能上存在一些限制,需要开发人员提前了解,在使用时做好规避。

  • key 批量操作 支持有限

    • 类似 mset、mget 操作,目前只支持对具有相同 slot 值的key 执行批量操作。对于 映射为不同 slot 值的 key 由于执行 mget、mget 等操作可能存在于多个节点上,因此不被支持。
  • key 事务操作 支持有限

    • 只支持 多 key 在 同一节点上 的 事务操作,当多个 key 分布在不同的节点上时 无法 使用事务功能。
  • key 作为 数据分区 的最小粒度

  • 不能将一个 大的键值 对象如 hash、list 等映射到 不同的节点。

  • 不支持 多数据库空间

    • 单机 下的 Redis 可以支持 16 个数据库(db0 ~ db15),集群模式下只能使用一个 数据库空间,即 db0
  • 复制结构 只支持一层

    • 从节点 只能复制 主节点,不支持 嵌套树状复制 结构。
  • 命令大多会重定向,耗时多

在这里插入图片描述

一致性 hash

一致性哈希 可以很好的解决 稳定性问题,可以将所有的 存储节点排列在收尾相接的Hash 环上,每个 key 在计算 Hash 后会 顺时针 找到 临接 的 存储节点存放。而当有节点 加入 或 退出 时,仅影响该节点在 Hash 环上 顺时针相邻 的 后续节点。

在这里插入图片描述

部署Cluster

1、创建 6 个 redis 节点

3 主 3 从方式,从为了同步备份,主进行 slot 数据分片

for port in $(seq 7001 7006); \
do \
mkdir -p /mydata/redis/node-${port}/conf
touch /mydata/redis/node-${port}/conf/redis.conf
cat << EOF >/mydata/redis/node-${port}/conf/redis.conf
port ${port}
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-announce-ip 192.168.56.10
cluster-announce-port ${port}
cluster-announce-bus-port 1${port}
appendonly yes
EOF
docker run -p ${port}:${port} -p 1${port}:1${port} --name redis-${port} \ -v /mydata/redis/node-${port}/data:/data \ -v /mydata/redis/node-${port}/conf/redis.conf:/etc/redis/redis.conf \ -d redis:5.0.7 redis-server /etc/redis/redis.conf; \
done

docker stop $(docker ps -a |grep redis-700 | awk '{ print $1}')
docker rm $(docker ps -a |grep redis-700 | awk '{ print $1}')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

2、使用 redis 建立集群

docker exec -it redis-7001 bash
redis-cli --cluster create 192.168.56.10:7001 192.168.56.10:7002 192.168.56.10:7003192.168.56.10:7004 192.168.56.10:7005 192.168.56.10:7006 --cluster-replicas 1
  • 1
  • 2

Redis脑裂问题产生原因及解决方式

在生产环境中,Redis都是主从哨兵模式的高可用集群,一个master主机,多slave从机。master提供写服务,slave提供读
服务。因为网络问题,导致redis master节点距redis slave节
点和sentinel集群处于不同的网络分区,此时因为sentinel集群无
法感知到master的存在,所以将slave节点提升为master节点。
此时存在两个不同的master节点,就像一个大脑分裂成了两个。

在这里插入图片描述

脑裂问题的解决方案:

redis的配置文件中,存在两个参数。

min-slaves-to-write 3
min-slaves-max-lag 10
  • 1
  • 2

第一个参数表示连接到master的最少slave数量
第二个参数表示slave连接到master的最大延迟时间

控照上面的配置,要求至少3个slave节点,且数据复制和同步的延迟不能超过10秒,否则的活master就会拒绝写清求,配置了这两个参数之后,如果发生集群脑裂,原先的master节点接收到客户端的写入请求会拒绝,这祥所有的写操作,就会在新的master上面,数据就可以保持一致,减少数据同步后的数据丢失。

Elaticsearch集群

集群原理
单节点

在这里插入图片描述

  • 一个运行中的 Elasticsearch 实例称为一个节点,而集群是由一个或者多个拥有相同cluster.name 配置的节点组成, 它们共同承担数据和负载的压力。当有节点加入集群中或者从集群中移除节点时,集群将会重新平均分布所有的数据
  • 当一个节点被选举成为 主节点时, 它将负责管理集群范围内的所有变更,例如增加、删除索引,或者增加、删除节点等。 而主节点并不需要涉及到文档级别的变更和搜索等操作,所以当集群只拥有一个主节点的情况下,即使流量的增加它也不会成为瓶颈。任何节点都可以成为主节点。我们的示例集群就只有一个节点,所以它同时也成为了主节点。
  • 作为用户,我们可以将请求发送到 集群中的任何节点 ,包括主节点。每个节点都知道任意文档所处的位置,并且能够将我们的请求直接转发到存储我们所需文档的节点。无论我们将请求发送到哪个节点,它都能负责从各个包含我们所需文档的节点收集回数据,并将最终结果返回給客户端。 Elasticsearch 对这一切的管理都是透明的。
集群健康

Elasticsearch 的集群监控信息中包含了许多的统计数据,其中最为重要的一项就是集群健康 , 它在 status 字段中展示为 green 、 yellow 或者 red 。

GET /_cluster/health

status 字段指示着当前集群在总体上是否工作正常。它的三种颜色含义如下:

  • green:所有的主分片和副本分片都正常运行。
  • yellow:所有的主分片都正常运行,但不是所有的副本分片都正常运行。
  • red:有主分片没能正常运行。
分片
  • 一个 分片 是一个底层的 工作单元 ,它仅保存了全部数据中的一部分。我们的文档被存储和索引到分片内,但是应用程序是直接与索引而不是与分片进行交互。分片就认为是一个数据区
  • 一个分片可以是 主 分片或者 副本 分片。索引内任意一个文档都归属于一个主分片,所以主分片的数目决定着索引能够保存的最大数据量。
  • 在索引建立的时候就已经确定了主分片数,但是副本分片数可以随时修改。
  • 让我们在包含一个空节点的集群内创建名为 blogs 的索引。 索引在默认情况下会被分配 5 个主分片, 但是为了演示目的,我们将分配 3 个主分片和一份副本(每个主分片拥有一个副本分片):
PUT /blogs{
"settings" : {
"number_of_shards" : 3,
"number_of_replicas" : 1
}}
  • 1
  • 2
  • 3
  • 4
  • 5

在这里插入图片描述

此时集群的健康状况为 yellow 则表示全部 主分片都正常运行(集群可以正常服务所有请求),但是 副本 分片没有全部处在正常状态。 实际上,所有 3 个副本分片都是unassigned—— 它们都没有被分配到任何节点。在同一个节点上既保存原始数据又保存副本是没有意义的,因为一旦失去了那个节点,我们也将丢失该节点上的所有副本数据。

当前我们的集群是正常运行的,但是在硬件故障时有丢失数据的风险。

新增节点

当你在同一台机器上启动了第二个节点时,只要它和第一个节点有同样的cluster.name 配置,它就会自动发现集群并加入到其中。 但是在不同机器上启动节点的时候,为了加入到同一集群,你需要配置一个可连接到的单播主机列表。 详细信息请查看最好使用单播代替组播

在这里插入图片描述

此时,cluster-health 现在展示的状态为 green ,这表示所有 6 个分片(包括3 个主分片和3 个副本分片)都在正常运行。我们的集群现在不仅仅是正常运行的,并且还处于始终可用 的状态。

水平扩容-启动第三个节点

在这里插入图片描述

Node 1 和 Node 2 上各有一个分片被迁移到了新的 Node 3 节点,现在每个节点上都拥有 2 个分片,而不是之前的 3 个。 这表示每个节点的硬件资源(CPU, RAM, I/O)将被更少的分片所共享,每个分片的性能将会得到提升。

在运行中的集群上是可以动态调整副本分片数目的,我们可以按需伸缩集群。让我们把副本数从默认的 1 增加到 2

PUT /blogs/_settings
{
"number_of_replicas" : 2
}
  • 1
  • 2
  • 3
  • 4

blogs 索引现在拥有 9 个分片:3 个主分片和 6 个副本分片。 这意味着我们可以将集群扩容到 9 个节点,每个节点上一个分片。相比原来 3 个节点时,集群搜索性能可以提升3 倍。

在这里插入图片描述

应对故障

在这里插入图片描述

  • 我们关闭的节点是一个主节点。而集群必须拥有一个主节点来保证正常工作,所以发生的第一件事情就是选举一个新的主节点: Node 2 。
  • 在我们关闭 Node 1 的同时也失去了主分片 1 和 2 ,并且在缺失主分片的时候索引也不能正常工作。 如果此时来检查集群的状况,我们看到的状态将会为red :不是所有主分片都在正常工作。
  • 幸运的是,在其它节点上存在着这两个主分片的完整副本, 所以新的主节点立即将这些分片在 Node 2 和 Node 3 上对应的副本分片提升为主分片,此时集群的状态将会为 yellow 。 这个提升主分片的过程是瞬间发生的,如同按下一个开关一般。
  • 为什么我们集群状态是 yellow 而不是 green 呢? 虽然我们拥有所有的三个主分片,但是同时设置了每个主分片需要对应 2 份副本分片,而此时只存在一份副本分片。所以集群不能为 green 的状态,不过我们不必过于担心:如果我们同样关闭了Node 2,我们的程序 依然 可以保持在不丢任何数据的情况下运行,因为Node 3 为每一个分片都保留着一份副本。
  • 如果我们重新启动 Node 1 ,集群可以将缺失的副本分片再次进行分配。如果Node1依然拥有着之前的分片,它将尝试去重用它们,同时仅从主分片复制发生了修改的数据文件。
问题与解决

① 主节点

主节点负责创建索引、删除索引、分配分片、追踪集群中的节点状态等工作。Elasticsearch中的主节点的工作量相对较轻,用户的请求可以发往集群中任何一个节点,由该节点负责分发和返回结果,而不需要经过主节点转发。而主节点是由候选主节点通过ZenDiscovery 机制选举出来的,所以要想成为主节点,首先要先成为候选主节点。

② 候选主节点

在 elasticsearch 集群初始化或者主节点宕机的情况下,由候选主节点中选举其中一个作为主节点。指定候选主节点的配置为:node.master: true

当主节点负载压力过大,或者集中环境中的网络问题,导致其他节点与主节点通讯的时候,主节点没来的及响应,这样的话,某些节点就认为主节点宕机,重新选择新的主节点,这样的话整个集群的工作就有问题了,比如我们集群中有 10 个节点,其中7 个候选主节点,1个候选主节点成为了主节点,这种情况是正常的情况。但是如果现在出现了我们上面所说的主节点响应不及时,导致其他某些节点认为主节点宕机而重选主节点,那就有问题了,这剩下的 6 个候选主节点可能有 3 个候选主节点去重选主节点,最后集群中就出现了两个主节点的情况,这种情况官方成为“脑裂现象”;

集群中不同的节点对于 master 的选择出现了分歧,出现了多个 master 竞争,导致主分片和副本的识别也发生了分歧,对一些分歧中的分片标识为了坏片

③ 数据节点

数据节点负责数据的存储和相关具体操作,比如 CRUD、搜索、聚合。所以,数据节点对机器配置要求比较高,首先需要有足够的磁盘空间来存储数据,其次数据操作对系统CPU、Memory 和 IO 的性能消耗都很大。通常随着集群的扩大,需要增加更多的数据节点来提高可用性。指定数据节点的配置:node.data: true。

elasticsearch 是允许一个节点既做候选主节点也做数据节点的,但是数据节点的负载较重,所以需要考虑将二者分离开,设置专用的候选主节点和数据节点,避免因数据节点负载重导致主节点不响应。

④ 客户端节点

客户端节点就是既不做候选主节点也不做数据节点的节点,只负责请求的分发、汇总等等,但是这样的工作,其实任何一个节点都可以完成,因为在 elasticsearch 中一个集群内的节点都可以执行任何请求,其会负责将请求转发给对应的节点进行处理。所以单独增加这样的节点更多是为了负载均衡。指定该节点的配置为:

node.master: false

node.data: false

⑤ 脑裂问题可能的成因

  1. 网络问题:集群间的网络延迟导致一些节点访问不到 master,认为master 挂掉了从而选举出新的 master,并对 master 上的分片和副本标红,分配新的主分片

  2. 节点负载:主节点的角色既为 master 又为 data,访问量较大时可能会导致ES 停止响应造成大面积延迟,此时其他节点得不到主节点的响应认为主节点挂掉了,会重新选取主节点。

  3. 内存回收:data 节点上的 ES 进程占用的内存较大,引发 JVM 的大规模内存回收,造成ES进程失去响应。

脑裂问题解决方案:

  • 角色分离:即 master 节点与 data 节点分离,限制角色;数据节点是需要承担存储和搜索的工作的,压力会很大。所以如果该节点同时作为候选主节点和数据节点,那么一旦选上它作为主节点了,这时主节点的工作压力将会非常大,出现脑裂现象的概率就增加了。
  • 减少误判:配置主节点的响应时间,在默认情况下,主节点3 秒没有响应,其他节点就认为主节点宕机了,那我们可以把该时间设置的长一点,该配置是:discovery.zen.ping_timeout: 5
  • 选举触发:discovery.zen.minimum_master_nodes:1(默认是1),该属性定义的是为了形成一个集群,有主节点资格并互相连接的节点的最小数目。
    • 一 个 有 10 节 点 的 集 群 , 且 每 个 节 点 都 有 成为主节点的资格,discovery.zen.minimum_master_nodes 参数设置为 6。
    • 正常情况下,10 个节点,互相连接,大于 6,就可以形成一个集群。
    • 若某个时刻,其中有 3 个节点断开连接。剩下 7 个节点,大于6,继续运行之前的集群。而断开的 3 个节点,小于 6,不能形成一个集群。
    • 该参数就是为了防止”脑裂”的产生。
    • 建议设置为(候选主节点数 / 2) + 1
集群结构

以三台物理机为例。在这三台物理机上,搭建了 6 个 ES 的节点,三个data 节点,三个master 节点(每台物理机分别起了一个 data 和一个 master),3 个 master 节点,目的是达到(n/2)+1 等于 2 的要求,这样挂掉一台 master 后(不考虑 data),n 等于2,满足参数,其他两个 master 节点都认为 master 挂掉之后开始重新选举,

master 节点上

node.master = true 
node.data = false
discovery.zen.minimum_master_nodes = 2
  • 1
  • 2
  • 3

data 节点上

node.master = false
node.data = true
  • 1
  • 2

在这里插入图片描述

集群搭建

所有之前先运行:sysctl -w vm.max_map_count=262144

我们只是测试,所以临时修改。永久修改使用下面

#防止 JVM 报错
echo vm.max_map_count=262144 >> /etc/sysctl.conf
sysctl -p
  • 1
  • 2
  • 3

1、准备 docker 网络

Docker 创建容器时默认采用 bridge 网络,自行分配 ip,不允许自己指定。

在实际部署中,我们需要指定容器 ip,不允许其自行分配 ip,尤其是搭建集群时,固定ip是必须的。

我们可以创建自己的 bridge 网络 : mynet,创建容器的时候指定网络为mynet 并指定ip即可。

查看网络模式 docker network ls

创建一个新的 bridge 网络:docker network create --driver bridge --subnet=172.18.12.0/16 --gateway=172.18.1.1mynet

查看网络信息:docker network inspect mynet

以后使用--network=mynet --ip 172.18.12.x指定 ip

2、3-Master 节点创建

for port in $(seq 1 3); \
do \
mkdir -p /mydata/elasticsearch/master-${port}/config
mkdir -p /mydata/elasticsearch/master-${port}/data
chmod -R 777 /mydata/elasticsearch/master-${port}
cat << EOF >/mydata/elasticsearch/master-${port}/config/elasticsearch.yml
cluster.name: my-es #集群的名称,同一个集群该值必须设置成相同的node.name: es-master-${port} #该节点的名字
node.master: true #该节点有机会成为 master 节点
node.data: false #该节点可以存储数据
network.host: 0.0.0.0
http.host: 0.0.0.0 #所有 http 均可访问
http.port: 920${port}
transport.tcp.port: 930${port}
#discovery.zen.minimum_master_nodes: 2 #设置这个参数来保证集群中的节点可以知道其它 N 个有 master 资格的节点。官方推荐(N/2)+1
discovery.zen.ping_timeout: 10s #设置集群中自动发现其他节点时 ping 连接的超时时间discovery.seed_hosts: ["172.18.12.21:9301", "172.18.12.22:9302", "172.18.12.23:9303"] #设置集群中的 Master 节点的初始列表,可以通过这些节点来自动发现其他新加入集群的节点,es7的新增配置
cluster.initial_master_nodes: ["172.18.12.21"] #新集群初始时的候选主节点,es7 的新增配置
EOF

docker run --name elasticsearch-node-${port} \ -p 920${port}:920${port} -p 930${port}:930${port} \ --network=mynet --ip 172.18.12.2${port} \
-e ES_JAVA_OPTS="-Xms300m -Xmx300m" \ 
-v
/mydata/elasticsearch/master-${port}/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \ 
-v /mydata/elasticsearch/master-${port}/data:/usr/share/elasticsearch/data \ 
-v /mydata/elasticsearch/master-${port}/plugins:/usr/share/elasticsearch/plugins \ 
-d elasticsearch:7.4.2
done
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

3、3-Data-Node 创建

for port in $(seq 4 6); \
do \
mkdir -p /mydata/elasticsearch/node-${port}/config
mkdir -p /mydata/elasticsearch/node-${port}/data
chmod -R 777 /mydata/elasticsearch/node-${port}
cat << EOF >/mydata/elasticsearch/node-${port}/config/elasticsearch.yml
cluster.name: my-es #集群的名称,同一个集群该值必须设置成相同的node.name: es-node-${port} #该节点的名字
node.master: false #该节点有机会成为 master 节点
node.data: true #该节点可以存储数据
network.host: 0.0.0.0
#network.publish_host: 192.168.56.10 #互相通信 ip,要设置为本机可被外界访问的ip,否则无法通信
http.host: 0.0.0.0 #所有 http 均可访问
http.port: 920${port}
transport.tcp.port: 930${port}
#discovery.zen.minimum_master_nodes: 2 #设置这个参数来保证集群中的节点可以知道其它 N 个有 master 资格的节点。官方推荐(N/2)+1
discovery.zen.ping_timeout: 10s #设置集群中自动发现其他节点时 ping 连接的超时时间discovery.seed_hosts: ["172.18.12.21:9301", "172.18.12.22:9302", "172.18.12.23:9303"] #设置集群中的 Master 节点的初始列表,可以通过这些节点来自动发现其他新加入集群的节点,es7的新增配置
cluster.initial_master_nodes: ["172.18.12.21"] #新集群初始时的候选主节点,es7 的新增配置EOF
docker run --name elasticsearch-node-${port} \ -p 920${port}:920${port} -p 930${port}:930${port} \ --network=mynet --ip 172.18.12.2${port} \ -e ES_JAVA_OPTS="-Xms300m -Xmx300m" \ 
-v
/mydata/elasticsearch/node-${port}/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \ 
-v /mydata/elasticsearch/node-${port}/data:/usr/share/elasticsearch/data \ 
-v /mydata/elasticsearch/node-${port}/plugins:/usr/share/elasticsearch/plugins \ 
-d elasticsearch:7.4.2
done
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

4、测试集群

http://192.168.56.10:9201/_nodes/process?pretty 查看节点状况
http://192.168.56.10:9201/_cluster/stats?pretty 查看集群状态
http://192.168.56.10:9201/_cluster/health?pretty 查看集群健康状况
http://192.168.56.10:9202/_cat/nodes 查看各个节点信息
$ curl localhost:9200/_cat
/_cat/allocation
/_cat/shards
/_cat/shards/{index}
/_cat/master
/_cat/nodes
/_cat/indices
/_cat/indices/{index}
/_cat/segments
/_cat/segments/{index}
/_cat/count
/_cat/count/{index}
/_cat/recovery
/_cat/recovery/{index}
/_cat/health
/_cat/pending_tasks
/_cat/aliases
/_cat/aliases/{alias}
/_cat/thread_pool
/_cat/plugins
/_cat/fielddata
/_cat/fielddata/{fields}
/_cat/nodeattrs
/_cat/repositories
/_cat/snapshots/{repository}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

RabbitMQ 集群

集群形式

RabbiMQ 是用 Erlang 开发的,集群非常方便,因为 Erlang 天生就是一门分布式语言,但其本身并不支持负载均衡。

RabbitMQ 集群中节点包括内存节点(RAM)磁盘节点(Disk,消息持久化),集群中至少有一个 Disk 节点。

① 普通模式(默认)

对于普通模式,集群中各节点有相同的队列结构,但消息只会存在于集群中的一个节点。对于消费者来说,若消息进入 A 节点的 Queue 中,当从 B 节点拉取时,RabbitMQ会将消息从 A 中取出,并经过 B 发送给消费者。

应用场景:该模式各适合于消息无需持久化的场合,如日志队列。当队列非持久化,且创建该队列的节点宕机,客户端才可以重连集群其他节点,并重新创建队列。若为持久化,只能等故障节点恢复。

② 镜像模式

与普通模式不同之处是消息实体会主动在镜像节点间同步,而不是在取数据时临时拉取,高可用;该模式下,mirror queue 有一套选举算法,即 1 个 master、n 个slaver,生产者、消费者的请求都会转至 master。

应用场景:可靠性要求较高场合,如下单、库存队列。

缺点:若镜像队列过多,且消息体量大,集群内部网络带宽将会被此种同步通讯所消耗。

  • 镜像集群也是基于普通集群,即只有先搭建普通集群,然后才能设置镜像队列
  • 若消费过程中,master 挂掉,则选举新 master,若未来得及确认,则可能会重复消费。
集群搭建

1、搭建集群

mkdir /mydata/rabbitmq
cd rabbitmq/
mkdir rabbitmq01 rabbitmq02 rabbitmq03

docker run -d --hostname rabbitmq01 --name rabbitmq01 -v/mydata/rabbitmq/rabbitmq01:/var/lib/rabbitmq -p 15673:15672 -p 5673:5672 -eRABBITMQ_ERLANG_COOKIE='atguigu' rabbitmq:management

docker run -d --hostname rabbitmq02 --name rabbitmq02 -v
/mydata/rabbitmq/rabbitmq02:/var/lib/rabbitmq -p 15674:15672 -p 5674:5672 -eRABBITMQ_ERLANG_COOKIE='atguigu' --link rabbitmq01:rabbitmq01rabbitmq:management

docker run -d --hostname rabbitmq03 --name rabbitmq03 -v/mydata/rabbitmq/rabbitmq03:/var/lib/rabbitmq -p 15675:15672 -p 5675:5672 -eRABBITMQ_ERLANG_COOKIE='atguigu' --link rabbitmq01:rabbitmq01 --linkrabbitmq02:rabbitmq02 rabbitmq:management

--hostname 设置容器的主机名
RABBITMQ_ERLANG_COOKIE 节点认证作用,部署集成时 需要同步该值
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

2、节点加入集群

docker exec -it rabbitmq01 /bin/bash

rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl start_app
Exit

进入第二个节点
docker exec -it rabbitmq02 /bin/bash
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl join_cluster --ram rabbit@rabbitmq01
rabbitmqctl start_app
exit

进入第三个节点
docker exec -it rabbitmq03 bash
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl join_cluster --ram rabbit@rabbitmq01
rabbitmqctl start_app
exit
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

3、实现镜像集群

docker exec -it rabbitmq01 bash

rabbitmqctl set_policy -p / ha "^" '{"ha-mode":"all","ha-sync-mode":"automatic"}'

可以使用 rabbitmqctl list_policies -p /;查看 vhost/下面的所有 policy

在 cluster 中任意节点启用策略,策略会自动同步到集群节点
rabbitmqctl set_policy-p/ha-all"^"{“ha-mode”:“all”}’ 策略模式 all 即复制到所有节点,包含新增节点,策略正则表达式为“^” 表示所有匹配所
有队列名称。“^hello”表示只匹配名为 hello 开始的队列
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号