MHA搭建
一. 介绍
MHA是一套MySQL高可用管理软件,除了检测Master宕机后,提升候选Slave为New Master之外(漂虚拟IP),还会自动让其他Slave与New Master建立复制关系。MHA Manager可以单独部署在一台独立的机器上,并管理多个master-slave集群。
二. 架构

1. 上图中存在以下节点:
- MHA Manager:MHA的管理节点,负责检测MySQL的状态,以及管理MySQL的复制关系
- Master:MySQL对外提供的服务( 通过 VIP )的主节点
- Slave-M:MySQL候选主节点,本质上为一个Slave节点,只是在Master宕机后,会被提升为 New Master
- Slave-1:MySQL从机节点,对外可以提供只读服务
2. 当Master宕机后,MHA Manager会让Slave-M提升为New Master,然后修改Slave-1的复制关系(CHANGE MASTER),指向New Master
- MHA Manager检测Master是否宕机, 不会简单的只检查自身与Master之间的直连状态
- MHA Manager会透过其他Slave节点去检测Master,进行二次探测,最大限度的避免脑裂的发生
-
- 比如说,检测到Master宕机了,MHA Manager会透过Slave-M检测是否为宕机
-
- 如果检测后仍为宕机状态,会继续透过Slave1...SlaveN进行探测,只有在透过所有节点检测到Master宕机了,才真的认为Master宕机了
-
- 以上操作步骤的前提是,MHA Manager到所有MySQL节点的SSH免密码登录要打通
3. 最后将提供对外服务的VIP漂移到New Master上,继续对外提供服务
4. 一个MHA Manager可以管理多个MySQL的Master-Slave集群(通过不同的app.conf)
三. Failover的过程
1. 从宕机崩溃的Master节点保存二进制日志事件(binlog events);
- 此种情况为MySQL挂了,但是服务器没挂,还是可以通过SSH连接过去,如果服务器彻底宕机了,该步骤略过;
2. 识别含有最新的更新的Slave节点;
3. 应用差异的中继日志(relay-log)到其他的Slave;
4. 应用从Master保存的二进制日志事件(binlog events);
- 如果之前Master彻底宕机了,就没有保存的binlog,该步骤略过;
5. 提升一个Slave为新的Master(New);
6. 使其他的Slave连接新的Master(New)进行复制;
从上面的步骤看,MHA无法完全保证数据不会丢;
-
- MySQL 5.7之前数据不丢的前提是Master服务器还可以被MHA Manager进行SSH连接,通过应用保存的binlog的方式来保证。
- MySQL 5.7之后通过无损复制,仅仅是减少了丢数据的可能性,假如此时的状态为切成异步的状态,那就和之前一样了(可以设置超时的时间很大);
- 当Master恢复的时候,最后一部分数据是否需要Flashback,MHA也是不负责这个事情,需要人工介入。
四. MHA安装
MHA由两个部分组成MHA Manager和MHA Node,Manager依赖Node,所以需要先安装Node。
1. 安装MHA Node
所有服务器均要安装 (包括MHA Manager)
- 安装Perl依赖
yum install perl-DBI perl-DBD-MySQL -y
- 安装Node
wget https://github.com/yoshinorim/mha4mysql-node/releases/download/v0.58/mha4mysql-node-0.58-0.el7.centos.noarch.rpm
rpm -ivh mha4mysql-node-0.58-0.el7.centos.noarch.rpm
安装成功后在/usr/bin/目录下生成以下文件
apply_diff_relay_logs #识别差异的中继日志事件并将其差异的事件应用于其他的slave
filter_mysqlbinlog #去除不必要的ROLLBACK事件(脚本中指出该脚本已经过时,应该是被废弃了)
purge_relay_logs #清除中继日志(不会阻塞SQL线程)
save_binary_logs #保存和复制master的二进制日志
2. MHA Manager的安装,仅仅只需要在MHA Manager服务器上安装
- 安装Perl依赖
yum install -y perl-Config-Tiny perl-Log-Dispatch perl-Parallel-ForkManager perl-Time-HiRes
- 安装Master
https://github.com/yoshinorim/mha4mysql-manager/releases/download/v0.58/mha4mysql-manager-0.58-0.el7.centos.noarch.rpm
rpm -ivh mha4mysql-manager-0.58-0.el7.centos.noarch.rpm
安装成功后在/usr/local/bin/目录下生成以下文件
masterha_check_repl # 检查MySQL复制状态
masterha_check_ssh # 检查MHA的SSH状态
masterha_check_status # 检查当前MHA的状态
masterha_conf_host # 添加或删除配置的server信息
masterha_manager # 启动MHA
masterha_master_monitor # 检测master是否宕机
masterha_master_switch # 控制故障转移(手动或者自动)
masterha_secondary_check # 通过slave检测master是否宕机(二次探测)
masterha_stop # MHA停止
3. 创建目录存放配置文件
mkdir -p /etc/masterha
4. 配置SSH免密登录(root用户)
SSH免密码登录是为了能够让MHA Manager可以远程登录到各个MySQL Server,以及各个MySQL Server之间可以相互远程登录,然后进行相关操作
- MHA Manager生成Key
ssh-keygen #执行该命令一直按Enter
- 公钥分发(执行ssh.sh,需要修改里面的ip地址)
[root@CN-MHA-MySQL-M mha]# cat ssh.sh
#!/bin/bash
UserName=root
IP="xx.xx.xx."
#分发公钥
for i in 43 44 45
do
ssh-copy-id -i ~/.ssh/id_rsa.pub $UserName@$IP$i
done
-
- MHA Manager
-
-
- 将 MHA Manager 服务器的公钥,依次分发给 Master 、 Slave1 和 Slave2
-
-
- MySQL Master
-
-
- 将 Master 的公钥依次分发给 Slave-M 和 Slave-1
-
-
- MySQL Slave-M
-
-
- 将 Slave-M 的公钥依次分发给 Master 和 Slave-1
-
-
- MySQL Slave2-1
-
-
- 将 Slave-1 的公钥依次分发给 Master 和 Slave-M
-
5. 确认ssh打通
上述操作配置完成以后,在每个服务器上,执行ssh@IP的操作(MySQL Server节点之间相互连接,以及MHA Manager到所有MySQL Server),确认可以免密码登录成功。
6. MHA环境说明
Role |
IP |
Hostname |
Server_id |
Desc |
---|---|---|---|---|
Master & MHA Manager |
10.100.172.45 |
MHA-MySQL-M |
11 |
MHA 管理服务器,用于监控,MySQL Master服务器,用于read-write |
Candicate Master (Slave) |
10.100.172.43 |
MHA-MySQL-S1 |
44 |
MySQL Slave服务器,read-only,当Master宕机后,被提升为Master(New) |
Slave |
10.100.172.44 |
MHA-MySQL-S2 |
55 |
MySQL Slave服务器,read-only |
7. MySQL建立复制,此处不赘述
8. MHA配置
MHA配置文件和说明配置文件统一放在/etc/masterha/中,针对当前这个MySQL复制组,我们定义为 app1.conf,如果还有其他MySQL复制组,可以继续定义
#
# MHA Manager 端 /etc/masterha/app1.conf
#
[server default]
log_level=debug
# 这两个参数需要根据不同的集群进行修改
manager_log=/var/log/masterha/jfrog-cn/manager.log
manager_workdir=/var/log/masterha/app1
# 按照master服务器存放binlog的实际路径进行修改,主要为了让MHA拉取binlog
master_binlog_dir=/data/mysql/
# 设置自动failover的脚本
master_ip_failover_script=/usr/local/bin/master_ip_failover
# 设置手动切换时候的脚本 (供(masterha_master_switch使用)
master_ip_online_change_script=/usr/local/bin/master_ip_online_change
# 告警脚本,可自行修改,这里没有使用
report_script=/opt/mha/failover_alerts
# 设置故障发生后关闭故障主机的脚本(主要作用是关闭主机防止发生脑裂,这里没有使用,类似Fence功能)
#shutdown_script="/usr/local/bin/power_manager --command=stopssh2 --host=test-1 --ssh_user=root"
# 监控主库的时间间隔,默认是3秒,尝试三次没有回应的时候自动进行railover
ping_interval=3
# 检测方式是insert,MHA-0.56开始支持insert
ping_type=INSERT
# 设置远端mysql在发生切换时binlog的保存位置
remote_workdir=/tmp
# 复制用的用户及密码
repl_user=repl
repl_password=*******
# 通过从机进行二次探测的脚本, IP地址按照实际的情况进行修改
secondary_check_script=/usr/bin/masterha_secondary_check -s 10.100.172.43 -s 10.100.172.44 --user=root --master_host=10.100.172.45 --master_port=3306
# 定义ssh的用户
ssh_user=root
# 监控的用户,此处时MySQL用户和password
user=root
password=*******
[server1]
# candidate_master参数的意思为:设置为候选Master,如果发生主从切换,该主机会被提升为Master,即使这个服务器上的数据不是最新的(会用relay-log补全)
candidate_master=1
check_repl_delay=0
# 这个hostname也可以配置成IP地址,同ip参数一样
# 如果这里写名字,需要DNS配合,或者使用/etc/hosts
hostname=10.100.172.45
port=3306
[server2]
candidate_master=1
# check_repl_delay参数的意思为:默认情况下如果一个slave落后master 100M的relay logs的话,MHA将不会选择该slave作为一个新的master;
# 因为对于这个slave的恢复需要花费很长时间;
# 通过设置check_repl_delay=0,MHA触发切换在选择一个新的master的时候将会忽略复制延时;
# 这个参数对于设置了candidate_master=1的主机非常有用,因为这个候选主在切换的过程中一定是新的master;
# 但是用sysbench测试发现如果从库落后主库太多还是不会切换,所以要做好主从延迟监控
check_repl_delay=0
hostname=10.100.172.43
port=3306
[server3]
hostname=10.100.172.44
# no_master表示该主机不会被提升为Master
no_master=1
port=3306
9. MHA脚本说明
- master_ip_failover
#!/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 $vip = '10.20.12.59/24';
my $key = '88';
my $ssh_start_vip = "sudo /sbin/ifconfig ens192:$key $vip";
my $ssh_stop_vip = "sudo /sbin/ifconfig ens192:$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() {
`ssh $ssh_user\@$new_master_host \" $ssh_start_vip \"`;
}
sub stop_vip() {
return 0 unless ($ssh_user);
`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";
}
- master_ip_online_change
#!/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 (
$command, $orig_master_is_new_slave, $orig_master_host,
$orig_master_ip, $orig_master_port, $orig_master_user,
$orig_master_password, $orig_master_ssh_user, $new_master_host,
$new_master_ip, $new_master_port, $new_master_user,
$new_master_password, $new_master_ssh_user,
);
my $vip = '10.20.12.59/24';
# 注意修改系统对应的接口名称(CentOS比较变态)
my $eth = 'ens192';
my $key = '88';
my $ssh_start_vip = "/sbin/ifconfig $eth:$key $vip";
my $ssh_stop_vip = "/sbin/ifconfig $eth:$key down";
my $ssh_user = "root";
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,
'orig_master_is_new_slave' => \$orig_master_is_new_slave,
'orig_master_host=s' => \$orig_master_host,
'orig_master_ip=s' => \$orig_master_ip,
'orig_master_port=i' => \$orig_master_port,
'orig_master_user=s' => \$orig_master_user,
'orig_master_password=s' => \$orig_master_password,
'orig_master_ssh_user=s' => \$orig_master_ssh_user,
'new_master_host=s' => \$new_master_host,
'new_master_ip=s' => \$new_master_ip,
'new_master_port=i' => \$new_master_port,
'new_master_user=s' => \$new_master_user,
'new_master_password=s' => \$new_master_password,
'new_master_ssh_user=s' => \$new_master_ssh_user,
);
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() {
`ssh $ssh_user\@$new_master_host \" $ssh_start_vip \"`;
}
sub stop_vip() {
return 0 unless ($ssh_user);
`ssh $ssh_user\@$orig_master_host \" $ssh_stop_vip \"`;
}
sub usage {
print
"Usage: master_ip_failover --command=start|stop|stopssh|status --ssh-user=user --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";
}
-
修改上述两个脚本中的$vip,以符合自己实际的需求
-
将上述两个文件复制到MHA Manager节点的 /usr/local/bin下
-
将上述两个文件赋予执行权限:
-
- chmod +x master_ip_failover
- chmod +x master_ip_online_change
- failover_alerts
#!/bin/bash
function send2im() {
grep -q 'Finished master recovery successfully' /var/log/masterha/jfrog-cn/manager.log
if [ $? -eq 0 ]; then
warnings="MHA Cluster Failover Successfully."
time=$(grep 'Finished master recovery successfully' /var/log/masterha/jfrog-cn/manager.log | awk -F '-' '{print $1}')
new_master=$(grep 'Enabling the VIP' /var/log/masterha/jfrog-cn/manager.log | awk -F '-' '{print $3}')
hostname=$(hostname)
if [ $hostname == "CN-MHA-MySQL-M" ]; then
env="CN MySQL Cluster"
else
env="INAP MySQL Cluster"
fi
curl "https://inbots.zoom.us/incoming/hook/oUGE5R-mM4LCzEa5Uadybue_?format=fields" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer xxxxxxxxxx" \
-d "{
\"Warnings\": \"${warnings}\",
\"New Master\": \"${new_master}\",
\"Time\": \"${time}\",
\"Env\": \"${env}\"
}"
else
warnings="MHA Cluster Failover Failed."
level="Highest"
curl "https://inbots.zoom.us/incoming/hook/oUGE5R-mM4LCzEa5Uadybue_?format=fields" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer xxxxxxxxxx" \
-d "{
\"Warnings\": \"${warnings}\",
\"Level\": \"${level}\"
}"
fi
}
send2im
10. Slave上的relay_log配置
从MHA Failover的过程中可以了解到,MHA Manager在恢复(补齐)其他Slave数据时会用到relay-log,因此这些relay-log需要被保留。而默认情况下,SQL线程在回放完毕后,MySQL会主动删除relay-log,需要禁用该功能,确保relay-log不被自动删除。在所有节点(包括Master)中配置如下参数,然后重启mysql 服务即可。
- 所有的节点
[mysqld]
# 关闭relay-log主动删除的功能
relay_log_purge = 0
但是这样做了以后又带来另外一个问题,relay-log会大量堆积,导致磁盘空间紧张,所以需要定时清空过时的relay-log。所幸的是MHA帮我们实现了这个功能,MHA Node的安装包中有一个pure_relay_logs工具,提供删除大量relay-log 的功能。在Linux下删除体积较大的文件需要一定的时间,且消耗一定的资源,所以pure_relay_logs在删除relay-log之前,会做一次硬连接,然后删除对应的relay-log(只删除指向relay-log的指针),这样不会造成系统资源消耗,从而影响复制(造成复制延时),最后再删除硬连接的relay-log(_hardlink_)
- 定时清理relay-log的脚本,将这个脚本放入所有的Slave节点,并添加到定时任务
#!/bin/bash
# Filename:/opt/mha/cron_purge_relay_logs.sh
user=root
passwd=xxxxxx
# port=3306
host=localhost
socket=/data/mysql/mysql.sock
log_dir='/data/mysql/purge_relay_logs'
work_dir='/data/mysql/relay_log_hardlink'
purge='/usr/bin/purge_relay_logs'
if [[ ! -d ${work_dir} ]];then
mkdir ${work_dir} -p
fi
if [[ ! -d ${log_dir} ]];then
mkdir ${log_dir} -p
fi
${purge} --user=${user} --password=${passwd} --host=${host} --socket=${socket} --workdir=${work_dir} --disable_relay_log_purge >> ${log_dir}/purge_relay_logs.log 2>&1
11. SSH检测
[root@CN-MHA-MySQL-M masterha]# masterha_check_ssh --conf=jfrog-cn.conf
Mon Jul 10 21:46:43 2023 - [warning] Global configuration file /etc/masterha_default.cnf not found. Skipping.
Mon Jul 10 21:46:43 2023 - [info] Reading application default configuration from jfrog-cn.conf..
Mon Jul 10 21:46:43 2023 - [info] Reading server configuration from jfrog-cn.conf..
Mon Jul 10 21:46:43 2023 - [info] Starting SSH connection tests..
Mon Jul 10 21:46:44 2023 - [debug]
Mon Jul 10 21:46:43 2023 - [debug] Connecting via SSH from root@10.100.172.45(10.100.172.45:22) to root@10.100.172.43(10.100.172.43:22)..
Mon Jul 10 21:46:44 2023 - [debug] ok.
Mon Jul 10 21:46:44 2023 - [debug] Connecting via SSH from root@10.100.172.45(10.100.172.45:22) to root@10.100.172.44(10.100.172.44:22)..
Mon Jul 10 21:46:44 2023 - [debug] ok.
Mon Jul 10 21:46:45 2023 - [debug]
Mon Jul 10 21:46:44 2023 - [debug] Connecting via SSH from root@10.100.172.43(10.100.172.43:22) to root@10.100.172.45(10.100.172.45:22)..
Mon Jul 10 21:46:44 2023 - [debug] ok.
Mon Jul 10 21:46:44 2023 - [debug] Connecting via SSH from root@10.100.172.43(10.100.172.43:22) to root@10.100.172.44(10.100.172.44:22)..
Mon Jul 10 21:46:45 2023 - [debug] ok.
Mon Jul 10 21:46:46 2023 - [debug]
Mon Jul 10 21:46:44 2023 - [debug] Connecting via SSH from root@10.100.172.44(10.100.172.44:22) to root@10.100.172.45(10.100.172.45:22)..
Mon Jul 10 21:46:45 2023 - [debug] ok.
Mon Jul 10 21:46:45 2023 - [debug] Connecting via SSH from root@10.100.172.44(10.100.172.44:22) to root@10.100.172.43(10.100.172.43:22)..
Mon Jul 10 21:46:45 2023 - [debug] ok.
Mon Jul 10 21:46:46 2023 - [info] All SSH connection tests passed successfully.
Use of uninitialized value in exit at /bin/masterha_check_ssh line 44. -- 该报错可能是脚本错误,不影响检测,所以忽略
12. 复制关系检测
masterha_check_repl --conf=/etc/masterha/jfrog-cn.conf
[root@CN-MHA-MySQL-M masterha]# masterha_check_repl --conf=jfrog-cn.conf
Mon Jul 10 21:47:46 2023 - [warning] Global configuration file /etc/masterha_default.cnf not found. Skipping.
Mon Jul 10 21:47:46 2023 - [info] Reading application default configuration from jfrog-cn.conf..
Mon Jul 10 21:47:46 2023 - [info] Reading server configuration from jfrog-cn.conf..
Mon Jul 10 21:47:46 2023 - [info] MHA::MasterMonitor version 0.58.
Mon Jul 10 21:47:46 2023 - [debug] Connecting to servers..
Mon Jul 10 21:47:47 2023 - [debug] Connected to: 10.100.172.45(10.100.172.45:3306), user=root
Mon Jul 10 21:47:47 2023 - [debug] Number of slave worker threads on host 10.100.172.45(10.100.172.45:3306): 16
Mon Jul 10 21:47:47 2023 - [debug] Connected to: 10.100.172.43(10.100.172.43:3306), user=root
Mon Jul 10 21:47:47 2023 - [debug] Number of slave worker threads on host 10.100.172.43(10.100.172.43:3306): 16
Mon Jul 10 21:47:47 2023 - [debug] Connected to: 10.100.172.44(10.100.172.44:3306), user=root
Mon Jul 10 21:47:47 2023 - [debug] Number of slave worker threads on host 10.100.172.44(10.100.172.44:3306): 16
Mon Jul 10 21:47:47 2023 - [debug] Comparing MySQL versions..
Mon Jul 10 21:47:47 2023 - [debug] Comparing MySQL versions done.
Mon Jul 10 21:47:47 2023 - [debug] Connecting to servers done.
Mon Jul 10 21:47:47 2023 - [info] GTID failover mode = 1
Mon Jul 10 21:47:47 2023 - [info] Dead Servers:
Mon Jul 10 21:47:47 2023 - [info] Alive Servers:
Mon Jul 10 21:47:47 2023 - [info] 10.100.172.45(10.100.172.45:3306)
Mon Jul 10 21:47:47 2023 - [info] 10.100.172.43(10.100.172.43:3306)
Mon Jul 10 21:47:47 2023 - [info] 10.100.172.44(10.100.172.44:3306)
Mon Jul 10 21:47:47 2023 - [info] Alive Slaves:
Mon Jul 10 21:47:47 2023 - [info] 10.100.172.43(10.100.172.43:3306) Version=8.0.33 (oldest major version between slaves) log-bin:enabled
Mon Jul 10 21:47:47 2023 - [info] GTID ON
Mon Jul 10 21:47:47 2023 - [debug] Relay log info repository: TABLE
Mon Jul 10 21:47:47 2023 - [info] Replicating from 10.100.172.45(10.100.172.45:3306)
Mon Jul 10 21:47:47 2023 - [info] Primary candidate for the new Master (candidate_master is set)
Mon Jul 10 21:47:47 2023 - [info] 10.100.172.44(10.100.172.44:3306) Version=8.0.33 (oldest major version between slaves) log-bin:enabled
Mon Jul 10 21:47:47 2023 - [info] GTID ON
Mon Jul 10 21:47:47 2023 - [debug] Relay log info repository: TABLE
Mon Jul 10 21:47:47 2023 - [info] Replicating from 10.100.172.45(10.100.172.45:3306)
Mon Jul 10 21:47:47 2023 - [info] Not candidate for the new Master (no_master is set)
Mon Jul 10 21:47:47 2023 - [info] Current Alive Master: 10.100.172.45(10.100.172.45:3306)
Mon Jul 10 21:47:47 2023 - [info] Checking slave configurations..
Mon Jul 10 21:47:47 2023 - [info] read_only=1 is not set on slave 10.100.172.43(10.100.172.43:3306).
Mon Jul 10 21:47:47 2023 - [info] read_only=1 is not set on slave 10.100.172.44(10.100.172.44:3306).
Mon Jul 10 21:47:47 2023 - [info] Checking replication filtering settings..
Mon Jul 10 21:47:47 2023 - [info] binlog_do_db= , binlog_ignore_db=
Mon Jul 10 21:47:47 2023 - [info] Replication filtering check ok.
Mon Jul 10 21:47:47 2023 - [info] GTID (with auto-pos) is supported. Skipping all SSH and Node package checking.
Mon Jul 10 21:47:47 2023 - [info] Checking SSH publickey authentication settings on the current master..
Mon Jul 10 21:47:47 2023 - [debug] SSH connection test to 10.100.172.45, option -o StrictHostKeyChecking=no -o PasswordAuthentication=no -o BatchMode=yes -o ConnectTimeout=5, timeout 5
Mon Jul 10 21:47:47 2023 - [info] HealthCheck: SSH to 10.100.172.45 is reachable.
Mon Jul 10 21:47:47 2023 - [info]
10.100.172.45(10.100.172.45:3306) (current master)
+--10.100.172.43(10.100.172.43:3306)
+--10.100.172.44(10.100.172.44:3306)
Mon Jul 10 21:47:47 2023 - [info] Checking replication health on 10.100.172.43..
Mon Jul 10 21:47:47 2023 - [info] ok.
Mon Jul 10 21:47:47 2023 - [info] Checking replication health on 10.100.172.44..
Mon Jul 10 21:47:47 2023 - [info] ok.
Mon Jul 10 21:47:47 2023 - [info] Checking master_ip_failover_script status:
Mon Jul 10 21:47:47 2023 - [info] /usr/local/bin/master_ip_failover --command=status --ssh_user=root --orig_master_host=10.100.172.45 --orig_master_ip=10.100.172.45 --orig_master_port=3306
IN SCRIPT TEST====/sbin/ifconfig ens192:88 down==/sbin/ifconfig ens192:88 10.100.172.47/24===
Checking the Status of the script.. OK
Mon Jul 10 21:47:47 2023 - [info] OK.
Mon Jul 10 21:47:47 2023 - [warning] shutdown_script is not defined.
Mon Jul 10 21:47:47 2023 - [debug] Disconnected from 10.100.172.45(10.100.172.45:3306)
Mon Jul 10 21:47:47 2023 - [debug] Disconnected from 10.100.172.43(10.100.172.43:3306)
Mon Jul 10 21:47:47 2023 - [debug] Disconnected from 10.100.172.44(10.100.172.44:3306)
Mon Jul 10 21:47:47 2023 - [info] Got exit code 0 (Not master dead).
MySQL Replication Health is OK. -- 复制关系正常
13. 启动MHA
(1). 添加VIP
由于MHA的主要目标是为了维护MySQL的复制关系,提供高可靠性的MySQL集群;至于Apps想如何访问这个集群,MHA其实没有规定的,留给我们自己决定,目前大概有如下几种访问方式:
- 虚拟IP
-
- 方式一:Keepalived:Keepalived是Linux下的一个高可用组件,可以让两台或多台Linux主机组成一个高可用集群,对外提供一个虚拟IP进行访问(也意味着同一时刻,只有一台主机能够被访问)。
-
-
- 我们这里可以在 Master 和 Slave1 ( Candicate Master )上安装keepalived,然后 检测mysql状态 (keepalived中可以定义检测脚本);
- 如果Master没有宕机,MySQL挂了,则Master上的Keepalived检测到MySQL挂了, 降低自己的优先级(低于Slave1) ,然后keepalived使用的 VRRP 协议(心跳)通过协商后,认为 Slave1的优先级更高 ,进而 VIP 转移到 Slave1 ;
- 如果Master直接宕机了,则 Slave1检测不到Master的存在 ,进而 VIP 也转移到 Slave1 ;
- keepalived会有可能产生脑裂,导致VIP在两个服务器上(有两台服务器宣称自己的IP地址是VIP),这样就会导致有的写操作落在Master上,而有的写操作落在Slave1上,从而到时整个集群的数据不一致;
- 产生脑裂的原因有多种,大部分情况是网络问题;又或者是服务器太卡,导致心跳包没有及时处理;
-
-
- 方式二:使用脚本 masterha_ip_failover ;由于这个脚本的start/stop操作只有在切换(Failover)期间才会执行,所以 即便你设置了该脚本,MHA也不会在一开始( masterha_manager启动 )的时候,帮你在Master上设置VIP,初次在Master上设置VIP需要人工操作,后期如果有Failover操作,MHA会执行脚本,帮你切换。
- 智能DNS
此种模式下,MySQL集群本身只做高可用,维持复制关系,Apps通过内网DNS来访问MySQL。此种情况下,需要DNS端可以检测MySQL服务是否可用,从而决定推送Master还是Slave1的IP地址给Apps,类似Smart Client的效果;
我们这里推荐使用虚拟IP--方式二
##
## Master 端
##
# 在我的虚拟机中,网卡名称为ens3
Shell> ifconfig ens192:88 10.100.172.47/24
(2). 启动masterha_manager
Shell> nohup masterha_manager --conf=/etc/masterha/app1.conf --remove_dead_master_conf --ignore_last_failover < /dev/null > /var/log/masterha/app1/manager.log 2>&1 &
Shell> masterha_check_status --conf=/etc/masterha/app1.conf
app1 (pid:18702) is running(0:PING_OK), master:Master
# 状态正常,且当前主机是 Master
14. 自动Failover测试
- 测试步骤如下:
-
- 停掉 Slave的IO线程 ,模拟复制延时(Slave1保持不变);
- 使用sysbench对Master进行测试,生成测试数据(可以 产生大量的binlog );
- 等待步骤2完成 后, 开启Slave上的IO线程 ,去追Master的binlog,同时立即操作第四步;
- 关闭Master上的MySQL ,让MHA 产生 Failover 操作;
- 观察最终状态;
注意:步骤3 和 步骤4 不要调换,我多次测试发现会有问题;
- Failover过程,大致包含如下步骤:
-
- 多次探测( 包括透过Slave进行二次探测 )Master,确认是否宕机;
- 检查配置文件阶段, 包括MySQL实例的当前的配置 ;
- 处理Master,前提是还能ssh到Master,否则跳过该步骤;
包括删除虚拟IP,还有关机操作(防止脑裂,但是关机配置我们注释了); - 如果还能SSH到Master上,则复制binlog到MHA Manager上,否则跳过;
这一步在源码和之前网上别人的日志中是能看到 scp 信息的,但是我这里没出现scp信息,不知道是否是MHA0.58的问题,但是最终数据是能一致(表明确实是获取binlog了)
从这里看出,单纯的靠MHA是不能完全保证数据不丢的; - 确认包含最新更新的Slave ;
- 应用从master保存的binlog ;
- 将配置中配置为 candidate_master=1 的Slave,提升为 New Master ;
- 配置其他的Slave连接( CHANGE MASTER TO ) New Master;
15. 手动Failover测试
注意事项:
在使用手动Failover的时候,masterha_manager(在线Failover功能)需要关闭 (自动切换还是手工切换自己选择),这个也是MHA留给我们自己决定的地方,线上使用可以选择使用自动切换,也可以通过报警后,处理问题,然后决定是否手工切换。
手工切换命令如下:
##
## MHA Manager 端
##
Shell> masterha_master_switch --master_state=dead --conf=/etc/masterha/app1.conf --dead_master_host=Master --dead_master_port=3306 --new_master_host=Slave1 --new_master_port=3306 --ignore_last_failover
注意事项:
- 如果你的 app1.conf 中配置的 hostname 为 Master、Slave1 等,则上述命令中的 --dead_master_host 和 --new_master_host 也需要写成 Master、Slave1 ,而 不能写成IP地址
- 如果你的 app1.conf 中配置的 hostname 为IP地址,则 --dead_master_host 和 --new_master_host 也要配置成IP地址
- 在进行手动Failover的时候,请确保Master上的MySQL挂了,否则会报如下错误:
None of server is dead. Stop failover. # 提示我们没有服务器挂
16. MHA总结
(1). 部署操作过程
- 配置好MySQL复制关系,至少三个节点,且确保rpl用户传递到Slave上;
- 在 MHA Manager 上安装 Manager 组件;
- 在所有节点上安装 Node 组件;
- 配置好/etc/masterha/app1.conf ;
- 启动masterha_manager(supervisord托管);
- 确认masterha_check_status状态;
(2). Old Master恢复
- 日志处理
当切换完成后,假如Old Master修复完成,这时需要对比一下数据是否一致;如果主从不一致,即Old Master 宕机时刻的binlog没有传到Slave,且MHA也无法获取到这部分binlog,需要通过Flashback工具将这部分数据切除 ;
-
- Old Master 的binlog信息可以通过 show master status\G 看到,或者通过 mysqlbinlog 进行查看;
-
- New Master 上查看执行到的 Old Master 复制过来的binlog信息可以通过 show global variables like "%gtid%"; 来进行查看(mysql会保留之前执行过的GTID信息);
-
-
- 在没有使用MHA,或者使用MHA手动Failover的时候,可以通过在Slave上执行 show slave status\G 通过 Exec_Master_Log_Pos 观察到执行到的位置(等待回放完毕)
-
-
-
- 而使用MHA切换后, show slave status\G 的信息会被MHA给reset掉(除非自己给MHA打补丁,将信息记录下来);
-
通过上述方法,大致可以判断是否需要Flashback或者哪些数据需要Flashback;
- 角色处理
当Old Master恢复后且日志处理完成,建议将该服务器作为New Master的Slave节点,通过CHANGE MASTER(MASTER_AUTO_POSITION=1)可以很方便的建立主从关系。
--
-- Old Master 端
--
mysql> change master to master_host='10.100.172.43', master_port=3306, master_auto_position=1, master_user='rpl', master_password='123';
mysql> start slave;
如果中间产生问题,和之前处理GTID复制出错一样, 跳过执行的部分 即可:
mysql> stop slave;
mysql> reset master;
mysql> set @@global.gtid_purged='2d408b04-0154-11e6-83b6-5254f035dabc:1-4389';
mysql> change master to master_host='10.100.172.43', master_port=3306, master_auto_position=1, master_user='rpl', master_password='123';
mysql> start slave;
备注:
1. 用普通用户配置MHA步骤(mysql为例)
- 配置mysql用户在服务器之间免密登陆
- 配置mysql用户免密执行sudo命令(为了在failover脚本中执行sudo命令)
- 在master_ip_failover和master_ip_online_change中的/sbin/ifconfig改为sudo /sbin/ifconfig
- 确保mysql用户具有以下权限:
-
- mysql用户可以启动数据库
- 数据目录属主和属组为mysql
- MHA脚本mysql用户可以执行
2. 将主从架构切换成MHA架构
- 将MHA搭建完成并测试
- 将Master-1的数据导出,导出时需要添加--set-gtid-purged=OFF,此时导出的sql文件(以下简称sql文件)中没有SET @@GLOBAL.GTID_PURGED命令,但是导出的结果会开启binlog
- 将Master-1的数据同步到Master-2,因为sql文件中开启了binlog,所以Master-2上产生了binlog,这样就会自动同步到Slave-M和Slave-1
- 因为sql文件中没有set purge gtid命令,所以如果直接执行change master命令会报错,所以先需要在Master-2上执行SET @@GLOBAL.GTID_PURGED。gtid的值获取方式如下:
[root@CN-MHA-MySQL-M mha]# head -n 30 test.sql
-- MySQL dump 10.13 Distrib 8.0.33, for Linux (x86_64)
--
-- Host: localhost Database: artdb
-- ------------------------------------------------------
-- Server version 8.0.33
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!50503 SET NAMES utf8mb4 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
--
-- Position to start replication or point-in-time recovery from
--
-- CHANGE MASTER TO MASTER_LOG_FILE='binlog.000040', MASTER_LOG_POS=237; -- 备份时刷新了binlog,这是最新的binlog文件及position
root@mysqldb 20:17: [(none)]> show binlog events in 'binlog.000040' limit 10; -- Previous_gtids即为已经purged gtid
+---------------+------+----------------+-----------+-------------+-----------------------------------------------------------------------------------------------------------+
| Log_name | Pos | Event_type | Server_id | End_log_pos | Info |
+---------------+------+----------------+-----------+-------------+-----------------------------------------------------------------------------------------------------------+
| binlog.000040 | 4 | Format_desc | 11 | 126 | Server ver: 8.0.33, Binlog ver: 4 |
| binlog.000040 | 126 | Previous_gtids | 11 | 237 | a0dae802-0b4c-11ee-a4f1-005056a4d832:1353002-8081776,
a5cd0d9c-0b4c-11ee-9992-005056a4da31:923814-930072 |
| binlog.000040 | 237 | Gtid | 11 | 316 | SET @@SESSION.GTID_NEXT= 'a0dae802-0b4c-11ee-a4f1-005056a4d832:8081777' |
| binlog.000040 | 316 | Query | 11 | 401 | BEGIN |
| binlog.000040 | 401 | Rows_query | 11 | 528 | # UPDATE access_topology SET state = 'HEALTHY', last_updated = 1689596066619 WHERE endpoint_id = 30830002 |
| binlog.000040 | 528 | Table_map | 11 | 616 | table_id: 238 (artdb.access_topology) |
| binlog.000040 | 616 | Update_rows | 11 | 948 | table_id: 238 flags: STMT_END_F |
| binlog.000040 | 948 | Rows_query | 11 | 1075 | # UPDATE access_topology SET state = 'HEALTHY', last_updated = 1689596066619 WHERE endpoint_id = 30830004 |
| binlog.000040 | 1075 | Table_map | 11 | 1163 | table_id: 238 (artdb.access_topology) |
| binlog.000040 | 1163 | Update_rows | 11 | 1471 | table_id: 238 flags: STMT_END_F |
+---------------+------+----------------+-----------+-------------+-----------------------------------------------------------------------------------------------------------+
10 rows in set (0.00 sec)
# Previous_gtids是1353002-8081776,这是因为之前迁移过一次,正常情况应该是1-8081776,第二个GTID同理
# 1353002-8081776表示1353002-8081776都被purge了,而1-1353002也被purge了,所以真正被purge的应该是1-8081776,同理另外一个被purge的应该是1-930072
- 在Master-2上执行
SET @@GLOBAL.GTID_PURGED='a0dae802-0b4c-11ee-a4f1-005056a4d832:1-8081776,
a5cd0d9c-0b4c-11ee-9992-005056a4da31:1-930072'
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· 葡萄城 AI 搜索升级:DeepSeek 加持,客户体验更智能
· 什么是nginx的强缓存和协商缓存
· 一文读懂知识蒸馏