MHA(Master High Availability)目前在MySQL高可用方面是一个相对成熟的解决方案,它由日本DeNA公司youshimaton(现就任于Facebook公司)开发,是一套优秀的做为MySQL高可用性环境下故障切换和主从提高的高可用软件。在MySQL故障切换过程当中,MHA能作到在0~30秒以内自动完成数据库的故障切换操做,而且在进行故障切换的过程当中,MHA能在最大程度上保证数据的一致性,以达到真正意义上的高可用。html
该软件由两部分组成:MHA Manager(管理节点)和MHA Node(数据节点)。MHA Manager能够单独部署在一台独立的机器上管理多个master-slave集群,也能够部署在一台slave节点上。MHA Node运行在每台MySQL服务器上,MHA Manager会定时探测集群中的master节点,当master出现故障时,它能够自动将最新数据的slave提高为新的master,而后将全部其余的slave从新指向新的master。整个故障转移过程对应用程序彻底透明。node
在MHA自动故障切换过程当中,MHA试图从宕机的主服务器上保存二进制日志,最大程度的保证数据的不丢失,但这并不老是可行的。例如,若是主服务器硬件故障或没法经过ssh访问,MHA无法保存二进制日志,只进行故障转移而丢失了最新的数据。使用MySQL 5.5的半同步复制,能够大大下降数据丢失的风险。MHA能够与半同步复制结合起来。若是只有一个slave已经收到了最新的二进制日志,MHA能够将最新的二进制日志应用于其余全部的slave服务器上,所以能够保证全部节点的数据一致性。mysql
目前MHA主要支持一主多从的架构,要搭建MHA,要求一个复制集群中必须最少有三台数据库服务器,一主二从,即一台充当master,一台充当备用master,另一台充当从库,由于至少须要三台服务器,出于机器成本的考虑,淘宝也在该基础上进行了改造,目前淘宝TMHA已经支持一主一从。(出自:《深刻浅出MySQL(第二版)》)linux
官方介绍:https://code.google.com/p/mysql-master-ha/redis
下图展现了如何经过MHA Manager管理多组主从复制。sql
能够将MHA工做原理总结为以下:shell
(1)从宕机崩溃的master保存二进制日志事件(binlog events); (2)识别含有最新更新的slave; (3)应用差别的中继日志(relay log)到其余的slave; (4)应用从master保存的二进制日志事件(binlog events); (5)提高一个slave为新的master; (6)使其余的slave链接新的master进行复制;
MHA软件由两部分组成,Manager工具包和Node工具包,具体的说明以下。数据库
Manager工具包主要包括如下几个工具:bash
masterha_check_ssh 检查MHA的SSH配置情况
masterha_check_repl 检查MySQL复制情况
masterha_manger 启动MHA
masterha_check_status 检测当前MHA运行状态
masterha_master_monitor 检测master是否宕机
masterha_master_switch 控制故障转移(自动或者手动)
masterha_conf_host 添加或删除配置的server信息
Node工具包(这些工具一般由MHA Manager的脚本触发,无需人为操做)主要包括如下几个工具:服务器
save_binary_logs 保存和复制master的二进制日志
apply_diff_relay_logs 识别差别的中继日志事件并将其差别的事件应用于其余的slave
filter_mysqlbinlog 去除没必要要的ROLLBACK事件(MHA已再也不使用这个工具)
purge_relay_logs 清除中继日志(不会阻塞SQL线程)
注意:为了尽量的减小主库硬件损坏宕机形成的数据丢失,所以在配置MHA的同时建议配置成MySQL 5.5的半同步复制。关于半同步复制原理各位本身进行查阅。(不是必须)
时间同步(同步后确认各服务器时间是否一致,不一致须要修改一下时区)
关闭防火墙
安装MySQL数据库(实验环境为MySQL5.6)
软件包连接:https://pan.baidu.com/s/1o934VZc
在master-db1 192.168.1.11上操做:
[root@master-db1 ~]# echo -e "\n" |ssh-keygen -t dsa -N "" [root@master-db1 ~]# ssh-copy-id -i .ssh/id_dsa.pub root@192.168.1.12 [root@master-db1 ~]# ssh-copy-id -i .ssh/id_dsa.pub root@192.168.1.13 [root@master-db1 ~]# ssh-copy-id -i .ssh/id_dsa.pub root@192.168.1.14
另外三台按照上面方法配置便可
注意:binlog-do-db 和 replicate-ignore-db 设置必须相同。 MHA 在启动时候会检测过滤规则,若是过滤规则不一样,MHA 不启动监控和故障转移。
1.备份主库数据
[root@master-db1 ~]# mysqldump --master-data=2 --single-transaction -R --triggers -A > all.sql
2.在Master 192.168.1.11和Candicate master 192.168.1.12上建立复制用户(slave若是配置为no-master能够不建立,不然也应当建立复制用户):
mysql> grant replication slave on *.* to 'repl'@'192.168.1.%' identified by '123456'; mysql> flush privileges;
3.查看主库备份时的binlog名称和位置:
mysql> show master status; +------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000002 | 407 | | | |
+------------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)
4.把备份复制到192.168.1.12和192.168.1.13
[root@master-db1 ~]# scp all.sql 192.168.1.12:/root [root@master-db1 ~]# scp all.sql 192.168.1.13:/root
5.分别在两台服务器上导入备份
[root@slave-db1 ~]# mysql < all.sql [root@slave-db2 ~]# mysql < all.sql
6.分别在两台服务器上执行复制相关命令
mysql> CHANGE MASTER TO MASTER_HOST='192.168.1.11',MASTER_USER='repl', MASTER_PASSWORD='123456',MASTER_LOG_FILE='mysql-bin.000002',MASTER_LOG_POS=407; Query OK, 0 rows affected, 2 warnings (0.12 sec) mysql> start slave; Query OK, 0 rows affected (0.08 sec) mysql> show slave status\G; *************************** 1. row *************************** Slave_IO_State: Waiting for master to send event Master_Host: 192.168.1.11 Master_User: repl Master_Port: 3306 Connect_Retry: 60 Master_Log_File: mysql-bin.000002 Read_Master_Log_Pos: 407 Relay_Log_File: relay-log.000002 Relay_Log_Pos: 283 Relay_Master_Log_File: mysql-bin.000002 Slave_IO_Running: Yes Slave_SQL_Running: Yes
7.建立mha管理的帐号,在全部mysql服务器上都须要执行:
mysql> grant all privileges on *.* to 'root'@'192.168.1.%' identified by '123456'; mysql> flush privileges;
若是是在slave服务器上安装的manager,则须要建立以本机hostname名链接的帐号,否则masterha_check_repl测试通不过。
GRANT ALL PRIVILEGES ON *.* TO 'root'@'master(主机名)' IDENTIFIED BY '123456'
1.安装MHA的Perl依赖包
在全部的mysql(192.168.1.11-13)上安装
[root@master-db1 ~]# yum install perl-DBD-MySQL -y [root@slave-db1 ~]# yum install perl-DBD-MySQL -y [root@slave-db2 ~]# yum install perl-DBD-MySQL -y
在mha-monitor(192.168.1.14)上安装MHA Manger依赖的perl模块
[root@mha-monitor ~]# yum install perl-DBD-MySQL perl-Config-Tiny perl-Log-Dispatch perl-Parallel-ForkManager perl-Time-HiRes -y
2.在全部的服务器(192.168.1.11-14)上安装MHA Node软件包
[root@master-db1 ~]# tar xf mha4mysql-node-0.56.tar.gz [root@master-db1 ~]# cd mha4mysql-node-0.56 [root@master-db1 mha4mysql-node-0.56]# perl Makefile.PL *** Module::AutoInstall version 1.03
*** Checking for Perl dependencies... [Core Features] - DBI ...loaded. (1.609) - DBD::mysql ...loaded. (4.013) *** Module::AutoInstall configuration finished. Checking if your kit is complete... Looks good Writing Makefile for mha4mysql::node [root@master-db1 mha4mysql-node-0.56]# make &&make install
3.在mha-monitor(192.168.1.14)上安装MHA Manager软件包
[root@mha-monitor ~]# tar xf mha4mysql-manager-0.56.tar.gz [root@mha-monitor ~]# cd mha4mysql-manager-0.56 [root@mha-monitor mha4mysql-manager-0.56]# perl Makefile.PL *** Module::AutoInstall version 1.03
*** Checking for Perl dependencies... [Core Features] - DBI ...loaded. (1.609) - DBD::mysql ...loaded. (4.013) - Time::HiRes ...loaded. (1.9721) - Config::Tiny ...loaded. (2.12) - Log::Dispatch ...loaded. (2.26) - Parallel::ForkManager ...loaded. (0.7.9) - MHA::NodeConst ...missing. ==> Auto-install the 1 mandatory module(s) from CPAN? [y] y *** Dependencies will be installed the next time you type 'make'. *** Module::AutoInstall configuration finished. Checking if your kit is complete... Looks good Warning: prerequisite MHA::NodeConst 0 not found. Writing Makefile for mha4mysql::manager [root@mha-monitor mha4mysql-manager-0.56]# make &&make install
安装完成后会在/usr/local/bin目录下面生成如下脚本文件,前面已经说过这些脚本的做用,这里再也不重复
[root@mha-monitor mha4mysql-manager-0.56]# ll /usr/local/bin 总用量 124
-r-xr-xr-x 1 root root 16367 1月 17 22:28 apply_diff_relay_logs -r-xr-xr-x 1 root root 4807 1月 17 22:28 filter_mysqlbinlog -r-xr-xr-x 1 root root 1995 1月 17 22:29 masterha_check_repl -r-xr-xr-x 1 root root 1779 1月 17 22:29 masterha_check_ssh -r-xr-xr-x 1 root root 1865 1月 17 22:29 masterha_check_status -r-xr-xr-x 1 root root 3201 1月 17 22:29 masterha_conf_host -r-xr-xr-x 1 root root 2517 1月 17 22:29 masterha_manager -r-xr-xr-x 1 root root 2165 1月 17 22:29 masterha_master_monitor -r-xr-xr-x 1 root root 2373 1月 17 22:29 masterha_master_switch -r-xr-xr-x 1 root root 5171 1月 17 22:29 masterha_secondary_check -r-xr-xr-x 1 root root 1739 1月 17 22:29 masterha_stop -r-xr-xr-x 1 root root 8261 1月 17 22:28 purge_relay_logs -r-xr-xr-x 1 root root 7525 1月 17 22:28 save_binary_logs
在/root/mha4mysql-manager-0.56/samples/scripts/下有些示例脚本复制到/usr/local/bin/下,这些脚本不完整,须要本身修改,这是软件开发着留给咱们本身发挥的,若是开启下面的任何一个脚本对应的参数,而对应这里的脚本又没有修改,则会抛错
[root@mha-monitor mha4mysql-manager-0.56]# ll /root/mha4mysql-manager-0.56/samples/scripts/ 总用量 32 -rwxr-xr-x 1 4984 users 3648 4月 1 2014 master_ip_failover #自动切换时vip管理的脚本,不是必须,若是咱们使用keepalived的,咱们能够本身编写脚本完成对vip的管理,好比监控mysql,若是mysql异常,咱们中止keepalived就行,这样vip就会自动漂移 -rwxr-xr-x 1 4984 users 9870 4月 1 2014 master_ip_online_change #在线切换时vip的管理,不是必须,一样能够能够自行编写简单的shell完成 -rwxr-xr-x 1 4984 users 11867 4月 1 2014 power_manager #故障发生后关闭主机的脚本,不是必须 -rwxr-xr-x 1 4984 users 1360 4月 1 2014 send_report #因故障切换后发送报警的脚本,不是必须,可自行编写简单的shell完成。
[root@mha-monitor scripts]# cp /root/mha4mysql-manager-0.56/samples/scripts/* /usr/local/bin/
1.建立MHA的工做目录,而且建立相关配置文件(在软件包解压后的目录里面有样例配置文件)。
[root@mha-monitor ~]# mkdir -p /etc/masterha [root@mha-monitor ~]# cp /root/mha4mysql-manager-0.56/samples/conf/app1.cnf /etc/masterha/ [root@mha-monitor ~]# ll /etc/masterha/ 总用量 4
-rw-r--r-- 1 root root 257 1月 17 22:40 app1.cnf
2.修改app1.cnf配置文件,修改后的文件内容以下
[server default] manager_log=/var/log/masterha/app1/manager.log //设置manager的日志
manager_workdir=/var/log/masterha/app1 //设置manager的工做目录
master_binlog_dir=/Data/apps/mysql-5.6.36/data/ //设置master 保存binlog的位置,以便MHA能够找到master的日志,我这里的也就是mysql的数据目录
master_ip_failover_script=/usr/local/bin/master_ip_failover //设置自动failover时候的切换脚本
master_ip_online_change_script=/usr/local/bin/master_ip_online_change //设置手动切换时候的切换脚本
password=123456//设置mysql中root用户的密码,这个密码是前文中建立监控用户的那个密码
user=root //设置监控用户root
ping_interval=1 //设置监控主库,发送ping包的时间间隔,默认是3秒,尝试三次没有回应的时候自动进行railover
remote_workdir=/tmp//设置远端mysql在发生切换时binlog的保存位置
repl_password=123456 //设置复制用户的密码
repl_user=repl//设置复制用户
report_script=/usr/local/send_report //设置发生切换后发送的报警的脚本
secondary_check_script=/usr/local/bin/masterha_secondary_check -s 192.168.1.11 -s 192.168.1.12 #实现多路由监测Master的可用性 shutdown_script="" //设置故障发生后关闭故障主机脚本(该脚本的主要做用是关闭主机放在发生脑裂,这里没有使用)
ssh_user=root //设置ssh的登陆用户名
[server1] hostname=192.168.1.11 port=3306 [server2] candidate_master=1 //设置为候选master,若是设置该参数之后,发生主从切换之后将会将此从库提高为主库,即便这个主库不是集群中事件最新的slave
check_repl_delay=0 //默认状况下若是一个slave落后master 100M的relay logs的话,MHA将不会选择该slave做为一个新的master,由于对于这个slave的恢复须要花费很长时间,经过设置check_repl_delay=0,MHA触发切换在选择一个新的master的时候将会忽略复制延时,这个参数对于设置了candidate_master=1的主机很是有用,由于这个候选主在切换的过程当中必定是新的master
hostname=192.168.1.12 port=3306 [server3] hostname=192.168.1.13 port=3306 no_master=1
3.设置relay log的清除方式(在每一个slave节点上):
[root@slave-db1 ~]# mysql -e 'set global relay_log_purge=0' [root@slave-db2 ~]# mysql -e 'set global relay_log_purge=0'
注意:
MHA在发生切换的过程当中,从库的恢复过程当中依赖于relay log的相关信息,因此这里要将relay log的自动清除设置为OFF,采用手动清除relay log的方式。在默认状况下,从服务器上的中继日志会在SQL线程执行完毕后被自动删除。可是在MHA环境中,这些中继日志在恢复其余从服务器时可能会被用到,所以须要禁用中继日志的自动删除功能。按期清除中继日志须要考虑到复制延时的问题。在ext3的文件系统下,删除大的文件须要必定的时间,会致使严重的复制延时。为了不复制延时,须要暂时为中继日志建立硬连接,由于在linux系统中经过硬连接删除大文件速度会很快。(在mysql数据库中,删除大表时,一般也采用创建硬连接的方式)
MHA节点中包含了pure_relay_logs命令工具,它能够为中继日志建立硬连接,执行SET GLOBAL relay_log_purge=1,等待几秒钟以便SQL线程切换到新的中继日志,再执行SET GLOBAL relay_log_purge=0。
pure_relay_logs脚本参数以下所示:
--user mysql 用户名 --password mysql 密码 --port 端口号 --workdir 指定建立relay log的硬连接的位置,默认是/var/tmp,因为系统不一样分区建立硬连接文件会失败,故须要执行硬连接具体位置,成功执行脚本后,硬连接的中继日志文件被删除 --disable_relay_log_purge 默认状况下,若是relay_log_purge=1,脚本会什么都不清理,自动退出,经过设定这个参数,当relay_log_purge=1的状况下会将relay_log_purge设置为0。清理relay log以后,最后将参数设置为OFF。
设置按期清理relay脚本(两台slave服务器)
[root@192.168.1.12 ~]# cat purge_relay_log.sh #!/bin/bash user=root passwd=123456 port=3306 log_dir='/data/masterha/log' work_dir='/Data/apps' purge='/usr/local/bin/purge_relay_logs'
if [ ! -d $log_dir ] then mkdir $log_dir -p fi $purge --user=$user --password=$passwd --disable_relay_log_purge --port=$port --workdir=$work_dir >> $log_dir/purge_relay_logs.log 2>&1
添加执行权限,并添加到crontab按期执行,另一台相同操做
[root@slave-db1 ~]#chmod +x purge_relay_log.sh [root@slave-db1 ~]#crontab -l 0 4 * * * /bin/bash /root/purge_relay_log.sh
purge_relay_logs脚本删除中继日志不会阻塞SQL线程。下面咱们手动执行看看什么状况。
[root@slave-db1 ~]# purge_relay_logs --user=root --password=123456 --port=3306 --host=192.168.1.12 -disable_relay_log_purge --workdir=/Data/apps/
2018-01-17 23:07:59: purge_relay_logs script started. Found relay_log.info: /Data/apps/mysql-5.6.36/data/relay-log.info Opening /Data/apps/mysql-5.6.36/data/relay-log.000001 .. Opening /Data/apps/mysql-5.6.36/data/relay-log.000002 .. Executing SET GLOBAL relay_log_purge=1; FLUSH LOGS; sleeping a few seconds so that SQL thread can delete older relay log files (if it keeps up); SET GLOBAL relay_log_purge=0; .. ok. 2018-01-17 23:08:02: All relay log purging operations succeeded.
4.因为自带的脚本master_ip_failover有些问题须要自行修改,修改内容以下:
#!/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 = '192.168.1.250/24'; # Virtual IP my $gateway = '192.168.1.1'; #Gateway IP my $interface = 'eth0'; my $key = "1"; my $ssh_start_vip = "/sbin/ifconfig $interface:$key $vip;/sbin/arping -I $interface -c 3 -s $vip $gateway >/dev/null 2>&1"; my $ssh_stop_vip = "/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" ) { #$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 "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" ) { # 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 "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"; `ssh $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 $ssh_user\@$new_master_host \" $ssh_start_vip \"`;
} # A simple system call that disable the VIP on the old_master sub stop_vip() { `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"; }
5.检查SSH配置
[root@mha-monitor ~]# masterha_check_ssh --conf=/etc/masterha/app1.cnf Wed Jan 17 23:13:30 2018 - [warning] Global configuration file /etc/masterha_default.cnf not found. Skipping. Wed Jan 17 23:13:30 2018 - [info] Reading application default configuration from /etc/masterha/app1.cnf.. Wed Jan 17 23:13:30 2018 - [info] Reading server configuration from /etc/masterha/app1.cnf.. Wed Jan 17 23:13:30 2018 - [info] Starting SSH connection tests.. Wed Jan 17 23:13:33 2018 - [debug] Wed Jan 17 23:13:30 2018 - [debug] Connecting via SSH from root@192.168.1.11(192.168.1.11:22) to root@192.168.1.12(192.168.1.12:22).. Wed Jan 17 23:13:32 2018 - [debug] ok. Wed Jan 17 23:13:32 2018 - [debug] Connecting via SSH from root@192.168.1.11(192.168.1.11:22) to root@192.168.1.13(192.168.1.13:22).. Wed Jan 17 23:13:33 2018 - [debug] ok. Wed Jan 17 23:13:33 2018 - [debug] Wed Jan 17 23:13:31 2018 - [debug] Connecting via SSH from root@192.168.1.12(192.168.1.12:22) to root@192.168.1.11(192.168.1.11:22).. Wed Jan 17 23:13:32 2018 - [debug] ok. Wed Jan 17 23:13:32 2018 - [debug] Connecting via SSH from root@192.168.1.12(192.168.1.12:22) to root@192.168.1.13(192.168.1.13:22).. Wed Jan 17 23:13:33 2018 - [debug] ok. Wed Jan 17 23:13:33 2018 - [debug] Wed Jan 17 23:13:31 2018 - [debug] Connecting via SSH from root@192.168.1.13(192.168.1.13:22) to root@192.168.1.11(192.168.1.11:22).. Wed Jan 17 23:13:33 2018 - [debug] ok. Wed Jan 17 23:13:33 2018 - [debug] Connecting via SSH from root@192.168.1.13(192.168.1.13:22) to root@192.168.1.12(192.168.1.12:22).. Wed Jan 17 23:13:33 2018 - [debug] ok. Wed Jan 17 23:13:33 2018 - [info] All SSH connection tests passed successfully.
能够看见各个节点ssh验证都是ok的。
6.检查整个复制环境情况。
[root@mha-monitor ~]# masterha_check_repl --conf=/etc/masterha/app1.cnf ..... Checking the Status of the script.. OK Wed Jan 17 23:18:04 2018 - [info] OK. Wed Jan 17 23:18:04 2018 - [warning] shutdown_script is not defined. Wed Jan 17 23:18:04 2018 - [info] Got exit code 0 (Not master dead). MySQL Replication Health is OK.
7.开启MHA Manager监控
[root@mha-monitor ~]# nohup masterha_manager --conf=/etc/masterha/app1.cnf --remove_dead_master_conf --ignore_last_failover /var/log/masterha/app1/manager.log 2>&1 & [1] 7191 [root@mha-monitor ~]# nohup: 忽略输入并把输出追加到"nohup.out" [root@mha-monitor ~]# jobs [1]+ Running nohup masterha_manager --conf=/etc/masterha/app1.cnf --remove_dead_master_conf --ignore_last_failover /var/log/masterha/app1/manager.log 2>&1 &
启动参数介绍:
--remove_dead_master_conf 该参数表明当发生主从切换后,老的主库的ip将会从配置文件中移除。 --manger_log 日志存放位置 --ignore_last_failover 在缺省状况下,若是MHA检测到连续发生宕机,且两次宕机间隔不足8小时的话,则不会进行Failover,之因此这样限制是为了不ping-pong效应。该参数表明忽略上次MHA触发切换产生的文件,默认状况下,MHA发生切换后会在日志目录,也就是上面我设置的/data产生app1.failover.complete文件,下次再次切换的时候若是发现该目录下存在该文件将不容许触发切换,除非在第一次切换后收到删除该文件,为了方便,这里设置为--ignore_last_failover。
8.查看MHA Manager监控状态:
[root@mha-monitor ~]# masterha_check_status --conf=/etc/masterha/app1.cnf app1 (pid:7191) is running(0:PING_OK), master:192.168.1.11
能够看见已经在监控了,并且master的主机为192.168.1.11
1.模拟MySQL故障,查看VIP漂移和MySQL自动切换状况
注:切换后MHA服务会自动中止,官方给出的缘由是
Running MHA Manager from daemontools Currently MHA Manager process does not run as a daemon. If failover completed successfully or the master process was killed by accident,
the manager stops working. To run as a daemon, daemontool. or any external daemon program can be used.
Here is an example to run from daemontools.
master上中止mysql服务器
[root@master-db1 ~]# service mysqld stop Shutting down MySQL..... [肯定]
在manager上查看MHA服务和切换日志
[root@mha-monitor ~]# masterha_check_status --conf=/etc/masterha/app1.cnf app1 is stopped(2:NOT_RUNNING). [1]+ Done nohup masterha_manager --conf=/etc/masterha/app1.cnf --remove_dead_master_conf --ignore_last_failover /var/log/masterha/app1/manager.log 2>&1 [root@mha-monitor ~]# tail -20 /var/log/masterha/app1/manager.log ----- Failover Report ----- app1: MySQL Master failover 192.168.1.11(192.168.1.11:3306) to 192.168.1.12(192.168.1.12:3306) succeeded Master 192.168.1.11(192.168.1.11:3306) is down! Check MHA Manager logs at mha-monitor:/var/log/masterha/app1/manager.log for details. Started automated(non-interactive) failover. Invalidated master IP address on 192.168.1.11(192.168.1.11:3306) The latest slave 192.168.1.12(192.168.1.12:3306) has all relay logs for recovery. Selected 192.168.1.12(192.168.1.12:3306) as a new master. 192.168.1.12(192.168.1.12:3306): OK: Applying all logs succeeded. 192.168.1.12(192.168.1.12:3306): OK: Activated master IP address. 192.168.1.13(192.168.1.13:3306): This host has the latest relay log events. Generating relay diff files from the latest slave succeeded. 192.168.1.13(192.168.1.13:3306): OK: Applying all logs succeeded. Slave started, replicating from 192.168.1.12(192.168.1.12:3306) 192.168.1.12(192.168.1.12:3306): Resetting slave info succeeded. Master failover to 192.168.1.12(192.168.1.12:3306) completed successfully.
看到最后的Master failover to 192.168.1.12(192.168.1.12:3306) completed successfully.说明备选master如今已经上位了。
从上面的输出能够看出整个MHA的切换过程,共包括如下的步骤:
1.配置文件检查阶段,这个阶段会检查整个集群配置文件配置
2.宕机的master处理,这个阶段包括虚拟ip摘除操做,主机关机操做(这个我这里尚未实现,须要研究)
3.复制dead maste和最新slave相差的relay log,并保存到MHA Manger具体的目录下
4.识别含有最新更新的slave
5.应用从master保存的二进制日志事件(binlog events)
6.提高一个slave为新的master进行复制
7.使其余的slave链接新的master进行复制
在slave-db2上查看主从复制状况(192.168.1.13)
mysql> show slave status\G;ges; *************************** 1. row *************************** Slave_IO_State: Waiting for master to send event Master_Host: 192.168.1.12 Master_User: repl Master_Port: 3306 Connect_Retry: 60 Master_Log_File: mysql-bin.000001 Read_Master_Log_Pos: 635947 Relay_Log_File: relay-log.000002 Relay_Log_Pos: 283 Relay_Master_Log_File: mysql-bin.000001 Slave_IO_Running: Yes Slave_SQL_Running: Yes
启动MHA Manger监控,查看集群里面如今谁是master
2.将MySQL故障服务器从新加入MHA环境步骤
1.把故障服务器设为新的slave 2.从新启动MHA manager 3.查看MHA状态
3在线手动切换主从
在许多状况下, 须要将现有的主服务器迁移到另一台服务器上。 好比主服务器硬件故障,RAID 控制卡须要重建,将主服务器移到性能更好的服务器上等等。维护主服务器引发性能降低, 致使停机时间至少没法写入数据。 另外, 阻塞或杀掉当前运行的会话会致使主主之间数据不一致的问题发生。 MHA 提供快速切换和优雅的阻塞写入,这个切换过程只须要 0.5-2s 的时间,这段时间内数据是没法写入的。在不少状况下,0.5-2s 的阻塞写入是能够接受的。所以切换主服务器不须要计划分配维护时间窗口。
MHA在线切换的大概过程:
1.检测复制设置和肯定当前主服务器 2.肯定新的主服务器 3.阻塞写入到当前主服务器 4.等待全部从服务器遇上复制 5.授予写入到新的主服务器 6.从新设置从服务器
注意,在线切换的时候应用架构须要考虑如下两个问题
1.自动识别master和slave的问题(master的机器可能会切换),若是采用了vip的方式,基本能够解决这个问题。 2.负载均衡的问题(能够定义大概的读写比例,每台机器可承担的负载比例,当有机器离开集群时,须要考虑这个问题)
在线切换步骤以下:
1.原master出现故障 masterha_stop --conf=/etc/masterha/app1.cnf #中止 masterha_master_switch --master_state=dead --conf=/etc/masterha/app1.cnf --dead_master_host=192.168.1.11 --dead_master_port=3306 --new_master_host=192.168.1.12 --new_master_port=3306 --ignore_last_failover
2.把原master变为slave切换
masterha_master_switch --conf=/etc/masterha/app1.cnf --master_state=alive --new_master_host=192.168.1.12 --new_master_port=3306 --orig_master_is_new_slave
注意:因为在线进行切换须要调用到master_ip_online_change这个脚本,可是因为该脚本不完整,须要本身进行相应的修改,我google到后发现仍是有问题,脚本中new_master_password这个变量获取不到,致使在线切换失败,因此进行了相关的硬编码,直接把mysql的root用户密码赋值给变量new_master_password,若是有哪位大牛知道缘由,请指点指点。这个脚本还能够管理vip。下面贴出脚本:
#!/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. use strict; use warnings FATAL => 'all'; use Getopt::Long; use MHA::DBHelper; use MHA::NodeUtil; use Time::HiRes qw( sleep gettimeofday tv_interval ); use Data::Dumper; my $_tstart; my $_running_interval = 0.1; my ( $command, $orig_master_host, $orig_master_ip, $orig_master_port, $orig_master_user, $new_master_host, $new_master_ip, $new_master_port, $new_master_user, ); my $vip = '192.168.0.88/24'; # Virtual IP my $key = "1"; my $ssh_start_vip = "/sbin/ifconfig eth1:$key $vip"; my $ssh_stop_vip = "/sbin/ifconfig eth1:$key down"; my $ssh_user = "root"; my $new_master_password='123456'; my $orig_master_password='123456'; 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, 'orig_master_user=s' => \$orig_master_user, #'orig_master_password=s' => \$orig_master_password, '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, ); exit &main(); sub current_time_us { my ( $sec, $microsec ) = gettimeofday(); my $curdate = localtime($sec); return $curdate . " " . sprintf( "%06d", $microsec ); } sub sleep_until { my $elapsed = tv_interval($_tstart); if ( $_running_interval > $elapsed ) { sleep( $_running_interval - $elapsed ); } } sub get_threads_util { my $dbh = shift; my $my_connection_id = shift; my $running_time_threshold = shift; my $type = shift; $running_time_threshold = 0 unless ($running_time_threshold); $type = 0 unless ($type); my @threads; my $sth = $dbh->prepare("SHOW PROCESSLIST"); $sth->execute(); while ( my $ref = $sth->fetchrow_hashref() ) { my $id = $ref->{Id}; my $user = $ref->{User}; my $host = $ref->{Host}; my $command = $ref->{Command}; my $state = $ref->{State}; my $query_time = $ref->{Time}; my $info = $ref->{Info}; $info =~ s/^\s*(.*?)\s*$/$1/ if defined($info); next if ( $my_connection_id == $id ); next if ( defined($query_time) && $query_time < $running_time_threshold ); next if ( defined($command) && $command eq "Binlog Dump" ); next if ( defined($user) && $user eq "system user" ); next if ( defined($command) && $command eq "Sleep" && defined($query_time) && $query_time >= 1 ); if ( $type >= 1 ) { next if ( defined($command) && $command eq "Sleep" ); next if ( defined($command) && $command eq "Connect" ); } if ( $type >= 2 ) { next if ( defined($info) && $info =~ m/^select/i ); next if ( defined($info) && $info =~ m/^show/i ); } push @threads, $ref; } return @threads; } sub main { if ( $command eq "stop" ) { ## Gracefully killing connections on the current master # 1. Set read_only= 1 on the new master # 2. DROP USER so that no app user can establish new connections # 3. Set read_only= 1 on the current master # 4. Kill current queries # * Any database access failure will result in script die. my $exit_code = 1; eval { ## Setting read_only=1 on the new master (to avoid accident) my $new_master_handler = new MHA::DBHelper(); # args: hostname, port, user, password, raise_error(die_on_error)_or_not $new_master_handler->connect( $new_master_ip, $new_master_port, $new_master_user, $new_master_password, 1 ); print current_time_us() . " Set read_only on the new master.. "; $new_master_handler->enable_read_only(); if ( $new_master_handler->is_read_only() ) { print "ok.\n"; } else { die "Failed!\n"; } $new_master_handler->disconnect(); # Connecting to the orig master, die if any database error happens my $orig_master_handler = new MHA::DBHelper(); $orig_master_handler->connect( $orig_master_ip, $orig_master_port, $orig_master_user, $orig_master_password, 1 ); ## Drop application user so that nobody can connect. Disabling per-session binlog beforehand #$orig_master_handler->disable_log_bin_local(); #print current_time_us() . " Drpping app user on the orig master..\n"; #FIXME_xxx_drop_app_user($orig_master_handler); ## Waiting for N * 100 milliseconds so that current connections can exit my $time_until_read_only = 15; $_tstart = [gettimeofday]; my @threads = get_threads_util( $orig_master_handler->{dbh}, $orig_master_handler->{connection_id} ); while ( $time_until_read_only > 0 && $#threads >= 0 ) { if ( $time_until_read_only % 5 == 0 ) { printf "%s Waiting all running %d threads are disconnected.. (max %d milliseconds)\n", current_time_us(), $#threads + 1, $time_until_read_only * 100; if ( $#threads < 5 ) { print Data::Dumper->new( [$_] )->Indent(0)->Terse(1)->Dump . "\n" foreach (@threads); } } sleep_until(); $_tstart = [gettimeofday]; $time_until_read_only--; @threads = get_threads_util( $orig_master_handler->{dbh}, $orig_master_handler->{connection_id} ); } ## Setting read_only=1 on the current master so that nobody(except SUPER) can write print current_time_us() . " Set read_only=1 on the orig master.. "; $orig_master_handler->enable_read_only(); if ( $orig_master_handler->is_read_only() ) { print "ok.\n"; } else { die "Failed!\n"; } ## Waiting for M * 100 milliseconds so that current update queries can complete my $time_until_kill_threads = 5; @threads = get_threads_util( $orig_master_handler->{dbh}, $orig_master_handler->{connection_id} ); while ( $time_until_kill_threads > 0 && $#threads >= 0 ) { if ( $time_until_kill_threads % 5 == 0 ) { printf "%s Waiting all running %d queries are disconnected.. (max %d milliseconds)\n", current_time_us(), $#threads + 1, $time_until_kill_threads * 100; if ( $#threads < 5 ) { print Data::Dumper->new( [$_] )->Indent(0)->Terse(1)->Dump . "\n" foreach (@threads); } } sleep_until(); $_tstart = [gettimeofday]; $time_until_kill_threads--; @threads = get_threads_util( $orig_master_handler->{dbh}, $orig_master_handler->{connection_id} ); } print "Disabling the VIP on old master: $orig_master_host \n"; &stop_vip(); ## Terminating all threads print current_time_us() . " Killing all application threads..\n"; $orig_master_handler->kill_threads(@threads) if ( $#threads >= 0 ); print current_time_us() . " done.\n"; #$orig_master_handler->enable_log_bin_local(); $orig_master_handler->disconnect(); ## After finishing the script, MHA executes FLUSH TABLES WITH READ LOCK $exit_code = 0; }; if ($@) { warn "Got Error: $@\n"; exit $exit_code; } exit $exit_code; } elsif ( $command eq "start" ) { ## Activating master ip on the new master # 1. Create app user with write privileges # 2. Moving backup script if needed # 3. Register new master's ip to the catalog database # We don't return error even though activating updatable accounts/ip failed so that we don't interrupt slaves' recovery. # If exit code is 0 or 10, MHA does not abort my $exit_code = 10; eval { my $new_master_handler = new MHA::DBHelper(); # args: hostname, port, user, password, raise_error_or_not $new_master_handler->connect( $new_master_ip, $new_master_port, $new_master_user, $new_master_password, 1 ); ## Set read_only=0 on the new master #$new_master_handler->disable_log_bin_local(); print current_time_us() . " Set read_only=0 on the new master.\n"; $new_master_handler->disable_read_only(); ## Creating an app user on the new master #print current_time_us() . " Creating app user on the new master..\n"; #FIXME_xxx_create_app_user($new_master_handler); #$new_master_handler->enable_log_bin_local(); $new_master_handler->disconnect(); ## Update master ip on the catalog database, etc print "Enabling the VIP - $vip on the new master - $new_master_host \n"; &start_vip(); $exit_code = 0; }; if ($@) { warn "Got Error: $@\n"; exit $exit_code; } exit $exit_code; } elsif ( $command eq "status" ) { # do nothing exit 0; } else { &usage(); exit 1; } } # A simple system call that enable the VIP on the new master sub start_vip() { `ssh $ssh_user\@$new_master_host \" $ssh_start_vip \"`; } # A simple system call that disable the VIP on the old_master sub stop_vip() { `ssh $ssh_user\@$orig_master_host \" $ssh_stop_vip \"`; } sub usage { print "Usage: master_ip_online_change --command=start|stop|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"; die; }
为了保证数据彻底一致性,在最快的时间内完成切换,MHA的在线切换必须知足如下条件才会切换成功,不然会切换失败。
1.全部slave的IO线程都在运行 2.全部slave的SQL线程都在运行 3.全部的show slave status的输出中Seconds_Behind_Master参数小于或者等于running_updates_limit秒,若是在切换过程当中不指定running_updates_limit,那么默认状况下running_updates_limit为1秒。 4.在master端,经过show processlist输出,没有一个更新花费的时间大于running_updates_limit秒。
最后补充一下邮件发送脚本send_report
#!/usr/bin/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. use strict; use warnings FATAL => 'all'; use Mail::Sender; use Getopt::Long; #new_master_host and new_slave_hosts are set only when recovering master succeeded my ( $dead_master_host, $new_master_host, $new_slave_hosts, $subject, $body ); my $smtp='smtp.163.com'; my $mail_from='xxxx'; my $mail_user='xxxxx'; my $mail_pass='xxxxx'; my $mail_to=['xxxx','xxxx']; GetOptions( 'orig_master_host=s' => \$dead_master_host, 'new_master_host=s' => \$new_master_host, 'new_slave_hosts=s' => \$new_slave_hosts, 'subject=s' => \$subject, 'body=s' => \$body, ); mailToContacts($smtp,$mail_from,$mail_user,$mail_pass,$mail_to,$subject,$body); sub mailToContacts { my ( $smtp, $mail_from, $user, $passwd, $mail_to, $subject, $msg ) = @_; open my $DEBUG, "> /tmp/monitormail.log" or die "Can't open the debug file:$!\n"; my $sender = new Mail::Sender { ctype => 'text/plain; charset=utf-8', encoding => 'utf-8', smtp => $smtp, from => $mail_from, auth => 'LOGIN', TLS_allowed => '0', authid => $user, authpwd => $passwd, to => $mail_to, subject => $subject, debug => $DEBUG }; $sender->MailMsg( { msg => $msg, debug => $DEBUG } ) or print $Mail::Sender::Error; return 1; } # Do whatever you want here exit 0;
总结:
目前高可用方案能够必定程度上实现数据库的高可用,好比前面文章介绍的MMM,heartbeat+drbd,Cluster等。还有percona的Galera Cluster等。这些高可用软件各有优劣。在进行高可用方案选择时,主要是看业务还有对数据一致性方面的要求。最后出于对数据库的高可用和数据一致性的要求,推荐使用MHA架构。