MHA(Master High Availability)目前在MySQL高可用方面是一个相对成熟的解决方案,它由日本DeNA公司youshimaton(现就职于Facebook公司)开发,是一套优秀的作为MySQL高可用性环境下故障切换和主从提升的高可用软件。在MySQL故障切换过程中,MHA能做到在0~30秒之内自动完成数据库的故障切换操作,并且在进行故障切换的过程中,MHA能在最大程度上保证数据的一致性,以达到真正意义上的高可用。
该软件由两部分组成:MHA Manager(管理节点)和MHA Node(数据节点)。MHA Manager可以单独部署在一台独立的机器上管理多个master-slave集群,也可以部署在一台slave节点上。MHA Node运行在每台MySQL服务器上,MHA Manager会定时探测集群中的master节点,当master出现故障时,它可以自动将最新数据的slave提升为新的master,然后将所有其他的slave重新指向新的master。整个故障转移过程对应用程序完全透明。
在MHA自动故障切换过程中,MHA试图从宕机的主服务器上保存二进制日志,最大程度的保证数据的不丢失,但这并不总是可行的。例如,如果主服务器硬件故障或无法通过ssh访问,MHA没法保存二进制日志,只进行故障转移而丢失了最新的数据。使用MySQL 5.5的半同步复制,可以大大降低数据丢失的风险。MHA可以与半同步复制结合起来。如果只有一个slave已经收到了最新的二进制日志,MHA可以将最新的二进制日志应用于其他所有的slave服务器上,因此可以保证所有节点的数据一致性。
目前MHA主要支持一主多从的架构,要搭建MHA,要求一个复制集群中必须最少有三台数据库服务器,一主二从,即一台充当master,一台充当备用master,另外一台充当从库,因为至少需要三台服务器,出于机器成本的考虑,淘宝也在该基础上进行了改造,目前淘宝TMHA已经支持一主一从。
工作原理:
(1)从宕机崩溃的master保存二进制日志事件(binlog events);
(2)识别含有最新更新的slave;
(3)应用差异的中继日志(relay log)到其他的slave;
(4)应用从master保存的二进制日志事件(binlog events);
(5)提升一个slave为新的master;
(6)使其他的slave连接新的master进行复制;
环境:
Server1:172.25.254.1 master
Server2:172.25.254.2 slave
Server3:172.25.254.3 slave
Server4:172.25.254.4 manager
mysql版本: mysql5.7
关于本篇博客中涉及的所有软件包下载可参考此博客:https://blog.csdn.net/qq657886445/article/details/83662696
先简单配置主从复制,一主两从
此处采用基于gtid的主从复制,其详细配置可参考博客:https://blog.csdn.net/qq657886445/article/details/83588877
由于搭建MHA任何一台虚拟机都有可能成为master所以每台虚拟机mysql配置文件中都需添加开启二进制日志的参数
一主二从结果如下图
server1,server2,server3都安装mha4mysql-node
yum install -y mha4mysql-node-0.56-0.el6.noarch.rpm
Server4
yum install -y mha4mysql-manager-0.56-0.el6.noarch.rpm perl-* mha4mysql-node-0.56-0.el6.noarch.rpm
mkdir /etc/masterha
cd /etc/masterha/
vim app1.cnf #编写配置文件
[server default]
manager_workdir=/etc/masterha #manager的工作目录
manager_log=/etc/masterha/manager.log #manager的日志地址
master_binlog_dir=/var/lib/mysql #master 保存binlog的位置,以便MHA可以找到master的日志
#master_ip_failover_script=/usr/local/bin/master_ip_failover
#master_ip_online_change_script= /usr/local/bin/master_ip_online_change
password=Westos.3q #mysql中root用户的密码
user=root #监控用户root
ping_interval=1 #设置监控主库,发送ping包的时间间隔
remote_workdir=/tmp #设置远端mysql在发生切换时binlog的保存位置
repl_password=Westos.3q #设置复制用户的密码
repl_user=repl #设置复制用户名
secondary_check_script=/usr/bin/masterha_secondary_check -s server3 -s server1
ssh_user=root #设置ssh的登录用户名
[server1]
hostname=172.25.254.1
port=3306
[server2]
hostname=172.25.254.2
port=3306
candidate_master=1 #优先选为新的master,即使这个主库不是集群中事件最新的slave
check_repl_delay=0 #选择新的master时MHA会忽略复制延迟
[server3]
hostname=172.25.254.3
port=3306
四个节点需要ssh的免密认证
生成认证key
ssh-kengen #全部回车即可
加密服务
cd ~/.ssh/
ssh-copy-id 172.25.254.4
分发密钥
scp -r .ssh/ server1:
scp -r .ssh/ server2:
scp -r .ssh/ server3:
测试:
因为每个节点第一次连接时都会确认是否连接,所以各节点都挨着互相连一遍
ssh 172.25.24.4
ssh 172.25.24.3
ssh 172.25.24.2
ssh 172.25.24.1
使用免密检测脚本检测:
masterha_check_ssh --conf=/etc/masterha/app1.cnf #全部成功则此步成功
登陆master端数据库授权
grant all on *.* to root@'%' identified by 'Westos.3q';
grant replication slave on *.* to repl@'%' identified by 'Westos.3q';
在server4执行用户检测脚本,需使复制用户存在和允许管理员用户的远程登陆
masterha_check_repl --conf=/etc/masterha/app1.cnf #最后显示OK即为成功
启动服务
nohup masterha_manager --conf=/etc/masterha/app1.cnf --ignore_last_failover & #启动服务并打入后台
masterha_check_status --conf=/etc/masterha/app1.cnf #查看状态
测试:
1.手动切换
1)将正在运行的master转换为指定的master。指定新master为server2
masterha_master_switch --conf=/etc/masterha/app1.cnf --master_state=alive --new_master_host=172.25.254.2 --new_master_port=3306 --orig_master_is_new_slave --running_updates_limit=10000
我做的时候在第一次输入yes后出现如下报错,需关闭MHA
此时关闭MHA再次测试,一直输入yes即可
查看各节点slave状态,可以发现server2已成为新的master,server1和server3为slave
2)手动切换故障机器
关闭server2上的mysql
指定新master为server3,执行命令一直输入yes
masterha_master_switch --master_state=dead --conf=/etc/masterha/app1.cnf --dead_master_host=172.25.254.2 --dead_master_port=3306 --new_master_host=172.25.254.3 --new_master_port=3306 --ignore_last_failover
分别在server3和server1上查看slave状态,master切换成功
当再次启动server2的mysql时,需要先关闭slave重新指定master后再开启slave
change master to master_host='172.25.254.3',master_user='repl',master_password='Westos.3q',master_auto_position=1;
2.自动切换
手动关闭master端mysql,自动切换需开启mha服务。
在server4查看日志
cat /etc/masterha/manager.log #master已切换到server2
修改/etc/masterha/app1.cnf文件
vim /etc/masterha/app1.cnf #取消注释
cat /usr/local/bin/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 = '172.25.254.100/24';
my $ssh_start_vip = "/sbin/ip addr add $vip dev eth0";
my $ssh_stop_vip = "/sbin/ip addr del $vip dev eth0";
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";
}
cat /usr/local/bin/master_ip_online_change
#!/usr/bin/env perl
use strict;
use warnings FATAL =>'all';
use Getopt::Long;
my $vip = '172.25.254.100/24'; # Virtual IP
my $ssh_start_vip = "/sbin/ip addr add $vip dev eth0";
my $ssh_stop_vip = "/sbin/ip addr del $vip dev eth0";
my $exit_code = 0;
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,
);
GetOptions(
'command=s' => \$command,
'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" ) {
# $orig_master_host, $orig_master_ip, $orig_master_port are passed.
# If you manage master ip address at global catalog database,
# invalidate orig_master_ip here.
my $exit_code = 1;
eval {
print "\n\n\n***************************************************************\n";
print "Disabling the VIP - $vip on old master: $orig_master_host\n";
print "***************************************************************\n\n\n\n";
&stop_vip();
$exit_code = 0;
};
if ($@) {
warn "Got Error: $@\n";
exit $exit_code;
}
exit $exit_code;
}
elsif ( $command eq "start" ) {
# all arguments are passed.
# If you manage master ip address at global catalog database,
# activate new_master_ip here.
# You can also grant write access (create user, set read_only=0, etc) here.
my $exit_code = 10;
eval {
print "\n\n\n***************************************************************\n";
print "Enabling the VIP - $vip on new master: $new_master_host \n";
print "***************************************************************\n\n\n\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";
`ssh $orig_master_ssh_user\@$orig_master_host \" $ssh_start_vip \"`;
exit 0;
}
else {
&usage();
exit 1;
}
}
# A simple system call that enable the VIP on the new master
sub start_vip() {
`ssh $new_master_ssh_user\@$new_master_host \" $ssh_start_vip \"`;
}
# A simple system call that disable the VIP on the old_master
sub stop_vip() {
`ssh $orig_master_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端现在没有vip,所有先在master端添加vip,若不添加vip会报错,server4查看可见此时master为server2
手动切换时需关闭MHA
masterha_check_status --conf=/etc/masterha/app1.cnf
手动将master切换为server3,先在server3查看是否有vip
在server4上进行切换
masterha_master_switch --conf=/etc/masterha/app1.cnf --master_state=alive --new_master_host=172.25.254.3 --new_master_port=3306 --orig_master_is_new_slave --running_updates_limit=10000