MySQL半同步复制和MHA高可用架构设计

Posted 郭怀远

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MySQL半同步复制和MHA高可用架构设计相关的知识,希望对你有一定的参考价值。

文章目录


一、半同步复制

1. 什么是半同步复制

所谓的半同步复制就是master主服务器每commit一个事务(简单来说就是做一个改变数据的操作),要确保slave从服务器接收完主服务器发送的binlog日志文件并写入自己的中继日志 relay log 里,然后会给 master主服务器信号,告诉对方已经接收完毕,这样master才能把事务成功commit,这样就保证了master-slave的数据绝对的一致(但是以牺牲master的性能为代价).但等待时间也是可以调整的。

2. 半同步复制的基本实现

第一步:准备一套M-S的主从架构(建议使用基于GTIDs架构设计)
参考文章 主从架构

第二步:给master与slave安装plugin插件(`$basedir/lib/plugin/)

在 master主服务器上:

mysql> install plugin rpl_semi_sync_master soname 'semisync_master.so';
Query OK, 0 rows affected (0.01 sec)

mysql> show global variables like 'rpl_semi_sync%';
+-------------------------------------------+------------+
| Variable_name                             | Value      |
+-------------------------------------------+------------+
| rpl_semi_sync_master_enabled              | OFF        |
| rpl_semi_sync_master_timeout              | 10000      |
| rpl_semi_sync_master_trace_level          | 32         |
| rpl_semi_sync_master_wait_for_slave_count | 1          |
| rpl_semi_sync_master_wait_no_slave        | ON         |
| rpl_semi_sync_master_wait_point           | AFTER_SYNC |
+-------------------------------------------+------------+

在slave从服务器上

mysql> install plugin rpl_semi_sync_slave soname 'semisync_slave.so';
Query OK, 0 rows affected (0.01 sec)

mysql> show global variables like 'rpl_semi_sync%';
+---------------------------------+-------+
| Variable_name                   | Value |
+---------------------------------+-------+
| rpl_semi_sync_slave_enabled     | OFF   |
| rpl_semi_sync_slave_trace_level | 32    |
+---------------------------------+-------+

第三步:激活master与slave服务器中的半同步复制插件

在master主服务器上:

mysql> set global rpl_semi_sync_master_enabled=on;
Query OK, 0 rows affected (0.00 sec)

mysql> show global status like 'rpl_semi_sync%';
+--------------------------------------------+-------+
| Variable_name                              | Value |
+--------------------------------------------+-------+
| Rpl_semi_sync_master_clients               | 0     |
| Rpl_semi_sync_master_net_avg_wait_time     | 0     |
| Rpl_semi_sync_master_net_wait_time         | 0     |
| Rpl_semi_sync_master_net_waits             | 0     |
| Rpl_semi_sync_master_no_times              | 0     |
| Rpl_semi_sync_master_no_tx                 | 0     |
| Rpl_semi_sync_master_status                | ON    |
| Rpl_semi_sync_master_timefunc_failures     | 0     |
| Rpl_semi_sync_master_tx_avg_wait_time      | 0     |
| Rpl_semi_sync_master_tx_wait_time          | 0     |
| Rpl_semi_sync_master_tx_waits              | 0     |
| Rpl_semi_sync_master_wait_pos_backtraverse | 0     |
| Rpl_semi_sync_master_wait_sessions         | 0     |
| Rpl_semi_sync_master_yes_tx                | 0     |
+--------------------------------------------+-------+

在 slave从服务器上:

mysql>  set global rpl_semi_sync_slave_enabled=on;
Query OK, 0 rows affected (0.00 sec)

mysql> show global variables like 'rpl_semi_sync%';
+---------------------------------+-------+
| Variable_name                   | Value |
+---------------------------------+-------+
| rpl_semi_sync_slave_enabled     | ON    |
| rpl_semi_sync_slave_trace_level | 32    |
+---------------------------------+-------+

第四步:在slave服务中重启IO线程

mysql> stop slave IO_THREAD;

mysql> start slave IO_THREAD;

第五步:测试验证半同步复制
当slave从库的IO_Thread 线程将binlog日志接受完毕后,要给master一个确认,如果超过10s未收到slave的接收确认信号,那么就会自动转换为传统的异步复制模式。

1)master插入一条记录,查看slave是否有成功返回

mysql> insert into tb_student values (null,'宋江','男');
Query OK, 1 row affected (0.00 sec)

mysql> show global status like 'rpl_semi_sync%_yes_tx';
+-----------------------------+-------+
| Variable_name               | Value |
+-----------------------------+-------+
| Rpl_semi_sync_master_yes_tx | 1     |
表示这次事物成功从slave返回一次确认信号
+-----------------------------+-------+

2)模拟slave服务器故障

在slave服务器上:

# service mysqld stop

在 master服务器上测试

mysql> insert into tb_student values (null,'小乔','女');
这次插入一个值需要等待10秒(默认的等待时间)

mysql> insert into tb_student values (null,'大.乔','女');
现在自动转成了原来的异步模式

3)重新启动半同步复制

在 slave服务器上:

# service mysqld start
# myslq -uroot -p

mysql> set global rpl_semi_sync_slave_enabled=on;
mysql> stop slave IO_THREAD;
mysql> start slave IO_THREAD;

master需要等到slave确认后才能提交,如果等不到确认消息,master等待10s种后自动变成异步同步;slave启起来后,master上改变的数据还是会自动复制过来,数据又回到一致。

3. 等待时间的修改(默认10s)

4. 卸载半同步复制插件

不需要半同步复制的时候就可以将其卸载掉

# 查看已经安装的插件
mysql> select plugin_name,load_option from information_schema.plugins;
mysql> uninstall plugin 插件名称;

卸载过程
在slave从服务器上,先停止 IO线程

mysql> stop slave IO_THREAD;

mysql> uninstall plugin rpl_semi_sync_slave;

再到主服务器上

mysql> stop slave IO_THREAD;
mysql> uninstall plugin rpl_semi_sync_master;

注意:mysqld重启后,需要手工启动主从同步

mysql> start slave;

二、MHA简介

1. 什么是MHA

MHA(Master High Availability)目前在MySQL高可用方面是一个相对成熟的解决方案,它由日本DeNA公司youshimaton(现就职于Facebook公司)开发,是一套优秀的作为MySQL高可用性环境下故障切换和主从提升的高可用软件。在MySQL故障切换过程中,MHA能做到在0~30秒之内自动完成数据库的故障切换操作,并且在进行故障切换的过程中,MHA能在较大程度上保证数据的一致性,以达到真正意义上的高可用。

2. MHA工作原理

  1. 当master出现故障时,通过对比slave之间I/O线程读取master上binlog的位置,选取最接近的slave做为最新的slave(latest slave)。
  2. 其它slave通过与latest slave对比生成差异中继日志,并应用
  3. 在latest slave上应用从master保存的binlog,同时将latest slave提升为master
  4. 最后在其它slave上应用相应的差异中继日志并开始从新的master开始复制.

3. MHA组件

  • MHA Manager**(管理节点)

    MHA Manager可以单独部署在一台独立的机器上管理多个master-slave集群,也可以部署在一台slave节点上。

  • MHA Node(数据节点)

    MHA Node运行在每台MySQL服务器上,MHA Manager会定时探测集群中的master节点,当master出现故障时,它可以自动将数据的slave提升为新的master,然后将所有其他的slave重新指向新的master。整个故障转移过程对应用程序完全透明。

4. MHA组件介绍

  • MHA Manager

运行一些工具,比如masterha_manager工具实现自动监控MySQL Master和实现master故障切换,其它工具手动实现master故障切换、在线mater转移、连接检查等等。一个Manager可以管理多 个master-slave集群

  • MHA Node

部署在所有运行MySQL的服务器上,无论是master还是slave。主要有三个作用:

1)保存二进制日志

如果能够访问故障master,会拷贝master的二进制日志

2)应用差异中继日志

​ 从拥有最新数据的slave上生成差异中继日志,然后应用差异日志。

3)清除中继日志

在不停止SQL线程的情况下删除中继日志

5. MHA中工具介绍

1)Manager工具

工具说明
masterha_check_ssh检查MHA的SSH配置
masterha_check_repl检查MySQL复制
masterha_manager启动MHA
masterha_check_status检测当前MHA运行状态
masterha_master_monitor监测master是否宕机
masterha_master_switch控制故障转移(自动或手动)
masterha_conf_host添加或删除配置的server信息

2)Node工具

工具说明
save_binary_logs保存和复制master的二进制日志
apply_diff_relay_logs识别差异的中继日志事件并应用于其它slave
filter_mysqlbinlog去除不必要的ROLLBACK事件(MHA已不再使用这个工具)
purge_relay_logs清除中继日志(不会阻塞SQL线程)

注意:Node这些工具通常由MHA Manager的脚本触发,无需人手操作

三、MHA部署架构

1. 环境准备

服务器角色IP主机名server-id功能备注
MHA-Manager192.168.44.100mha管理节点
MHA-Node(Master)192.168.44.120master10数据节点
MHA-Node(Slave1)192.168.44.140slave120数据节点
MHA-Node(Slave2)192.168.44.160slave230数据节点

2. 系统和软件版本

系统版本MySQL版本MAH版本
CentOS 7.6MySQL-5.7.31mha4mysql-manager-0.57 mha4mysql-node-0.57

3. 系统环境配置

第一步:准备4台最小化安装的机器并启动

第二步:给4台机器分别设置主机名

# hostnamectl set-hostname mha
# su

# hostnamectl set-hostname master
# su

# hostnamectl set-hostname slave1
# su

# hostnamectl set-hostname slave2
# su

第三步:给4台机器配置ip

vim /etc/sysconfig/network-scripts/ifcfg-ens33
...
TYPE="Ethernet"
BOOTPROTO="static"
IPADDR=192.168.44.* 不同机器不同ip
NETMASK=255.255.255.0
GATEWAY=192.168.44.2
DNS1=8.8.8.8
DNS2=114.114.114.114
NAME="ens33"
DEVICE="ens33"
ONBOOT="yes"

再重启网络
# systemctl restart network

使用 Xshell连接4台机器,绑定IP地址与主机名到/etc/hosts文件中

# vi /etc/hosts

192.168.44.100 mha
192.168.44.120 master
192.168.44.140 slave1
192.168.44.160 slave2

第四步:4台机器都关闭防火墙和SELinux

# systemctl stop firewalld
# systemctl disable firewalld
# setenforce 0
# sed -i '/SELINUX=enforcing/cSELINUX=disabled' /etc/selinux/config

第五步:配置YUM源

安装wget命令
# yum install wget -y
备份官方yum源
# mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup
# 获取腾讯源
# wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.cloud.tencent.com/repo/centos7_base.repo
清空yum缓存,重新建立
# yum clean all
# yum makecache

配置epel源

# wget -O /etc/yum.repos.d/epel.repo http://mirrors.cloud.tencent.com/repo/epel-7.repo
# yum clean all
# yum makecache

安装扩展软件(bash-completion自动补全、vim编辑器、net-tools网络工具包以及ntpdate时间同步工具)

# yum install bash-completion vim net-tools ntpdate -y

或者配置本地自建仓库的yum源

① 上传mha-yum(已经下好的软件包)软件包到/soft/mha目录

# mkdir -p /soft/mha

② 配置本地yum源,当我们使用yum时,可以自动找到mha-yum这个仓库目录

# vim /etc/yum.repos.d/local.repo
[mha]
name=mha soft
baseurl=file:///soft/mha/mha-yum
enabled=1
gpgcheck=0

测试MHA依赖软件库的安装

yum -y install perl-DBD-MySQL \\
perl-Config-Tiny \\
perl-Time-HiRes \\
perl-Mail-Sender \\
perl-Mail-Sendmail \\
perl-MIME-Base32 \\
perl-MIME-Charset \\
perl-MIME-EncWords \\
perl-Params-Classify \\
perl-Params-Validate.x86_64 \\
perl-Log-Dispatch \\
perl-Parallel-ForkManager \\
net-tools

最后一步时间同步

# ntpdate 111.230.50.201

4. 部署MySQL主从复制环境

  • 数据库安装位置:/usr/local/mysql

  • 数据库的数据目录:/usr/local/mysql/data

  • 套接字文件:/tmp/mysql.sock

  • 端口设置:3306

  • 配置文件位置:/usr/local/mysql/my.cnf(MySQL => 安装目录下 => /etc目录下)

1) master主服务器

第一步:上传MySQL软件到/root目录

第二步:使用脚本安装MySQL 软件

# vim mysql.sh
#!/bin/bash
yum install libaio -y
tar -xf mysql-5.7.31-linux-glibc2.12-x86_64.tar.gz
mv mysql-5.7.31-linux-glibc2.12-x86_64 /usr/local/mysql
useradd -r -s /sbin/nologin mysql
rm -rf /etc/my.cnf
cd /usr/local/mysql
mkdir mysql-files
chown mysql:mysql mysql-files
chmod 750 mysql-files
bin/mysqld --initialize --user=mysql --basedir=/usr/local/mysql &> /root/password.txt
bin/mysql_ssl_rsa_setup --datadir=/usr/local/mysql/data
cp support-files/mysql.server /etc/init.d/mysqld
service mysqld start
echo 'export PATH=$PATH:/usr/local/mysql/bin' >> /etc/profile
source /etc/profile

# source mysql.sh

第三步:设置MySQL密码

# mysql -uroot -p
Enter password: 去/root/password.txt里查看自动生成的密码
mysql> set password='root';
mysql> flush privileges;

第四步:进行安全初始化

# mysql_secure_installation

第五步:编写MASTER主机中my.cnf配置文件

# vim /usr/local/mysql/my.cnf
[mysqld]
basedir=/usr/local/mysql
datadir=/usr/local/mysql/data
socket=/tmp/mysql.sock
port=3306
log-error=/usr/local/mysql/data/master.err
log-bin=/usr/local/mysql/data/binlog
server-id=10
character_set_server=utf8mb4
gtid-mode=on
log-slave-updates=1
enforce-gtid-consistency

# service mysqld restart

2) slave1/slave2从服务器

第一步:上传MySQL软件到服务器端

第二步:使用脚本安装MySQL,但是不需要进行初始化

# vim mysql.sh
#!/bin/bash
yum install libaio -y
tar -xf mysql-5.7.31-linux-glibc2.12-x86_64.tar.gz
rm -rf /usr/local/mysql
mv mysql-5.7.31-linux-glibc2.12-x86_64 /usr/local/mysql
useradd -r -s /sbin/nologin mysql
rm -rf /etc/my.cnf
cd /usr/local/mysql
mkdir mysql-files
chown mysql:mysql mysql-files
chmod 750 mysql-files
cp support-files/mysql.server /etc/init.d/mysqld
echo 'export PATH=$PATH:/usr/local/mysql/bin' >> /etc/profile
source /etc/profile

# source mysql.sh

第三步:使用rsync把master服务器中的data数据目录同步到slave1与slave2
在 master主机上:

# rm -rf /usr/local/mysql/data/auto.cnf
# rsync -av /usr/local/mysql/data root@192.168.44.140:/usr/local/mysql/

# rsync -av /usr/local/mysql/data root@192.168.44.160:/usr/local/mysql/

第四步:给slave1与slave2配置my.cnf文件
slave1:

# vim /usr/local/mysql/my.cnf
[mysqld]
basedir=/usr/local/mysql
datadir=/usr/local/mysql/data
socket=/tmp/mysql.sock
port=3306
log-error=/usr/local/mysql/data/slave1.err
log-bin=/usr/local/mysql/data/binlog
relay-log=/usr/local/mysql/data/relaylog
server-id=20
character_set_server=utf8mb4
gtid-mode=on
log-slave-updates=1
enforce-gtid-consistency
skip-slave-start

slave2:

# vim /usr/local/mysql/my.cnf
[mysqld]
basedir=/usr/local/mysql
datadir=/usr/local/mysql/data
socket=/tmp/mysql.sock
port=3306
log-error=/usr/local/mysql/data/slave2.err
log-bin=/usr/local/mysql/data/binlog
relay-log=/usr/local/mysql/data/relaylog
server-id=30
character_set_server=utf8mb4
gtid-mode=on
log-slave-updates=1
enforce-gtid-consistency
skip-slave-start

配置完成后,启动MySQL软件

# service mysqld stop

# service mysqld start

3) 配置主从数据同步

第一步:在master服务器中创建一个slave同步账号

mysql> create user 'slave'@'192.168.44.%' identified by '123';
mysql> grant replication slave on *.* to 'slave'@'192.168.44.%';
mysql> flush privileges;

第二步:创建一个mha账号(方便后期MHA监控主从同步状态)

mysql> create user 'mha'@'192.168.44.100' identified by '123';
mysql> grant all privileges on *.* to 'mha'@'192.168.44.100';
mysql> flush privileges;

第三步:在slave1与slave2中配置主从数据同步

mysql> change master to master_host='192.168.44.120',master_port=3306,master_user='slave',master_password='123',master_auto_position=1;

mysql> start slave;
mysql> show slave status\\G

到此整个一主两从就配置完毕了!

5. MHA软件的安装

1) 不同节点安装软件

说明:在所有节点安装mha-node 软件包,在 mha 管理端再安装 mha-manager 软件包

所有主机上
# yum –y install mha4mysql-node-0.57-0.el7.noarch.rpm

安装
# rpm -ivh mha4mysql-node-0.57-0.el7.noarch.rpm

在mha上
# yum –y install mha4mysql-manager-0.57-0.el7.noarch.rpm

可以直接上传软件包

2) 配置ssh免密登录

说明:

  1. 在生产环境中几乎都是禁止root远程登陆服务器的,所以ssh免密码登陆要在admin用户下进行配置,这是处于安全角度考虑出发。
  2. admin用户可以是任意普通用户
  3. 该普通用户用于mha的管理节点远程访问mysql复制组中的所有主机,完成一些其他工作

第一步:在所有机器上创建一个普通账号admin,密码123

# useradd admin
# echo 123|passwd --stdin admin

第二步:配置mha主机到其他主机的admin用户互信

[root@mha ~]# su admin
[admin@mha root]$ ssh-keygen -P "" -f ~/.ssh/id_rsa

[admin@mha root]$ cd ~/.ssh
[admin@mha .ssh]$ ls
id_rsa  id_rsa.pub
[admin@mha .ssh]$ mv id_rsa.pub authorized_keys
[admin@mha .ssh]$ for i in 120 140 160;do scp -r ../.ssh/ 192.168.44.$i:~/;done
一路yes,再输入刚刚设置的 admin用户密码123

在mha主机上测试免密登录

[admin@mha .ssh]$ ssh 192.168.44.120
[admin@mha .ssh]$ ssh 192.168.44.140
[admin@mha .ssh]$ ssh 192.168.44.160

疑问:SSH免密只需要拷贝公钥,但是以上代码是把整个.ssh目录拷贝过去了。
答:因为MHA的互信,要求不仅仅是MHA免密到MASTER、SLAVE1、SLAVE2,MASTER也要免密到MHA、SLAVE1、SLAVE2…

3) 配置admin用户的sudo权限

master主机上

# vim /etc/sudoers.d/admin

#User_Alias  表示具有sudo权限的用户列表; Host_Alias表示主机的列表
User_Alias MYSQL_USERS = admin
#Runas_Alias  表示用户以什么身份登录
Runas_Alias MYSQL_RUNAS = root
#Cmnd_Alias  表示允许执行命令的列表(命令需要使用完整路径)
Cmnd_Alias MYSQL_CMNDS = /sbin/ifconfig,/sbin/arping
MYSQL_USERS ALL = (MYSQL_RUNAS) NOPASSWD: MYSQL_CMNDS

把这个权限分发给SLAVE1与SLAVE2一份(当故障发生时,从服务器也可以自己设置VIP)

[root@master ~]# for i in 140 160;do scp /etc/sudoers.d/admin 192.168.44.$i:/etc/sudoers.d/;done;

测试admin用户是否可以挂载VIP(只能在MASTER机器上挂载VIP)

[root@master ~]# su admin
[admin@master root]$ sudo /sbin/ifconfig ens33:1 192.168.44.200 broadcast 192.168.44.255 netmask 255.255.255.0

一张网卡挂多个ip,255是默认广播地址
[admin@master root]$ sudo /sbin/arping -fqc 5 -w 5 -I ens33 -s 192.168.44.200 -U 192.168.44.120
[admin@master root]$ ip a


arping:用来向局域网内的其它主机发送ARP请求的指令,可以用来测试局域网内的某个IP是否已被使用。

-f:收到第一个响应包后退出。
-q:quite模式,不显示输出。 
-c:发送指定的count个ARP REQUEST包后停止。如果指定了-w参数,则会等待相同数量的ARP REPLY包,直到超时为止。
-w:指定一个超时时间,单位为秒,arping在到达指定时间后退出,无论期间发送或接收了多少包。在这种情况下,arping在发送完指定的count(-c)个包后并不会停止,而是等待到超时或发送的count个包都进行了回应后才会退出。 
-I:指定设备名,用来发送ARP REQUEST包的网络设备的名称。
-D:重复地址探测模式,用来检测有没有IP地址冲突,如果没有IP冲突则返回0。 
-s:设置发送ARP包的IP资源地址
-U:无理由的(强制的)ARP模式去更新别的主机上的ARP CACHE列表中的本机的信息,不需要响应。
-h:显示帮助页。 

4) 创建mha相关配置文件

mha服务器:创建工作目录

[root@mha ~]# mkdir /etc/mha
[root@mha ~]# mkdir -p /data/mha/masterha/app1
[root@mha ~]# chown -R admin. /data/mha

创建mha局部配置文件:

[server default]
# 设置监控用户和密码(修改1)
user=mha
password=123
# 设置复制环境中的复制用户和密码(修改2)
repl_user=slave
repl_password=123
# 设置ssh的登录用户名(修改3)
ssh_user=admin
# 设置监控主库,发送ping包的时间间隔,默认是3秒,尝试三次没有回应的时候自动进行failover
ping_interval=3
# 设置mgr的工作目录
manager_workdir=/data/mha/masterha/app1
# 设置mysql master保存binlog的目录,以便MHA可以找到master的二进制日志(修改4)
master_binlog_dir=/usr/local/mysql/data
# 设置master的pid文件(修改5)
master_pid_file=/usr/local/mysql/data/master.itcast.cn.pid
# 设置mysql master在发生切换时保存binlog的目录(在mysql master上创建这个目录)
remote_workdir=/data/mysql/mha
# 设置mgr日志文件(MHA遇到问题,主要看这个日志)
manager_log=/data/mha/masterha/app1/app1-3306.log
# MHA到master的监控之间出现问题,MHA Manager将会尝试从slave1和slave2登录到master上(修改4)
secondary_check_script=/usr/bin/masterha_secondary_check -s 192.168.44.140 -s 192.168.44.160 --user=admin --port=22 --master_host=192.168.44.120 --master_port=3306
# 设置自动failover时候的切换脚本(故障发生时,自动挂载VIP到SLAVE1或SLAVE2)(修改5)
master_ip_failover_script="/etc/mha/master_ip_failover.sh 192.168.44.200 1"
# 设置手动切换时候的切换脚本
#master_ip_online_change_script="/etc/mha/master_ip_online_change.sh 192.168.44.200 1"
# 设置故障发生后关闭故障主机脚本
# shutdown_script="/etc/mha/power_manager"
[server1]
hostname=192.168.44.120
port= 3306
candidate_master=1
[server2]
hostname=192.168.44.140
port= 3306
candidate_master=1
[server3]
hostname=192.168.44.160
port= 3306
candidate_master=1



5) 编写相应脚本/etc/mha目录,然后更改配置信息,授权

[root@mha ~]# ls /etc/mha/
app1.conf  master_ip_failover.sh
注意:脚本内容中要修改网卡名字和连接用户为admin
my $vip  = shift;
my $interface = 'ens33';		网卡名(38行)
my $key = shift;
...
sub stop_vip() 
    my $ssh_user = "admin";		用户名(110行)
    print "=======$ssh_stop_vip==================\\n";
    `ssh $ssh_user\\@$orig_master_host \\" $ssh_stop_vip \\"`;


# chmod +x /etc/mha/master_ip_*

下面是脚本文件

#!/usr/bin/env perl

#  Copyright (C) 2011 DeNA Co.,Ltd.
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#   along with this program; if not, write to the Free Software
#  Foundation, Inc.,
#  51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

## Note: This is a sample script and is not complete. Modify the script based on your environment.
#!/usr/bin/env perl
#!/usr/bin/env perl

use strict;
use warnings FATAL => 'all';

use Getopt::Long;

my (
    $command,          $ssh_user,        $orig_master_host, $orig_master_ip,
    $orig_master_port, $new_master_host, $new_master_ip,    $new_master_port
);

#my $gateway = '10.77.133.1';
my $vip  = shift;
#my $bcast = '10.77.133.255';
#my $netmask = '255.255.255.0';
my $interface = 'ens33';
my $key = shift;
#my $ssh_start_vip = "sudo /sbin/ifconfig $interface:$key $vip netmask $netmask && sudo /sbin/arping -f -q -c 5 -w 5 -I $interface -s $vip  -U $gateway";
my $ssh_stop_vip = "sudo /sbin/ifconfig $interface:$key down";

GetOptions(
    'command=s'          => \\$command,
    'ssh_user=s'         => \\$ssh_user,
    'orig_master_host=s' => \\$orig_master_host,
    'orig_master_ip=s'   => \\$orig_master_ip,
    'orig_master_port=i' => \\$orig_master_port,
    'new_master_host=s'  => \\$new_master_host,
    'new_master_ip=s'    => \\$new_master_ip,
    'new_master_port=i'  => \\$new_master_port,
);

exit &main();

sub main 

    #print "\\n\\nIN SCRIPT TEST====$ssh_stop_vip==$ssh_start_vip===\\n\\n";

    if ( $command eq "stop" || $command eq "stopssh" ) 

        my $exit_code = 1;
        eval 
            print "Disabling the VIP on old master: $orig_master_host \\n";
            &stop_vip();
            $exit_code = 0;
        ;
        if ($@) 
            warn "Got Error: $@\\n";
            exit $exit_code;
        
        exit $exit_code;
    
    elsif ( $command eq "start" ) 

        my $exit_code = 10;
        eval 
            print "Enabling the VIP - $vip on the new master - $new_master_host \\n";
            &start_vip();
            $exit_code = 0;
        ;
        if ($@) 
            warn $@;
            exit $exit_code;
        
        exit $exit_code;
    
    elsif ( $command eq "status" ) 
        print "Checking the Status of the script.. OK \\n";
        exit 0;
    
    else 
        &usage();
        exit 1;
    


sub start_vip() 
    my $bcast  = `ssh $ssh_user\\@$new_master_host sudo /sbin/ifconfig | grep 'Bcast' | head -1 | awk 'print \\$3' | awk -F":" 'print \\$2'`;
    chomp $bcast;
    my $gateway  = `ssh $ssh_user\\@$new_master_host sudo /sbin/route -n  | grep 'UG' | awk 'print \\$2'`;
    chomp $gateway;
    my $netmask  = `ssh $ssh_user\\@$new_master_host sudo /sbin/ifconfig | grep 'Bcast' | head -1 | awk 'print \\$4' | awk -F":" 'print \\$2'`;
    chomp $netmask;
    my $ssh_start_vip = "sudo /sbin/ifconfig $interface:$key $vip broadcast $bcast netmask $netmask && sudo /sbin/arping -f -q -c 5 -w 5 -I $interface -s $vip  -U $gateway";
    print "=======$ssh_start_vip=================\\n";
    `ssh $ssh_user\\@$new_master_host \\" $ssh_start_vip \\"`;

sub stop_vip() 
    my $ssh_user = "admin";
    print "=======$ssh_stop_vip==================\\n";
    `ssh $ssh_user\\@$orig_master_host \\" $ssh_stop_vip \\"`;


sub usage 
    print
    "Usage: master_ip_failover --command=start|stop|stopssh|status --orig_master_host=host --orig_master_ip=ip --orig_master_port=port --new_master_host=host --new_master_ip=ip --new_master_port=port\\n";


6. 检测SSH互信以及MySQL主从状态

在mha机器上

# su - admin

# 检测SSH互信
[admin@mha ~]$ masterha_check_ssh --conf=/etc/mha/app1.conf
最后出现这句话说明没有问题
Fri Sep  2 17:22:08 2022 - [info] All SSH connection tests passed successfully.
# 检测集群状态
[admin@mha ~]$ masterha_check_repl --conf=/etc/mha/app1.conf

出现一下,说明没问题

Fri Sep  2 18:28:54 2022 - [warning] Global configuration file /etc/masterha_default.cnf not found. Skipping.
Fri Sep  2 18:28:54 2022 - [info] Reading application default configuration from /etc/mha/app1.conf..
Fri Sep  2 18:28:54 2022 - [info] Reading server configuration from /etc/mha/app1.conf..
Fri Sep  2 18:28:54 2022 - [info] Starting SSH connection tests..
Fri Sep  2 18:28:55 2022 - [debug] 
Fri Sep  2 18:28:54 2022 - [debug]  Connecting via SSH from admin@192.168.44.120(192.168.44.120:22) to admin@192.168.44.140(192.168.44.140:22)..
Fri Sep  2 18:28:55 2022 - [debug]   ok.
Fri Sep  2 18:28:55 2022 - [debug]  Connecting via SSH from admin@192.168.44.120(192.168.44.120:22) to admin@192.168.44.160(192.168.44.160:22)..
Fri Sep  2 18:28:55 2022 - [debug]   ok.
Fri Sep  2 18:28:56 2022 - [debug] 
Fri Sep  2 18:28:55 2022 - [debug]  Connecting via SSH from admin@192.168.44.140(192.168.44.140:22) to admin@192.168.44.120(192.168.44.120:22)..
Fri Sep  2 18:28:55 2022 - [debug]   ok.
Fri Sep  2 18:28:55 2022 - [debug]  Connecting via SSH from admin@192.168.44.140(192.168.44.140:22) to admin@192.168.44.160(192.168.44.160:22)..
Fri Sep  2 18:28:56 2022 - [debug]   ok.
Fri Sep  2 18:28:57 2022 - [debug] 
Fri Sep  2 18:28:55 2022 - [debug]  Connecting via SSH from admin@192.168.44.160(192.168.44.160:22) to admin@192.168.44.120(192.168.44.120:22)..
Fri Sep  2 18:28:56 2022 - [debug]   ok.
Fri Sep  2 18:28:56 2022 - [debug]  Connecting via SSH from admin@192.168.44.160(192.168.44.160:22) to admin@192.168.44.140(192.168.44.140:22)..
Fri Sep  2 18:28:56 2022 - [debug]   ok.
Fri Sep  2 18:28:57 2022 - [info] All SSH connection tests passed successfully.
[admin@mha mha]$ masterha_check_repl --conf=/etc/mha/app1.conf
Fri Sep  2 18:29:05 2022 - [warning] Global configuration file /etc/masterha_default.cnf not found. Skipping.
Fri Sep  2 18:29:05 2022 - [info] Reading application default configuration from /etc/mha/app1.conf..
Fri Sep  2 18:29:05 2022 - [info] Reading server configuration from /etc/mha/app1.conf..
Fri Sep  2 18:29:05 2022 - [info] MHA::MasterMonitor version 0.57.
Fri Sep  2 18:29:06 2022 - [info] GTID failover mode = 1
Fri Sep  2 18:29:06 2022 - [info] Dead Servers:
Fri Sep  2 18:29:06 2022 - [info] Alive Servers:
Fri Sep  2 18:29:06 2022 - [info]   192.168.44.120(192.168.44.120:3306)
Fri Sep  2 18:29:06 2022 - [info]   192.168.44.140(192.168.44.140:3306)
Fri Sep  2 18:29:06 2022 - [info]   192.168.44.160(192.168.44.160:3306)
Fri Sep  2 18:29:06 2022 - [info] Alive Slaves:
Fri Sep  2 18:29:06 2022 - [info]   192.168.44.140(192.168.44.140:3306)  Version=5.7.31-log (oldest major version between slaves) log-bin:enabled
Fri Sep  2 18:29:06 2022 - [info]     GTID ON
Fri Sep  2 18:29:06 2022 - [info]     Replicating from 192.168.44.120(192.168.44.120:3306)
Fri Sep  2 18:29:06 2022 - [info]     Primary candidate for the new Master (candidate_master is set)
Fri Sep  2 18:29:06 2022 - [info]   192.168.44.160(192.168.44.160:3306)  Version=5.7.31-log (oldest major version between slaves) log-bin:enabled
Fri Sep  2 18:29:06 2022 - [info]     GTID ON
Fri Sep  2 18:29:06 2022 - [info]     Replicating from 192.168.44.120(192.168.44.120:3306)
Fri Sep  2 18:29:06 2022 - [info]     Primary candidate for the new Master (candidate_master is set)
Fri Sep  2 18:29:06 2022 - [info] Current Alive Master: 192.168.44.120(192.168.44.120:3306)
Fri Sep  2 18:29:06 2022 - [info] Checking slave configurations..
Fri Sep  2 18:29:06 2022 - [info]  read_only=1 is not set on slave 192.168.44.140(192.168.44.140:3306).
Fri Sep  2 18:29:06 2022 - [info]  read_only=1 is not set on slave 192.168.44.160(192.168.44.160:3306).
Fri Sep  2 18:29:06 2022 - [info] Checking replication filtering settings..
Fri Sep  2 18:29:06 2022 - [info]  binlog_do_db= , binlog_ignore_db= 
Fri Sep  2 18:29:06 2022 - [info]  Replication filtering check ok.
Fri Sep  2 18:29:06 2022 - [info] GTID (with auto-pos) is supported. Skipping all SSH and Node package checking.
Fri Sep  2 18:29:06 2022 - [info] Checking SSH publickey authentication settings on the current master..
Fri Sep  2 18:29:06 2022 - [info] HealthCheck: SSH to 192.168.44.120 is reachable.
Fri Sep  2 18:29:06 2022 - [info] 
192.168.44.120(192.168.44.120:3306) (current master)
 +--192.168.44.140(192.168.44.140:3306)
 +--192.168.44.160(192.168.44.160:3306)

Fri Sep  2 18:29:06 2022 - [info] Checking replication health on 192.168.44.140..
Fri Sep  2 18:29:06 2022 - [info]  ok.
Fri Sep  2 18:29:06 2022 - [info] Checking replication health on 192.168.44.160..
Fri Sep  2 18:29:06 2022 - [info]  ok.
Fri Sep  2 18:29:06 2022 - [info] Checking master_ip_failover_script status:
Fri Sep  2 18:29:06 2022 - [info]   /etc/mha/master_ip_failover.sh 192.168.44.200 1 --command=status --ssh_user=admin --orig_master_host=192.168.44.120 --orig_master_ip=192.168.44.120 --orig_master_port=3306 
Checking the Status of the script.. OK 
Fri Sep  2 18:29:06 2022 - [info]  OK.
Fri Sep  2 18:29:06 2022 - [warning] shutdown_script is not defined.
Fri Sep  2 18:29:06 2022 - [info] Got exit code 0 (Not master dead).

MySQL Replication Health is OK.

7. 检查MHA状态,然后运行MHA(监控开始)

在mha机器上操作

[admin@mha mha]$ masterha_check_status --conf=/etc/mha/app1.conf 说明已经停止
app1 is stopped(2:NOT_RUNNING).


开启MHA Manager监控(关键,代表启动MHA):
&:表示后台运行
nohup :关闭终端也运行
[admin@mha ~]$ nohup masterha_manager --conf=/etc/mha/app1.conf --remove_dead_master_conf --ignore_last_failover &

再次查看监控状态:
[admin@mha ~]$ masterha_check_status --conf=/etc/mha/app1.conf

注意:
1. 如果正常,会显示”PING_OK ”,否则会显示”NOT_RUNNING ”,说明 MHA监控没有开启
2. 使用admin用户启动监控,否则会报权限拒绝
3. 手动停止监控命令:masterha_stop --conf=/etc/mha/app1.conf

四、自动Failover测试

1.在MASTER服务器安装测试工具

[root@master ~]# yum -y install sysbench

2. 插入测试数据

master服务器上创建测试库db_test
mysql> create database db_test charset utf8mb4;
mysql> exit

# 用脚本创建一个表,并插入100000条数据
[root@master ~]# sysbench /usr/share/sysbench/oltp_read_only.lua \\
 --mysql-host=10.1.1.10  --mysql-port=3306  --mysql-user=mha \\
 --mysql-password=123  --mysql-socket=/tmp/mysql.sock \\
 --mysql-db=test  --db-driver=mysql  --tables=1 \\
 --table-size=100000 --report-interval=10 --threads=128 --time=120 prepare

mysql> select count(*) from db_test.sbtest1;
+----------+
| count(*) |
+----------+
|   100000 |
+----------+

3. 拍摄快照

给4台机器都拍摄快照

4. 模拟master服务器故障

mha服务器:监控日志

[root@mgr ~]# tail -f /data/mha/masterha/app1/app1-3306.log

mha日志显示结果
故障切换:


VIP漂移:


以上是关于MySQL半同步复制和MHA高可用架构设计的主要内容,如果未能解决你的问题,请参考以下文章

mysql高可用之MHA+半同步复制

MHA-结合MySQL半同步复制高可用集群(Centos7)

MySQL数据库---MHA高可用群集架构

MySQL MHA高可用架构官方文档全文翻译(完成2/3)

Heartbeat+HAProxy+MySQL半复制高可用架构

运维实战 Mysql高可用与健康检查