MySQL高可用-MHA架构搭建

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 node 运行在每台 MySQL 服务器上(master/slave/manager),它通过监控具备解析和清理 logs 功能的脚本来加快故障转移的。

在 MHA 自动故障切换过程中,MHA 试图从宕机的主服务器上保存二进制日志,最大程度的保证数据的不丢失,但这并不总是可行的。例如,如果主服务器硬件故障或无法通过 ssh 访问,MHA 没法保存二进制日志,只进行故障转移而丢失了最新的数据。使用 MySQL 5.5 的半同步复制,可以大大降低数据丢失的风险。MHA 可以与半同步复制结合起来。如果只有一个 slave 已经收到了最新的二进制日志,MHA 可以将最新的二进制日志应用于其他所有的 slave 服务器上,因此它们可以彼此保证数据一致性。

目前 MHA 主要支持一主多从的架构,要搭建 MHA,要求一个复制集群中必须最少有三台数据库服务器,一主二从,即一台充当 master,一台充当备用 master,另外一台充当从库,因为至少需要三台数据库,出于机器成本的考虑,淘宝也在该基础上进行了改造,目前淘宝 TMHA 已经支持一主一从。

MHA 特点
(1)10-30s 实现 master failover(9-12s 可以检测到主机故障,7-10s 可以关闭主机避免 SB,在用很短的时间应用差异日志)
(2) 部署简单,无需对现有 M-S 结构做任何改动(至少 3 台,保证切换后仍保持 M-S 结构)
(3) 支持手动在线切换(主机硬件维护),downtime 几乎很短 0.5-2s
(4) 保证故障切换后多从库数据的一致性
(5) 完全自动化的 failover 及快速复制架构恢复方案(一主多从)
(6) 恢复过程包括:选择新主库、确认从库间 relaylog 差异、新主库应用必要语句、其他从库同步差异语句、重新建立复制连接

优点
(1)出同步最成功的一台从服务器(也就是与主服务器数据最接近的那台从服务器)自动切换成主服务器;
(2)如果主机还能够访问,从主服务器上找回最新从机与主机间的数据差异;
(3)在每一台从服务器上操作,确定他们缺少哪些 events,并分别进行补充;
(4)将最新的一台从服务器提升为主服务器后,将其它从服务器重新指向新的主服务器;

缺点
(1)当群集内的数据库进行故障转移时,对外提供服务的虚拟 IP 也进行转移;
(2)MHA 管理进程需要以后台守护进程的方式运行,并有监控机制保证 MHA 管理进程的正常运行;
(3)有监控机制保证当主机出现故障时,MHA 能确定进行成功的 Failover;
(4)当故障主机恢复后,能重新回到群集中,并成为新的 Slave,自动实现重新同步;
(5)由于主机和从机上备份策略不同,进行故障转移后,自动调整 cron 中的调度(例如全备份);

MHA 集群架构图
MySQL 高可用-MHA 架构搭建

上图示意了如果通过 MHA Manager 管理多组主从复制。可以将 MHA 工作原理总结为:
(1)从宕机崩溃的 master 保存二进制日志事件(binlog events);
(2)识别含有最新更新的 slave;
(3)应用差异的中继日志(relay log)到其他 slave;
(4)应用从 master 保存的二进制日志事件(binlog events)
(5)提升一个 slave 为新 master;
(6)使其他的 slave 连接新的 master 进行复制;

MHA 软件由两部分组成,Manager 工具包和 Node 工具包,说明:
Manager 工具包主要包括以下几个工具
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

1.环境配置

角色IP 地址主机名Server ID类型
Master 192.168.199.134 DB1 1 写入
Candicate master 192.168.199.212 DB2 2
Slave 192.168.199.233 DB3 3
Monitor host,Manager 192.168.199.180 monitor   监控集群组

其中 master 对外提供写服务,备选 master 提供读服务,slave 也提供相关的读服务,一旦 master 宕机,将会把备选 master 提升为新的 master,slave 指向新的 master。
修改 hosts 文件(4 台机器都需要添加)
192.168.199.134 db1
192.168.199.212 db2
192.168.199.233 db3
192.168.199.180 monitor

所需软件及版本

软件版本
系统版本 CentOS Linux release 7.2.1511
系统内核 3.10.0-327.el7.x86_64
mha4mysql-manager 0.56
mha4mysql-node 0.56
MySQL 5.7.18
keepalived 1.3.5
sysbench 1.0.6
daemontools 0.76

2.安装 MHA node

在所有的 MySQL 服务器上安装

(1) 安装 MHA node 所需要的 perl 模块

在 MySQL 服务器上安装 MHA node 所需要的 perl 模块

# yum install perl-DBD-MySQL -y       
# yum install -y perl-devel
# yum install -y perl-CPAN

(2) 在所有的节点上安装 mha node

# wget -P /usr/local/  https://downloads.mariadb.com/MHA/mha4mysql-node-0.56.tar.gz
# tar zxf mha4mysql-node-0.56.tar.gz 
# cd mha4mysql-node-0.56
# perl Makefile.PL 
# make && make install 

安装后会在/usr/bin/下生成一下脚本文件:
MySQL 高可用-MHA 架构搭建

Node 脚本说明:(这些工具通常由 MHA Manager 的脚本触发,无需人为操作)
save_binary_logs //保存和复制 master 的二进制日志
apply_diff_relay_logs //识别差异的中继日志事件并将其差异的事件应用于其他的 slave
filter_mysqlbinlog //去除不必要的 ROLLBACK 事件(MHA 已不再使用这个工具)
purge_relay_logs //清除中继日志(不会阻塞 SQL 线程)

3.安装 MHA Manager (monitor 机器)

MHA Manager 中主要包括了几个管理员的命令行工具,例如 masterha_manager、masterha_master_switch 等。MHA Manager 也是依赖于一些 perl 模块。

(1) 安装 MHA Node 软件包

在 MHA Manager 的主机上也要安装 MHA Node
安装步骤参考上面

(2) 安装 MHA Mnager 软件

安装 MHA Manager 所需要的 Perl 模块:

# yum install perl-DBD-MySQL perl-Config-Tiny perl-Log-Dispatch perl-Parallel-ForkManager perl-Time-HiRes -y

安装 MHA Manager 软件包:

# wget -P /usr/local/ https://downloads.mariadb.com/MHA/mha4mysql-manager-0.56.tar.gz
# tar zxf mha4mysql-manager-0.56.tar.gz
# perl Makefile.PL 
# make  
# make install

MySQL 高可用-MHA 架构搭建
安装后会在/usr/bin/下生成以下脚本文件:
MySQL 高可用-MHA 架构搭建

4.配置 SSH 无密码登录验证

MHA 环境需要 3 台主机互相信任,实现 3 台机器免密码登录
(使用 key 登录,工作中常用,最好不要禁掉密码登录,如果禁了,可能会有问题)

(1) 在 manager 上配置到所有 Node 节点的无密码验证

# ssh-keygen 
# ssh-copy-id -i root@db1
# ssh-copy-id -i root@db2
# ssh-copy-id -i root@db3

(2) 在 MHA Node DB1 上

# ssh-keygen 
# ssh-copy-id -i root@db2
# ssh-copy-id -i root@db3

(3) 在 MHA Node DB2 上

# ssh-keygen 
# ssh-copy-id -i root@db1
# ssh-copy-id -i root@db3

(4) 在 MHA Node DB3 上

# ssh-keygen 
# ssh-copy-id -i root@db1
# ssh-copy-id -i root@db2

5.搭建主从复制环境

主从我们是使用 GTID + ROW 的方式进行配置

(1) 在 master 上执行备份

# mysqldump -uroot -p --master-data=2 --single-transaction -R --triggers -A > db1.sql

(2) 在 master 上创建复制用户

mysql> grant replication slave on *.* to 'repl'@'192.168.199.%' identified by 'unixfbi';
mysql> flush privilges;

(3) 将备份复制到 db2 和 db3 上

# scp db1.sql root@db2:~/
# scp db1.sql root@db3:~/

(4) 在 DB2 上搭建备库

# mysql -uroot -p < db1.sql 
mysql> CHANGE MASTER TO
  MASTER_HOST='192.168.199.134',
  MASTER_USER='repl',
  MASTER_PASSWORD='unixfbi',
  MASTER_PORT=3306,
  MASTER_AUTO_POSITION=1;

mysql> start slave;

查看同步状态
MySQL 高可用-MHA 架构搭建

(5) 在 DB3 上搭建备库

# mysql -uroot -p < db1.sql 
mysql> CHANGE MASTER TO
  MASTER_HOST='192.168.199.134',
  MASTER_USER='repl',
  MASTER_PASSWORD='unixfbi',
  MASTER_PORT=3306,
  MASTER_AUTO_POSITION=1;

mysql> start slave;

查看复制状态:
MySQL 高可用-MHA 架构搭建

(6) slave 服务器设置 read only

将每个 slave 设置为 read only:
从库对外提供读操作,这里将 read_only 设置为 1

# mysql -uroot -punixfbi -e "set global read_only=1;"

(7) 创建监控用户

整个复制集群已经搭建完毕,这时还需要创建监控所需要的用户,在 DB1 上执行:

mysql> grant all privileges on *.* to root@'192.168.199.%' identified by 'unixfbi';
mysql> flush privileges;

至此,MHA 软件已经基本安装完毕。下面开始配置 MHA 软件。

6.配置 MHA (在 monitor 机器)

配置 MHA 的大体步骤如下

(1) 创建 MHA 工作目录,并且创建相关配置文件

创建数据文件目录
# mkdir -p  /usr/local/masterha/app1
创建配置文件目录
# mkdir -p /etc/masterha
# cp /usr/local/mha4mysql-manager-0.56/samples/conf/app1.cnf /etc/masterha/

修改/etc/masterha/app1.conf 配置文件,修改后内容为:

# cat /etc/masterha/app1.cnf 
[server default]
manager_workdir=/usr/local/masterha/app1             #该目录为 mha manager 产生相关状态文件全路径数据目录
manager_log=/var/log/masterha/app1/manager.log  #设置 manager 的日志
master_binlog_dir=/data/mysql/mysql3306/logs/      设置 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=unixfbi    # 设置 mysql 中 root 用户的密码,这个密码是前文中创建监控用户的那个密码
user=root       # 设置监控用户 root
ping_interval=1   # 设置监控主库,发送 ping 包的时间间隔,默认是 3 秒,尝试三次没有回应的时候自动进行 railover
remote_workdir=/usr/local/masterha/app1    # MHA node 上工作目录的全路径名。如果不存在,MHA node 会自动创建,如果不允许创建,MHA Node 自动异常退出。这个配置不是必须配置的,默认目录是在/var/tmp
repl_password=unixfbi   #设置复制用户的密码
repl_user=repl                 #设置复制环境中的复制用户名
report_script=/usr/local/bin/send_report   # 设置发生切换后发送的报警的脚本 
secondary_check_script= /usr/local/bin/masterha_secondary_check -s db2 -s db1 --user=root --master_host=db1 --master_ip=192.168.199.134 --master_port=3306    # 一旦 MHA 到 db1 的监控之间出现问题,MHA Manager 将会尝试从 db2 登录到 db1
shutdown_script=""            # 设置故障发生后关闭故障主机脚本(该脚本的主要作用是关闭主机放在发生脑裂,这里没有使用)
ssh_user=root                     #   设置 ssh 的登录用户名

[server1]
hostname=db1
port=3306

[server2]
hostname=db2
port=3306
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

[server3]
hostname=db3
port=3306

(2) 设置 relay log 清除方式(在每个 slave 上)

#  mysql -uroot -punixfbi -e "set global relay_log_purge=0" 

MHA 在发生切换的过程中,从库的恢复过程中依赖于 relay log 的相关信息,所以这里要将 relay log 的自动清除设置为 OFF,采用手动清除 relay log 的方式。在默认情况下,从服务器上的中继日志会在 SQL 线程执行完毕后被自动删除。但是在 MHA 环境中,这些中继日志在恢复其他从服务器时可能会被用到,因此需要禁用中继日志的自动删除功能。定期清除中继日志需要考虑到复制延时的问题。在 ext3 的文件系统下,删除大的文件需要一定的时间,会导致严重的复制延时。为了避免复制延时,需要暂时为中继日志创建硬链接,因为在 linux 系统中通过硬链接删除大文件速度会很快。(在 mysql 数据库中,删除大表时,通常也采用建立硬链接的方式)

(3) 设置定期清理 relay 脚本

使用如下命令设置 crontab 来定期清理 Relay Log:

# cat purge_relay_log.sh 
#!/bin/bash
user=root
passwd=unixfbi
port=3306
log_dir='/data/masterha/log'
work_dir='/data'
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 -l
0 4 * * * /bin/bash /root/purge_relay_log.sh

参数说明

--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。

purge_relay_logs 脚本删除中继日志不会阻塞 SQL 线程。下面我们手动执行看看什么情况:

[root@db2~]# purge_relay_logs --user=root --password=unixfbi --port=3306 --disable_relay_log_purge --workdir=/data/ 


2017-07-26 17:05:28: purge_relay_logs script started. 
Found relay_log.info: /data/mysql/mysql3306/data/relay-log.info 
Removing hard linked relay log files relay-bin* under /data/.. done.
Current relay log file: /data/mysql/mysql3306/data/relay-bin.000004
Archiving unused relay log files (up to /data/mysql/mysql3306/data/relay-bin.000003) ... 
Creating hard link for /data/mysql/mysql3306/data/relay-bin.000003 under /data//relay-bin.000003 .. ok. 
Creating hard links for unused relay log files completed. 
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. 
Removing hard linked relay log files relay-bin* under /data/.. done.
2017-07-26 17:05:31: All relay log purging operations succeeded.

7.检查 SSH 的配置

检查 MHA Manager 到所有 MHA Node 的 SSH 连接状态:

# masterha_check_ssh --conf=/etc/masterha/app1.cnf
# masterha_check_ssh --conf=/etc/masterha/app1.cnf
Wed Jul 26 17:08:46 2017 - [warning] Global configuration file /etc/masterha_default.cnf not found. Skipping.
Wed Jul 26 17:08:46 2017 - [info] Reading application default configurations from /etc/masterha/app1.cnf..
Wed Jul 26 17:08:46 2017 - [info] Reading server configurations from /etc/masterha/app1.cnf..
Wed Jul 26 17:08:46 2017 - [info] Starting SSH connection tests..
Wed Jul 26 17:08:49 2017 - [debug] 
Wed Jul 26 17:08:47 2017 - [debug]  Connecting via SSH from root@db2(192.168.199.212:22) to root@db1(192.168.199.134:22)..
Wed Jul 26 17:08:48 2017 - [debug]   ok.
Wed Jul 26 17:08:48 2017 - [debug]  Connecting via SSH from root@db2(192.168.199.212:22) to root@db3(192.168.199.233:22)..
Wed Jul 26 17:08:48 2017 - [debug]   ok.
Wed Jul 26 17:08:49 2017 - [debug] 
Wed Jul 26 17:08:47 2017 - [debug]  Connecting via SSH from root@db3(192.168.199.233:22) to root@db1(192.168.199.134:22)..
Wed Jul 26 17:08:48 2017 - [debug]   ok.
Wed Jul 26 17:08:48 2017 - [debug]  Connecting via SSH from root@db3(192.168.199.233:22) to root@db2(192.168.199.212:22)..
Wed Jul 26 17:08:48 2017 - [debug]   ok.
Wed Jul 26 17:08:49 2017 - [debug] 
Wed Jul 26 17:08:46 2017 - [debug]  Connecting via SSH from root@db1(192.168.199.134:22) to root@db2(192.168.199.212:22)..
Wed Jul 26 17:08:48 2017 - [debug]   ok.
Wed Jul 26 17:08:48 2017 - [debug]  Connecting via SSH from root@db1(192.168.199.134:22) to root@db3(192.168.199.233:22)..
Wed Jul 26 17:08:48 2017 - [debug]   ok.
Wed Jul 26 17:08:49 2017 - [info] All SSH connection tests passed successfully.

可以看到各个节点 ssh 验证都 OK。

8.检查整个复制环境状况(monitor 节点执行)

通过 masterha_check_repl 脚本查看整个集群的状态:

注意:先把/etc/masterha/app1.cnf 中的 master_ip_failover_script= /usr/local/bin/master_ip_failover 注释掉;后面引入 keepalived 后和修改该脚本以后再开启该选项
Failover 两种方式:一种是虚拟 IP 地址,一种是全局配置文件。MHA 并没有限定使用哪一种方式,而是让用户自己选择,虚拟 IP 地址的方式会牵扯到其它的软件,比如 keepalive 软件,而且还要修改脚本 master_ip_failover。

# masterha_check_repl --conf=/etc/masterha/app1.cnf
Wed Jul 26 17:20:40 2017 - [warning] Global configuration file /etc/masterha_default.cnf not found. Skipping.
Wed Jul 26 17:20:40 2017 - [info] Reading application default configurations from /etc/masterha/app1.cnf..
Wed Jul 26 17:20:40 2017 - [info] Reading server configurations from /etc/masterha/app1.cnf..
Wed Jul 26 17:20:40 2017 - [info] MHA::MasterMonitor version 0.56.
Wed Jul 26 17:20:41 2017 - [info] Dead Servers:
Wed Jul 26 17:20:41 2017 - [info] Alive Servers:
Wed Jul 26 17:20:41 2017 - [info]   db1(192.168.199.134:3306)
Wed Jul 26 17:20:41 2017 - [info]   db2(192.168.199.212:3306)
Wed Jul 26 17:20:41 2017 - [info]   db3(192.168.199.233:3306)
Wed Jul 26 17:20:41 2017 - [info] Alive Slaves:
Wed Jul 26 17:20:41 2017 - [info]   db2(192.168.199.212:3306)  Version=5.7.18-log (oldest major version between slaves) log-bin:enabled
Wed Jul 26 17:20:41 2017 - [info]     GTID ON
Wed Jul 26 17:20:41 2017 - [info]     Replicating from 192.168.199.134(192.168.199.134:3306)
Wed Jul 26 17:20:41 2017 - [info]     Primary candidate for the new Master (candidate_master is set)
Wed Jul 26 17:20:41 2017 - [info]   db3(192.168.199.233:3306)  Version=5.7.18-log (oldest major version between slaves) log-bin:enabled
Wed Jul 26 17:20:41 2017 - [info]     GTID ON
Wed Jul 26 17:20:41 2017 - [info]     Replicating from 192.168.199.134(192.168.199.134:3306)
Wed Jul 26 17:20:41 2017 - [info] Current Alive Master: db1(192.168.199.134:3306)
Wed Jul 26 17:20:41 2017 - [info] Checking slave configurations..
Wed Jul 26 17:20:41 2017 - [info] Checking replication filtering settings..
Wed Jul 26 17:20:41 2017 - [info]  binlog_do_db= , binlog_ignore_db= 
Wed Jul 26 17:20:41 2017 - [info]  Replication filtering check ok.
Wed Jul 26 17:20:41 2017 - [info] GTID is supported. Skipping all SSH and Node package checking.
Wed Jul 26 17:20:41 2017 - [info] Checking SSH publickey authentication settings on the current master..
Wed Jul 26 17:20:41 2017 - [info] HealthCheck: SSH to db1 is reachable.
Wed Jul 26 17:20:41 2017 - [info] 
db1 (current master)
 +--db2
 +--db3

Wed Jul 26 17:20:41 2017 - [info] Checking replication health on db2..
Wed Jul 26 17:20:41 2017 - [info]  ok.
Wed Jul 26 17:20:41 2017 - [info] Checking replication health on db3..
Wed Jul 26 17:20:41 2017 - [info]  ok.
Wed Jul 26 17:20:41 2017 - [warning] master_ip_failover_script is not defined.
Wed Jul 26 17:20:41 2017 - [warning] shutdown_script is not defined.
Wed Jul 26 17:20:41 2017 - [info] Got exit code 0 (Not master dead).

MySQL Replication Health is OK.

已经显示复制 OK,两个 warning 可以忽略;

9.检查 MHA Manager 的状态

通过 masterha_check_status 脚本查看 Manager 的状态:

# masterha_check_status --conf=/etc/masterha/app1.cnf
app1 is stopped(2:NOT_RUNNING).

"PING_OK" 表示正常,“NOT_RUNNING” 表示 MHA 监控没有开启

10.开启 MHA Manager 监控

# mkdir -p  /var/log/masterha/app1/
# nohup masterha_manager --conf=/etc/masterha/app1.cnf --remove_dead_master_conf --ignore_last_failover < /dev/null > /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 发生切换后会在日志目录,也就是上面我设置的/var/log/masterha/app1/产生 app1.failover.complete 文件,下次再次切换的时候如果发现该目录下存在该文件将不允许触发切换,除非在第一次切换后收到删除该文件,为了方便,这里设置为--ignore_last_failover。

查看 MHA Manager 监控是否正常:

# masterha_check_status --conf=/etc/masterha/app1.cnf
app1 (pid:2498) is running(0:PING_OK), master:db1

11.查看启动日志

通过 tail 命令查看启动过程中的日志输出信息:

# tail -n20 /var/log/masterha/app1/manager.log
Wed Jul 26 17:29:26 2017 - [info]     Replicating from 192.168.199.134(192.168.199.134:3306)
Wed Jul 26 17:29:26 2017 - [info] Current Alive Master: db1(192.168.199.134:3306)
Wed Jul 26 17:29:26 2017 - [info] Checking slave configurations..
Wed Jul 26 17:29:26 2017 - [info] Checking replication filtering settings..
Wed Jul 26 17:29:26 2017 - [info]  binlog_do_db= , binlog_ignore_db= 
Wed Jul 26 17:29:26 2017 - [info]  Replication filtering check ok.
Wed Jul 26 17:29:26 2017 - [info] GTID is supported. Skipping all SSH and Node package checking.
Wed Jul 26 17:29:26 2017 - [info] Checking SSH publickey authentication settings on the current master..
Wed Jul 26 17:29:26 2017 - [info] HealthCheck: SSH to db1 is reachable.
Wed Jul 26 17:29:26 2017 - [info] 
db1 (current master)
 +--db2
 +--db3

Wed Jul 26 17:29:26 2017 - [warning] master_ip_failover_script is not defined.
Wed Jul 26 17:29:26 2017 - [warning] shutdown_script is not defined.
Wed Jul 26 17:29:26 2017 - [info] Set master ping interval 1 seconds.
Wed Jul 26 17:29:26 2017 - [info] Set secondary check script: /usr/local/bin/masterha_secondary_check -s db2 -s db1 --user=root --master_host=db1 --master_ip=192.168.199.134 --master_port=3306
Wed Jul 26 17:29:26 2017 - [info] Starting ping health check on db1(192.168.199.134:3306)..
Wed Jul 26 17:29:26 2017 - [info] Ping(SELECT) succeeded, waiting until MySQL doesn't respond..

其中"Ping(SELECT) succeeded, waiting until MySQL doesn't respond.."说明整个系统已经开始监控了。

12.关闭 MHA Manager 监控

# masterha_stop  --conf=/etc/masterha/app1.cnf
Stopped app1 successfully.
[1]+  退出 1                nohup masterha_manager --conf=/etc/masterha/app1.cnf --remove_dead_master_conf --ignore_last_failover < /dev/null > /var/log/masterha/app1/manager.log 2>&1

三、配置 VIP

vip 配置可以采用两种方式,一种通过 keepalived 的方式管理虚拟 ip 的浮动;另外一种通过脚本方式启动虚拟 ip 的方式(即不需要 keepalived 或者 heartbeat 类似的软件)。

1.keepalived 配置步骤如下

(1) 下载集群心跳软件 keepalived 并进行安装

# yum -y install kernel-devel make gcc openssl-devel  libnl* popt*  libnfnetlink-devel
# yum install -y openssl openssl-devel
# wget -P /usr/local/src/   wget http://www.keepalived.org/software/keepalived-1.3.5.tar.gz
# cd /usr/local/src/
# tar zxf keepalived-1.3.5.tar.gz 
# cd keepalived-1.3.5
# ./configure --sysconfdir=/etc   --prefix=/usr/local/keepalived 
# make  && make install 
# cp /usr/local/keepalived/sbin/keepalived /usr/sbin/

(2) 配置 keepalived 的配置文件

在 db1 上设置
VIP 我们设置为:192.168.199.110

# cat keepalived.conf
! Configuration File for keepalived

global_defs {
   notification_email {
    unixfbi@unixfbi.com 
   }
   notification_email_from Alexandre.Cassen@firewall.loc
   smtp_server 127.0.0.1
   smtp_connect_timeout 30
   router_id MySQLHA_DEVEL
}


vrrp_instance HA_1 {
    state BACKUP
    nopreempt    
    interface eth0
    virtual_router_id 134
    priority 100
    advert_int 5
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    virtual_ipaddress {
        192.168.199.110
    }
}

在 db2 上配置

# cat keepalived.conf
! Configuration File for keepalived

global_defs {
   notification_email {
    unixfbi@unixfbi.com 
   }
   notification_email_from Alexandre.Cassen@firewall.loc
   smtp_server 127.0.0.1
   smtp_connect_timeout 30
   router_id MySQLHA_DEVEL
}


vrrp_instance HA_1 {
    state BACKUP  
    interface eth0
    virtual_router_id 134
    priority 90
    advert_int 5
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    virtual_ipaddress {
        192.168.199.110
    }
}

(3) 启动 keepalived

先启动 db1 上的,然后启动 db2 上的 keepalived;
在 db1 上操作:

# systemctl start keepalived.service
# systemctl status keepalived.service
● keepalived.service - LVS and VRRP High Availability Monitor
   Loaded: loaded (/usr/lib/systemd/system/keepalived.service; disabled; vendor preset: disabled)
   Active: active (running) since Thu 2017-07-27 10:45:33 CST; 23s ago
  Process: 9834 ExecStart=/usr/local/keepalived/sbin/keepalived $KEEPALIVED_OPTIONS (code=exited, status=0/SUCCESS)
 Main PID: 9835 (keepalived)
   CGroup: /system.slice/keepalived.service
           ├─9835 /usr/local/keepalived/sbin/keepalived -D
           ├─9836 /usr/local/keepalived/sbin/keepalived -D
           └─9837 /usr/local/keepalived/sbin/keepalived -D

Jul 27 10:45:33 db1 Keepalived_vrrp[9837]: VRRP sockpool: [ifindex(2), proto(112), unicast(0), fd(10,11)]
Jul 27 10:45:49 db1 Keepalived_vrrp[9837]: VRRP_Instance(HA_1) Transition to MASTER STATE
Jul 27 10:45:54 db1 Keepalived_vrrp[9837]: VRRP_Instance(HA_1) Entering MASTER STATE
Jul 27 10:45:54 db1 Keepalived_vrrp[9837]: VRRP_Instance(HA_1) setting protocol VIPs.
Jul 27 10:45:54 db1 Keepalived_vrrp[9837]: Sending gratuitous ARP on eth0 for 192.168.199.110
Jul 27 10:45:54 db1 Keepalived_vrrp[9837]: VRRP_Instance(HA_1) Sending/queueing gratuitous ARPs on eth0 for 192.168.199.110
Jul 27 10:45:54 db1 Keepalived_vrrp[9837]: Sending gratuitous ARP on eth0 for 192.168.199.110
Jul 27 10:45:54 db1 Keepalived_vrrp[9837]: Sending gratuitous ARP on eth0 for 192.168.199.110
Jul 27 10:45:54 db1 Keepalived_vrrp[9837]: Sending gratuitous ARP on eth0 for 192.168.199.110
Jul 27 10:45:54 db1 Keepalived_vrrp[9837]: Sending gratuitous ARP on eth0 for 192.168.199.110

db2 上:

# systemctl start keepalived.service
# systemctl status keepalived.service
● keepalived.service - LVS and VRRP High Availability Monitor
   Loaded: loaded (/usr/lib/systemd/system/keepalived.service; disabled; vendor preset: disabled)
   Active: active (running) since Thu 2017-07-27 10:46:48 CST; 4s ago
  Process: 8351 ExecStart=/usr/local/keepalived/sbin/keepalived $KEEPALIVED_OPTIONS (code=exited, status=0/SUCCESS)
 Main PID: 8352 (keepalived)
   CGroup: /system.slice/keepalived.service
           ├─8352 /usr/local/keepalived/sbin/keepalived -D
           ├─8353 /usr/local/keepalived/sbin/keepalived -D
           └─8354 /usr/local/keepalived/sbin/keepalived -D

Jul 27 10:46:48 db2 Keepalived_vrrp[8354]: Registering Kernel netlink reflector
Jul 27 10:46:48 db2 Keepalived_vrrp[8354]: Registering Kernel netlink command channel
Jul 27 10:46:48 db2 Keepalived_vrrp[8354]: Registering gratuitous ARP shared channel
Jul 27 10:46:48 db2 Keepalived_vrrp[8354]: Opening file '/etc/keepalived/keepalived.conf'.
Jul 27 10:46:48 db2 Keepalived_healthcheckers[8353]: Initializing ipvs
Jul 27 10:46:48 db2 Keepalived_healthcheckers[8353]: Opening file '/etc/keepalived/keepalived.conf'.
Jul 27 10:46:48 db2 Keepalived_vrrp[8354]: VRRP_Instance(HA_1) removing protocol VIPs.
Jul 27 10:46:48 db2 Keepalived_vrrp[8354]: Using LinkWatch kernel netlink reflector...
Jul 27 10:46:48 db2 Keepalived_vrrp[8354]: VRRP_Instance(HA_1) Entering BACKUP STATE
Jul 27 10:46:48 db2 Keepalived_vrrp[8354]: VRRP sockpool: [ifindex(2), proto(112), unicast(0), fd(10,11)]

(4) 查看 IP 绑定情况

db1:

[root@db1 ~]# ip add 
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN 
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 00:25:64:86:40:74 brd ff:ff:ff:ff:ff:ff
    inet 192.168.199.134/24 brd 192.168.199.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet 192.168.199.110/32 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::225:64ff:fe86:4074/64 scope link 
       valid_lft forever preferred_lft forever

db2:

[root@db2 ~]# ip add 
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN 
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 44:37:e6:19:85:81 brd ff:ff:ff:ff:ff:ff
    inet 192.168.199.212/24 brd 192.168.199.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::4637:e6ff:fe19:8581/64 scope link 
       valid_lft forever preferred_lft forever

由 ip add 可以看出 VIP 已经绑定到了 db1 上,符合我们的预期;

注意:这里 db1 和 db2 都要设置为 BACKUP 模式,在 keepalived 中有两种模式,分别是 master-->backup 模式和 backup-->backup 模式,这两种模式区别:
在 master-->backup 模式下,一旦主库宕机,虚拟 IP 会自动漂移到从库,当主库修复后,keepalived 启动后,还会把虚拟 IP 抢过来,即使你设置 nopreempt(不抢占)的方式抢占 IP 的动作也会发生。
在 backup-->backup 模式下,当主库宕掉后虚拟 IP 会自动漂移到从库上,当原主恢复之后重启 keepalived 服务,并不会抢占新主的虚拟 IP,即使是优先级高于从库的优先级,也不会抢占 IP,为了减少 IP 的漂移次数,生产中我们通常是把修复好的主库当作新主库的备库。

(5) MHA 引入 keepalived

那么如何才能把 keepalived 服务引入 MHA 呢?很简单,只需要修改切换时触发的脚本文件 master_ip_failover 即可。在该脚本中添加在 master 发生宕机时对 keepalived 的处理。

修改配置文件 master_ip_failover:

# cp /usr/local/mha4mysql-manager-0.56/samples/scripts/master_ip_failover /usr/local/bin/

# 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 = '192.168.199.110';
my $ssh_start_vip = "systemctl start keepalived.service";
my $ssh_stop_vip = "systemctl stop keepalived.service";

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 \"`;
}
# 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";
}

/usr/local/bin/master_ip_failover 添加或者修改的内容意思是当主库数据库发生故障时,会触发 MHA 切换,MHA Manager 会停掉主库上的 keepalived 服务,触发虚拟 ip 漂移到备选从库,从而完成切换。当然可以在 keepalived 里面引入脚本,这个脚本监控 mysql 是否正常运行,如果不正常,则调用该脚本杀掉 keepalived 进程。

把#master_ip_failover_script= /usr/local/bin/master_ip_failover 打开

执行检测:

# masterha_check_repl --conf=/etc/masterha/app1.cnf
Thu Jul 27 13:27:56 2017 - [warning] Global configuration file /etc/masterha_default.cnf not found. Skipping.
Thu Jul 27 13:27:56 2017 - [info] Reading application default configurations from /etc/masterha/app1.cnf..
Thu Jul 27 13:27:56 2017 - [info] Reading server configurations from /etc/masterha/app1.cnf..
Thu Jul 27 13:27:56 2017 - [info] MHA::MasterMonitor version 0.56.
Thu Jul 27 13:27:57 2017 - [info] Dead Servers:
Thu Jul 27 13:27:57 2017 - [info] Alive Servers:
Thu Jul 27 13:27:57 2017 - [info]   db1(192.168.199.134:3306)
Thu Jul 27 13:27:57 2017 - [info]   db2(192.168.199.212:3306)
Thu Jul 27 13:27:57 2017 - [info]   db3(192.168.199.233:3306)
Thu Jul 27 13:27:57 2017 - [info] Alive Slaves:
Thu Jul 27 13:27:57 2017 - [info]   db2(192.168.199.212:3306)  Version=5.7.18-log (oldest major version between slaves) log-bin:enabled
Thu Jul 27 13:27:57 2017 - [info]     GTID ON
Thu Jul 27 13:27:57 2017 - [info]     Replicating from 192.168.199.134(192.168.199.134:3306)
Thu Jul 27 13:27:57 2017 - [info]     Primary candidate for the new Master (candidate_master is set)
Thu Jul 27 13:27:57 2017 - [info]   db3(192.168.199.233:3306)  Version=5.7.18-log (oldest major version between slaves) log-bin:enabled
Thu Jul 27 13:27:57 2017 - [info]     GTID ON
Thu Jul 27 13:27:57 2017 - [info]     Replicating from 192.168.199.134(192.168.199.134:3306)
Thu Jul 27 13:27:57 2017 - [info] Current Alive Master: db1(192.168.199.134:3306)
Thu Jul 27 13:27:57 2017 - [info] Checking slave configurations..
Thu Jul 27 13:27:57 2017 - [info] Checking replication filtering settings..
Thu Jul 27 13:27:57 2017 - [info]  binlog_do_db= , binlog_ignore_db= 
Thu Jul 27 13:27:57 2017 - [info]  Replication filtering check ok.
Thu Jul 27 13:27:57 2017 - [info] GTID is supported. Skipping all SSH and Node package checking.
Thu Jul 27 13:27:57 2017 - [info] Checking SSH publickey authentication settings on the current master..
Thu Jul 27 13:27:57 2017 - [info] HealthCheck: SSH to db1 is reachable.
Thu Jul 27 13:27:57 2017 - [info] 
db1 (current master)
 +--db2
 +--db3

Thu Jul 27 13:27:57 2017 - [info] Checking replication health on db2..
Thu Jul 27 13:27:57 2017 - [info]  ok.
Thu Jul 27 13:27:57 2017 - [info] Checking replication health on db3..
Thu Jul 27 13:27:57 2017 - [info]  ok.
Thu Jul 27 13:27:57 2017 - [info] Checking master_ip_failover_script status:
Thu Jul 27 13:27:57 2017 - [info]   /usr/local/bin/master_ip_failover --command=status --ssh_user=root --orig_master_host=db1 --orig_master_ip=192.168.199.134 --orig_master_port=3306 
Thu Jul 27 13:27:57 2017 - [info]  OK.
Thu Jul 27 13:27:57 2017 - [warning] shutdown_script is not defined.
Thu Jul 27 13:27:57 2017 - [info] Got exit code 0 (Not master dead).

MySQL Replication Health is OK.

2.配置发邮件脚本

首先安装

# yum install epel-release -y
# yum install perl-Mail-Sender

编写脚本

# cat /usr/local/bin/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;

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='work2312@163.com';
my $mail_user='work2312@163.com';
my $mail_pass='XXXXX';
my $mail_to=['unixfbi@unixfbi.com'];
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;

3.进行模拟主 Master(db1)down 了

# /etc/init.d/mysqld stop

通过日志可以看到切换的过程:

Mon Jul 31 19:45:40 2017 - [warning] Got error on MySQL select ping: 2013 (Lost connection to MySQL server during query)
Mon Jul 31 19:45:40 2017 - [info] Executing seconary network check script: /usr/local/bin/masterha_secondary_check -s db2 -s db1 --user=root --master_host=db1 --master_ip=192.168.199.134 --master_port=3306  --user=root  --master_host=db1  --master_ip=192.168.199.134  --master_port=3306
Mon Jul 31 19:45:40 2017 - [info] Executing SSH check script: exit 0
Mon Jul 31 19:45:40 2017 - [info] HealthCheck: SSH to db1 is reachable.
Monitoring server db2 is reachable, Master is not reachable from db2. OK.
Monitoring server db1 is reachable, Master is not reachable from db1. OK.
Mon Jul 31 19:45:40 2017 - [info] Master is not reachable from all other monitoring servers. Failover should start.
Mon Jul 31 19:45:41 2017 - [warning] Got error on MySQL connect: 2003 (Can't connect to MySQL server on '192.168.199.134' (111))
Mon Jul 31 19:45:41 2017 - [warning] Connection failed 1 time(s)..
Mon Jul 31 19:45:42 2017 - [warning] Got error on MySQL connect: 2003 (Can't connect to MySQL server on '192.168.199.134' (111))
Mon Jul 31 19:45:42 2017 - [warning] Connection failed 2 time(s)..
Mon Jul 31 19:45:43 2017 - [warning] Got error on MySQL connect: 2003 (Can't connect to MySQL server on '192.168.199.134' (111))
Mon Jul 31 19:45:43 2017 - [warning] Connection failed 3 time(s)..
Mon Jul 31 19:45:43 2017 - [warning] Master is not reachable from health checker!
Mon Jul 31 19:45:43 2017 - [warning] Master db1(192.168.199.134:3306) is not reachable!
Mon Jul 31 19:45:43 2017 - [warning] SSH is reachable.
Mon Jul 31 19:45:43 2017 - [info] Connecting to a master server failed. Reading configuration file /etc/masterha_default.cnf and /etc/masterha/app1.cnf again, and trying to connect to all servers to check server status..
Mon Jul 31 19:45:43 2017 - [warning] Global configuration file /etc/masterha_default.cnf not found. Skipping.
Mon Jul 31 19:45:43 2017 - [info] Reading application default configurations from /etc/masterha/app1.cnf..
Mon Jul 31 19:45:43 2017 - [info] Reading server configurations from /etc/masterha/app1.cnf..
Mon Jul 31 19:45:44 2017 - [info] Dead Servers:
Mon Jul 31 19:45:44 2017 - [info]   db1(192.168.199.134:3306)
Mon Jul 31 19:45:44 2017 - [info] Alive Servers:
Mon Jul 31 19:45:44 2017 - [info]   db2(192.168.199.212:3306)
Mon Jul 31 19:45:44 2017 - [info]   db3(192.168.199.233:3306)
Mon Jul 31 19:45:44 2017 - [info] Alive Slaves:
Mon Jul 31 19:45:44 2017 - [info]   db2(192.168.199.212:3306)  Version=5.7.18-log (oldest major version between slaves) log-bin:enabled
Mon Jul 31 19:45:44 2017 - [info]     GTID ON
Mon Jul 31 19:45:44 2017 - [info]     Replicating from 192.168.199.134(192.168.199.134:3306)
Mon Jul 31 19:45:44 2017 - [info]     Primary candidate for the new Master (candidate_master is set)
Mon Jul 31 19:45:44 2017 - [info]   db3(192.168.199.233:3306)  Version=5.7.18-log (oldest major version between slaves) log-bin:enabled
Mon Jul 31 19:45:44 2017 - [info]     GTID ON
Mon Jul 31 19:45:44 2017 - [info]     Replicating from 192.168.199.134(192.168.199.134:3306)
Mon Jul 31 19:45:44 2017 - [info] Checking slave configurations..
Mon Jul 31 19:45:44 2017 - [info] Checking replication filtering settings..
Mon Jul 31 19:45:44 2017 - [info]  Replication filtering check ok.
Mon Jul 31 19:45:44 2017 - [info] Master is down!
Mon Jul 31 19:45:44 2017 - [info] Terminating monitoring script.
Mon Jul 31 19:45:44 2017 - [info] Got exit code 20 (Master dead).
Mon Jul 31 19:45:44 2017 - [info] MHA::MasterFailover version 0.56.
Mon Jul 31 19:45:44 2017 - [info] Starting master failover.
Mon Jul 31 19:45:44 2017 - [info] 
Mon Jul 31 19:45:44 2017 - [info] * Phase 1: Configuration Check Phase..
Mon Jul 31 19:45:44 2017 - [info] 
Mon Jul 31 19:45:45 2017 - [info] Dead Servers:
Mon Jul 31 19:45:45 2017 - [info]   db1(192.168.199.134:3306)
Mon Jul 31 19:45:45 2017 - [info] Checking master reachability via mysql(double check)..
Mon Jul 31 19:45:45 2017 - [info]  ok.
Mon Jul 31 19:45:45 2017 - [info] Alive Servers:
Mon Jul 31 19:45:45 2017 - [info]   db2(192.168.199.212:3306)
Mon Jul 31 19:45:45 2017 - [info]   db3(192.168.199.233:3306)
Mon Jul 31 19:45:45 2017 - [info] Alive Slaves:
Mon Jul 31 19:45:45 2017 - [info]   db2(192.168.199.212:3306)  Version=5.7.18-log (oldest major version between slaves) log-bin:enabled
Mon Jul 31 19:45:45 2017 - [info]     GTID ON
Mon Jul 31 19:45:45 2017 - [info]     Replicating from 192.168.199.134(192.168.199.134:3306)
Mon Jul 31 19:45:45 2017 - [info]     Primary candidate for the new Master (candidate_master is set)
Mon Jul 31 19:45:45 2017 - [info]   db3(192.168.199.233:3306)  Version=5.7.18-log (oldest major version between slaves) log-bin:enabled
Mon Jul 31 19:45:45 2017 - [info]     GTID ON
Mon Jul 31 19:45:45 2017 - [info]     Replicating from 192.168.199.134(192.168.199.134:3306)
Mon Jul 31 19:45:45 2017 - [info] ** Phase 1: Configuration Check Phase completed.
Mon Jul 31 19:45:45 2017 - [info] 
Mon Jul 31 19:45:45 2017 - [info] * Phase 2: Dead Master Shutdown Phase..
Mon Jul 31 19:45:45 2017 - [info] 
Mon Jul 31 19:45:45 2017 - [info] Forcing shutdown so that applications never connect to the current master..
Mon Jul 31 19:45:45 2017 - [info] Executing master IP deactivatation script:
Mon Jul 31 19:45:45 2017 - [info]   /usr/local/bin/master_ip_failover --orig_master_host=db1 --orig_master_ip=192.168.199.134 --orig_master_port=3306 --command=stopssh --ssh_user=root  


IN SCRIPT TEST====systemctl stop keepalived.service==systemctl start keepalived.service===

Disabling the VIP on old master: db1 
Mon Jul 31 19:45:46 2017 - [info]  done.
Mon Jul 31 19:45:46 2017 - [warning] shutdown_script is not set. Skipping explicit shutting down of the dead master.
Mon Jul 31 19:45:46 2017 - [info] * Phase 2: Dead Master Shutdown Phase completed.
Mon Jul 31 19:45:46 2017 - [info] 
Mon Jul 31 19:45:46 2017 - [info] * Phase 3: Master Recovery Phase..
Mon Jul 31 19:45:46 2017 - [info] 
Mon Jul 31 19:45:46 2017 - [info] * Phase 3.1: Getting Latest Slaves Phase..
Mon Jul 31 19:45:46 2017 - [info] 
Mon Jul 31 19:45:46 2017 - [info] The latest binary log file/position on all slaves is mysql-bin.000004:194
Mon Jul 31 19:45:46 2017 - [info] Retrieved Gtid Set: 3604995d-6749-11e7-9c03-002564864074:1-4
Mon Jul 31 19:45:46 2017 - [info] Latest slaves (Slaves that received relay log files to the latest):
Mon Jul 31 19:45:46 2017 - [info]   db2(192.168.199.212:3306)  Version=5.7.18-log (oldest major version between slaves) log-bin:enabled
Mon Jul 31 19:45:46 2017 - [info]     GTID ON
Mon Jul 31 19:45:46 2017 - [info]     Replicating from 192.168.199.134(192.168.199.134:3306)
Mon Jul 31 19:45:46 2017 - [info]     Primary candidate for the new Master (candidate_master is set)
Mon Jul 31 19:45:46 2017 - [info]   db3(192.168.199.233:3306)  Version=5.7.18-log (oldest major version between slaves) log-bin:enabled
Mon Jul 31 19:45:46 2017 - [info]     GTID ON
Mon Jul 31 19:45:46 2017 - [info]     Replicating from 192.168.199.134(192.168.199.134:3306)
Mon Jul 31 19:45:46 2017 - [info] The oldest binary log file/position on all slaves is mysql-bin.000004:194
Mon Jul 31 19:45:46 2017 - [info] Retrieved Gtid Set: 3604995d-6749-11e7-9c03-002564864074:1-4
Mon Jul 31 19:45:46 2017 - [info] Oldest slaves:
Mon Jul 31 19:45:46 2017 - [info]   db2(192.168.199.212:3306)  Version=5.7.18-log (oldest major version between slaves) log-bin:enabled
Mon Jul 31 19:45:46 2017 - [info]     GTID ON
Mon Jul 31 19:45:46 2017 - [info]     Replicating from 192.168.199.134(192.168.199.134:3306)
Mon Jul 31 19:45:46 2017 - [info]     Primary candidate for the new Master (candidate_master is set)
Mon Jul 31 19:45:46 2017 - [info]   db3(192.168.199.233:3306)  Version=5.7.18-log (oldest major version between slaves) log-bin:enabled
Mon Jul 31 19:45:46 2017 - [info]     GTID ON
Mon Jul 31 19:45:46 2017 - [info]     Replicating from 192.168.199.134(192.168.199.134:3306)
Mon Jul 31 19:45:46 2017 - [info] 
Mon Jul 31 19:45:46 2017 - [info] * Phase 3.3: Determining New Master Phase..
Mon Jul 31 19:45:46 2017 - [info] 
Mon Jul 31 19:45:46 2017 - [info] Searching new master from slaves..
Mon Jul 31 19:45:46 2017 - [info]  Candidate masters from the configuration file:
Mon Jul 31 19:45:46 2017 - [info]   db2(192.168.199.212:3306)  Version=5.7.18-log (oldest major version between slaves) log-bin:enabled
Mon Jul 31 19:45:46 2017 - [info]     GTID ON
Mon Jul 31 19:45:46 2017 - [info]     Replicating from 192.168.199.134(192.168.199.134:3306)
Mon Jul 31 19:45:46 2017 - [info]     Primary candidate for the new Master (candidate_master is set)
Mon Jul 31 19:45:46 2017 - [info]  Non-candidate masters:
Mon Jul 31 19:45:46 2017 - [info]  Searching from candidate_master slaves which have received the latest relay log events..
Mon Jul 31 19:45:46 2017 - [info] New master is db2(192.168.199.212:3306)
Mon Jul 31 19:45:46 2017 - [info] Starting master failover..
Mon Jul 31 19:45:46 2017 - [info] 
From:
db1 (current master)
 +--db2
 +--db3

To:
db2 (new master)
 +--db3
Mon Jul 31 19:45:46 2017 - [info] 
Mon Jul 31 19:45:46 2017 - [info] * Phase 3.3: New Master Recovery Phase..
Mon Jul 31 19:45:46 2017 - [info] 
Mon Jul 31 19:45:46 2017 - [info]  Waiting all logs to be applied.. 
Mon Jul 31 19:45:46 2017 - [info]   done.
Mon Jul 31 19:45:46 2017 - [info] Getting new master's binlog name and position..
Mon Jul 31 19:45:46 2017 - [info]  mysql-bin.000001:1058
Mon Jul 31 19:45:46 2017 - [info]  All other slaves should start replication from here. Statement should be: CHANGE MASTER TO MASTER_HOST='db2 or 192.168.199.212', MASTER_PORT=3306, MASTER_AUTO_POSITION=1, MASTER_USER='repl', MASTER_PASSWORD='xxx';
Mon Jul 31 19:45:46 2017 - [info] Master Recovery succeeded. File:Pos:Exec_Gtid_Set: mysql-bin.000001, 1058, 3604995d-6749-11e7-9c03-002564864074:1-4
Mon Jul 31 19:45:46 2017 - [info] Executing master IP activate script:
Mon Jul 31 19:45:46 2017 - [info]   /usr/local/bin/master_ip_failover --command=start --ssh_user=root --orig_master_host=db1 --orig_master_ip=192.168.199.134 --orig_master_port=3306 --new_master_host=db2 --new_master_ip=192.168.199.212 --new_master_port=3306 --new_master_user='root' --new_master_password='unixfbi'  
Unknown option: new_master_user
Unknown option: new_master_password


IN SCRIPT TEST====systemctl stop keepalived.service==systemctl start keepalived.service===

Enabling the VIP - 192.168.199.110 on the new master - db2 
Mon Jul 31 19:45:46 2017 - [info]  OK.
Mon Jul 31 19:45:46 2017 - [info] Setting read_only=0 on db2(192.168.199.212:3306)..
Mon Jul 31 19:45:46 2017 - [info]  ok.
Mon Jul 31 19:45:46 2017 - [info] ** Finished master recovery successfully.
Mon Jul 31 19:45:46 2017 - [info] * Phase 3: Master Recovery Phase completed.
Mon Jul 31 19:45:46 2017 - [info] 
Mon Jul 31 19:45:46 2017 - [info] * Phase 4: Slaves Recovery Phase..
Mon Jul 31 19:45:46 2017 - [info] 
Mon Jul 31 19:45:46 2017 - [info] 
Mon Jul 31 19:45:46 2017 - [info] * Phase 4.1: Starting Slaves in parallel..
Mon Jul 31 19:45:46 2017 - [info] 
Mon Jul 31 19:45:46 2017 - [info] -- Slave recovery on host db3(192.168.199.233:3306) started, pid: 12688. Check tmp log /var/log/masterha/app1/db3_3306_20170731194544.log if it takes time..
Mon Jul 31 19:45:47 2017 - [info] 
Mon Jul 31 19:45:47 2017 - [info] Log messages from db3 ...
Mon Jul 31 19:45:47 2017 - [info] 
Mon Jul 31 19:45:46 2017 - [info]  Resetting slave db3(192.168.199.233:3306) and starting replication from the new master db2(192.168.199.212:3306)..
Mon Jul 31 19:45:47 2017 - [info]  Executed CHANGE MASTER.
Mon Jul 31 19:45:47 2017 - [info]  Slave started.
Mon Jul 31 19:45:47 2017 - [info] End of log messages from db3.
Mon Jul 31 19:45:47 2017 - [info] -- Slave on host db3(192.168.199.233:3306) started.
Mon Jul 31 19:45:47 2017 - [info] All new slave servers recovered successfully.
Mon Jul 31 19:45:47 2017 - [info] 
Mon Jul 31 19:45:47 2017 - [info] * Phase 5: New master cleanup phase..
Mon Jul 31 19:45:47 2017 - [info] 
Mon Jul 31 19:45:47 2017 - [info] Resetting slave info on the new master..
Mon Jul 31 19:45:48 2017 - [info]  db2: Resetting slave info succeeded.
Mon Jul 31 19:45:48 2017 - [info] Master failover to db2(192.168.199.212:3306) completed successfully.
Mon Jul 31 19:45:48 2017 - [info] Deleted server1 entry from /etc/masterha/app1.cnf .
Mon Jul 31 19:45:48 2017 - [info] 

----- Failover Report -----

app1: MySQL Master failover db1 to db2 succeeded

Master db1 is down!

Check MHA Manager logs at monitor:/var/log/masterha/app1/manager.log for details.

Started automated(non-interactive) failover.
Invalidated master IP address on db1.
Selected db2 as a new master.
db2: OK: Applying all logs succeeded.
db2: OK: Activated master IP address.
db3: OK: Slave started, replicating from db2.
db2: Resetting slave info succeeded.
Master failover to db2(192.168.199.212:3306) completed successfully.
Mon Jul 31 19:45:48 2017 - [info] Sending mail..
Unknown option: conf

4.在 db1 上查看 VIP

[root@db1 ~]# ip add
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN 
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 00:25:64:86:40:74 brd ff:ff:ff:ff:ff:ff
    inet 192.168.199.134/24 brd 192.168.199.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::225:64ff:fe86:4074/64 scope link 
       valid_lft forever preferred_lft forever

可以看到 VIP 已经不在 db1 上了
查看一下备选 master(db2)的 IP

[root@db2 ~]# ip add
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN 
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 44:37:e6:19:85:81 brd ff:ff:ff:ff:ff:ff
    inet 192.168.199.212/24 brd 192.168.199.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet 192.168.199.110/32 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::4637:e6ff:fe19:8581/64 scope link 
       valid_lft forever preferred_lft forever

发现 VIP 已经切换到 db2 上了。

5.确认是否收到邮件

MySQL 高可用-MHA 架构搭建
发现已经收到邮件了

6.在 monitor 机器上查看配置文件

可以发现/etc/masterha/app1.cnf 里面的[server1]内容已经被自动去掉了。

# cat /etc/masterha/app1.cnf 
[server default]
manager_log=/var/log/masterha/app1/manager.log
manager_workdir=/var/log/masterha/app1
master_binlog_dir=/data/mysql/mysql3306/logs/
master_ip_failover_script=/usr/local/bin/master_ip_failover
master_ip_online_change_script=/usr/local/bin/master_ip_online_change
password=unixfbi
ping_interval=1
remote_workdir=/tmp/app1
repl_password=unixfbi
repl_user=repl
report_script=/usr/local/bin/send_report
secondary_check_script=/usr/local/bin/masterha_secondary_check -s db2 -s db1 --user=root --master_host=db1 --master_ip=192.168.199.134 --master_port=3306
shutdown_script=""
ssh_user=root
user=root

[server2]
candidate_master=1
check_repl_delay=0
hostname=db2
port=3306

[server3]
hostname=db3
port=3306

7.通过脚本的方式管理 VIP

注意:master_ip_failover_script=/usr/bin/master_ip_failover。在用虚拟 IP 的时候,需要在开启 MHA 程序之前要把虚拟 IP 先设置到主上去,否则 MHA 是不会自己的去设置 VIP,第一次设置 VIP 之后,后续脚本的故障转移等功能会自动的对 VIP 进行切换。在用虚拟 IP 的时候,需要在开启 MHA 程序之前要把虚拟 IP 先设置到主上去,否则 MHA 是不会自己的去设置 VIP,第一次设置 VIP 之后,后续脚本的故障转移等功能会自动的对 VIP 进行切换。

脚本如下

# cat 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 = '192.168.199.110';
my $key = '1';
my $ssh_start_vip = "/sbin/ifconfig eth1:$key $vip";
my $ssh_stop_vip = "/sbin/ifconfig eth1:$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 \"`;
}
# 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";
}

四、自动 Failover

为了防止脑裂情况的发生,这里将采用通过脚本的方式来管理虚拟 IP,到此为止,基本上 MHA 集群环境已经配置完毕,接下来我们将通过生产中的一些案例来看一下 MHA 到底是如何进行工作的。下面将从 MHA 自动 failover、手动 failover、在线切换三种方式来介绍 MHA 的工作情况。
首先在 db1 上设置 VIP

[root@db1 ~]# /sbin/ifconfig eth0:1 192.168.199.110/24
[root@db1 ~]# ip add
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN 
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 00:25:64:86:40:74 brd ff:ff:ff:ff:ff:ff
    inet 192.168.199.134/24 brd 192.168.199.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet 192.168.199.110/24 brd 192.168.199.255 scope global secondary eth0:1
       valid_lft forever preferred_lft forever
    inet6 fe80::225:64ff:fe86:4074/64 scope link 
       valid_lft forever preferred_lft forever

自动 failover 模拟测试的操作步骤如下。

1.sysbench 生成测试数据

在 db1 上使用 sysbench 生成数据,在 sbtest

# yum install sysbench -y
# mysql -uroot -punixfbi -e "create database sbtest;" 
# sysbench /usr/share/sysbench/oltp_read_write.lua   --table-size=100000 --mysql-user=root  --mysql-password=unixfbi  --mysql-socket=/tmp/mysql3306.sock   --mysql-db=sbtest  --db-driver=mysql  --threads=30  --time=1800  --rand-type=uniform  prepare 
sysbench 1.0.6 (using system LuaJIT 2.0.4)

Initializing worker threads...

Creating table 'sbtest1'...
Inserting 100000 records into 'sbtest1'
Creating a secondary index on 'sbtest1'...

查看生成的数据

root@localhost [sbtest]>select count(*) from sbtest1;
+----------+
| count(*) |
+----------+
|   100000 |
+----------+
1 row in set (0.03 sec)

2.停掉 slave io 线程,模拟主从延迟

在 db2 上停掉 slave 的 io 线程,以模拟日志不一致

root@localhost [sbtest]>stop slave io_thread;
Query OK, 0 rows affected (0.02 sec)

db3 还继续传输日志

3.模拟 sysbench 压力测试

在 db1 上进行 sysbench 测试,运行 3 分钟,产生大量的 binlog

# sysbench /usr/share/sysbench/oltp_read_write.lua   --table-size=1000000 --mysql-user=root  --mysql-password=unixfbi  --mysql-socket=/tmp/mysql3306.sock   --mysql-db=sbtest  --db-driver=mysql  --threads=30  --time=180 --rand-type=uniform   run 
sysbench 1.0.6 (using system LuaJIT 2.0.4)

Running the test with following options:
Number of threads: 30
Initializing random number generator from current time


Initializing worker threads...

Threads started!

SQL statistics:
    queries performed:
        read:                            2163336
        write:                           231083
        other:                           696061
        total:                           3090480
    transactions:                        154524 (857.65 per sec.)
    queries:                             3090480 (17152.98 per sec.)
    ignored errors:                      0      (0.00 per sec.)
    reconnects:                          0      (0.00 per sec.)

General statistics:
    total time:                          180.1692s
    total number of events:              154524

Latency (ms):
         min:                                  1.24
         avg:                                 34.95
         max:                                835.82
         95th percentile:                    106.75
         sum:                            5401126.68

Threads fairness:
    events (avg/stddev):           5150.8000/39.59
    execution time (avg/stddev):   180.0376/0.06

查看主库生成多少条数据

root@localhost [sbtest]>select count(*) from sbtest1;
+----------+
| count(*) |
+----------+
|   229151 |
+----------+
1 row in set (0.29 sec)

4.停掉 db1 上的 mysql

# ps aux|grep mysql |grep -v grep |awk '{print $2}' |xargs kill -9 

5.查看 MHA 切换日志

此时通过 mha 也可以查看到状态:

# masterha_check_status --conf=/etc/masterha/app1.cnf
app1 master maybe down(20:PING_FAILING). master:db1
Check /var/log/masterha/app1/manager.log for details.

提示告知 master db1 可能 down 掉了
下面可以通过日志了解整个切换过程
了解整个切换过程。在 monitor 机器上查看 MHA 切换日志

Wed Aug  2 17:43:18 2017 - [warning] Got error on MySQL select ping: 2013 (Lost connection to MySQL server during query)
Wed Aug  2 17:43:18 2017 - [info] Executing seconary network check script: /usr/local/bin/masterha_secondary_check -s db2 -s db1 --user=root --master_host=db1 --master_ip=192.168.199.134 --master_port=3306  --user=root  --master_host=db1  --master_ip=192.168.199.134  --master_port=3306
Wed Aug  2 17:43:18 2017 - [info] Executing SSH check script: exit 0
Wed Aug  2 17:43:19 2017 - [info] HealthCheck: SSH to db1 is reachable.
Monitoring server db2 is reachable, Master is not reachable from db2. OK.
Monitoring server db1 is reachable, Master is not reachable from db1. OK.
Wed Aug  2 17:43:19 2017 - [info] Master is not reachable from all other monitoring servers. Failover should start.
Wed Aug  2 17:43:19 2017 - [warning] Got error on MySQL connect: 2003 (Can't connect to MySQL server on '192.168.199.134' (111))
Wed Aug  2 17:43:19 2017 - [warning] Connection failed 1 time(s)..
Wed Aug  2 17:43:20 2017 - [warning] Got error on MySQL connect: 2003 (Can't connect to MySQL server on '192.168.199.134' (111))
Wed Aug  2 17:43:20 2017 - [warning] Connection failed 2 time(s)..
Wed Aug  2 17:43:21 2017 - [warning] Got error on MySQL connect: 2003 (Can't connect to MySQL server on '192.168.199.134' (111))
Wed Aug  2 17:43:21 2017 - [warning] Connection failed 3 time(s)..
Wed Aug  2 17:43:21 2017 - [warning] Master is not reachable from health checker!
Wed Aug  2 17:43:21 2017 - [warning] Master db1(192.168.199.134:3306) is not reachable!
Wed Aug  2 17:43:21 2017 - [warning] SSH is reachable.
Wed Aug  2 17:43:21 2017 - [info] Connecting to a master server failed. Reading configuration file /etc/masterha_default.cnf and /etc/masterha/app1.cnf again, and trying to connect to all servers to check server status..
Wed Aug  2 17:43:21 2017 - [warning] Global configuration file /etc/masterha_default.cnf not found. Skipping.
Wed Aug  2 17:43:21 2017 - [info] Reading application default configurations from /etc/masterha/app1.cnf..
Wed Aug  2 17:43:21 2017 - [info] Reading server configurations from /etc/masterha/app1.cnf..
Wed Aug  2 17:43:22 2017 - [info] Dead Servers:
Wed Aug  2 17:43:22 2017 - [info]   db1(192.168.199.134:3306)
Wed Aug  2 17:43:22 2017 - [info] Alive Servers:
Wed Aug  2 17:43:22 2017 - [info]   db2(192.168.199.212:3306)
Wed Aug  2 17:43:22 2017 - [info]   db3(192.168.199.233:3306)
Wed Aug  2 17:43:22 2017 - [info] Alive Slaves:
Wed Aug  2 17:43:22 2017 - [info]   db2(192.168.199.212:3306)  Version=5.7.18-log (oldest major version between slaves) log-bin:enabled
Wed Aug  2 17:43:22 2017 - [info]     GTID ON
Wed Aug  2 17:43:22 2017 - [info]     Replicating from 192.168.199.134(192.168.199.134:3306)
Wed Aug  2 17:43:22 2017 - [info]     Primary candidate for the new Master (candidate_master is set)
Wed Aug  2 17:43:22 2017 - [info]   db3(192.168.199.233:3306)  Version=5.7.18-log (oldest major version between slaves) log-bin:enabled
Wed Aug  2 17:43:22 2017 - [info]     GTID ON
Wed Aug  2 17:43:22 2017 - [info]     Replicating from 192.168.199.134(192.168.199.134:3306)
Wed Aug  2 17:43:22 2017 - [info] Checking slave configurations..
Wed Aug  2 17:43:22 2017 - [info]  read_only=1 is not set on slave db3(192.168.199.233:3306).
Wed Aug  2 17:43:22 2017 - [info] Checking replication filtering settings..
Wed Aug  2 17:43:22 2017 - [info]  Replication filtering check ok.
Wed Aug  2 17:43:22 2017 - [info] Master is down!
Wed Aug  2 17:43:22 2017 - [info] Terminating monitoring script.
Wed Aug  2 17:43:22 2017 - [info] Got exit code 20 (Master dead).
Wed Aug  2 17:43:22 2017 - [info] MHA::MasterFailover version 0.56.
Wed Aug  2 17:43:22 2017 - [info] Starting master failover.
Wed Aug  2 17:43:22 2017 - [info] 
Wed Aug  2 17:43:22 2017 - [info] * Phase 1: Configuration Check Phase..
Wed Aug  2 17:43:22 2017 - [info] 
Wed Aug  2 17:43:23 2017 - [info] Dead Servers:
Wed Aug  2 17:43:23 2017 - [info]   db1(192.168.199.134:3306)
Wed Aug  2 17:43:23 2017 - [info] Checking master reachability via mysql(double check)..
Wed Aug  2 17:43:23 2017 - [info]  ok.
Wed Aug  2 17:43:23 2017 - [info] Alive Servers:
Wed Aug  2 17:43:23 2017 - [info]   db2(192.168.199.212:3306)
Wed Aug  2 17:43:23 2017 - [info]   db3(192.168.199.233:3306)
Wed Aug  2 17:43:23 2017 - [info] Alive Slaves:
Wed Aug  2 17:43:23 2017 - [info]   db2(192.168.199.212:3306)  Version=5.7.18-log (oldest major version between slaves) log-bin:enabled
Wed Aug  2 17:43:23 2017 - [info]     GTID ON
Wed Aug  2 17:43:23 2017 - [info]     Replicating from 192.168.199.134(192.168.199.134:3306)
Wed Aug  2 17:43:23 2017 - [info]     Primary candidate for the new Master (candidate_master is set)
Wed Aug  2 17:43:23 2017 - [info]   db3(192.168.199.233:3306)  Version=5.7.18-log (oldest major version between slaves) log-bin:enabled
Wed Aug  2 17:43:23 2017 - [info]     GTID ON
Wed Aug  2 17:43:23 2017 - [info]     Replicating from 192.168.199.134(192.168.199.134:3306)
Wed Aug  2 17:43:23 2017 - [info] ** Phase 1: Configuration Check Phase completed.
Wed Aug  2 17:43:23 2017 - [info] 
Wed Aug  2 17:43:23 2017 - [info] * Phase 2: Dead Master Shutdown Phase..
Wed Aug  2 17:43:23 2017 - [info] 
Wed Aug  2 17:43:23 2017 - [info] Forcing shutdown so that applications never connect to the current master..
Wed Aug  2 17:43:23 2017 - [info] Executing master IP deactivatation script:
Wed Aug  2 17:43:23 2017 - [info]   /usr/local/bin/master_ip_failover --orig_master_host=db1 --orig_master_ip=192.168.199.134 --orig_master_port=3306 --command=stopssh --ssh_user=root  


IN SCRIPT TEST====/sbin/ifconfig eth0:1 down==/sbin/ifconfig eth0:1 192.168.199.110===

Disabling the VIP on old master: db1 
Wed Aug  2 17:43:24 2017 - [info]  done.
Wed Aug  2 17:43:24 2017 - [warning] shutdown_script is not set. Skipping explicit shutting down of the dead master.
Wed Aug  2 17:43:24 2017 - [info] * Phase 2: Dead Master Shutdown Phase completed.
Wed Aug  2 17:43:24 2017 - [info] 
Wed Aug  2 17:43:24 2017 - [info] * Phase 3: Master Recovery Phase..
Wed Aug  2 17:43:24 2017 - [info] 
Wed Aug  2 17:43:24 2017 - [info] * Phase 3.1: Getting Latest Slaves Phase..
Wed Aug  2 17:43:24 2017 - [info] 
Wed Aug  2 17:43:24 2017 - [info] The latest binary log file/position on all slaves is mysql-bin.000001:120764079
Wed Aug  2 17:43:24 2017 - [info] Retrieved Gtid Set: 3604995d-6749-11e7-9c03-002564864074:1-154564
Wed Aug  2 17:43:24 2017 - [info] Latest slaves (Slaves that received relay log files to the latest):
Wed Aug  2 17:43:24 2017 - [info]   db3(192.168.199.233:3306)  Version=5.7.18-log (oldest major version between slaves) log-bin:enabled
Wed Aug  2 17:43:24 2017 - [info]     GTID ON
Wed Aug  2 17:43:24 2017 - [info]     Replicating from 192.168.199.134(192.168.199.134:3306)
Wed Aug  2 17:43:24 2017 - [info] The oldest binary log file/position on all slaves is mysql-bin.000001:34194847
Wed Aug  2 17:43:24 2017 - [info] Retrieved Gtid Set: 3604995d-6749-11e7-9c03-002564864074:1-25584
Wed Aug  2 17:43:24 2017 - [info] Oldest slaves:
Wed Aug  2 17:43:24 2017 - [info]   db2(192.168.199.212:3306)  Version=5.7.18-log (oldest major version between slaves) log-bin:enabled
Wed Aug  2 17:43:24 2017 - [info]     GTID ON
Wed Aug  2 17:43:24 2017 - [info]     Replicating from 192.168.199.134(192.168.199.134:3306)
Wed Aug  2 17:43:24 2017 - [info]     Primary candidate for the new Master (candidate_master is set)
Wed Aug  2 17:43:24 2017 - [info] 
Wed Aug  2 17:43:24 2017 - [info] * Phase 3.3: Determining New Master Phase..
Wed Aug  2 17:43:24 2017 - [info] 
Wed Aug  2 17:43:24 2017 - [info] Searching new master from slaves..
Wed Aug  2 17:43:24 2017 - [info]  Candidate masters from the configuration file:
Wed Aug  2 17:43:24 2017 - [info]   db2(192.168.199.212:3306)  Version=5.7.18-log (oldest major version between slaves) log-bin:enabled
Wed Aug  2 17:43:24 2017 - [info]     GTID ON
Wed Aug  2 17:43:24 2017 - [info]     Replicating from 192.168.199.134(192.168.199.134:3306)
Wed Aug  2 17:43:24 2017 - [info]     Primary candidate for the new Master (candidate_master is set)
Wed Aug  2 17:43:24 2017 - [info]  Non-candidate masters:
Wed Aug  2 17:43:24 2017 - [info]  Searching from candidate_master slaves which have received the latest relay log events..
Wed Aug  2 17:43:24 2017 - [info]   Not found.
Wed Aug  2 17:43:24 2017 - [info]  Searching from all candidate_master slaves..
Wed Aug  2 17:43:24 2017 - [info] New master is db2(192.168.199.212:3306)
Wed Aug  2 17:43:24 2017 - [info] Starting master failover..
Wed Aug  2 17:43:24 2017 - [info] 
From:
db1 (current master)
 +--db2
 +--db3

To:
db2 (new master)
 +--db3
Wed Aug  2 17:43:24 2017 - [info] 
Wed Aug  2 17:43:24 2017 - [info] * Phase 3.3: New Master Recovery Phase..
Wed Aug  2 17:43:24 2017 - [info] 
Wed Aug  2 17:43:24 2017 - [info]  Waiting all logs to be applied.. 
Wed Aug  2 17:43:24 2017 - [info]   done.
Wed Aug  2 17:43:24 2017 - [info]  Replicating from the latest slave db3(192.168.199.233:3306) and waiting to apply..
Wed Aug  2 17:43:24 2017 - [info]  Waiting all logs to be applied on the latest slave.. 
Wed Aug  2 17:43:24 2017 - [info]  Resetting slave db2(192.168.199.212:3306) and starting replication from the new master db3(192.168.199.233:3306)..
Wed Aug  2 17:43:24 2017 - [info]  Executed CHANGE MASTER.
Wed Aug  2 17:43:24 2017 - [info]  Slave started.
Wed Aug  2 17:43:24 2017 - [info]  Waiting to execute all relay logs on db2(192.168.199.212:3306)..
Wed Aug  2 17:44:38 2017 - [info]  master_pos_wait(mysql-bin.000001:119063908) completed on db2(192.168.199.212:3306). Executed 128588 events.
Wed Aug  2 17:44:38 2017 - [info]   done.
Wed Aug  2 17:44:38 2017 - [info]   done.
Wed Aug  2 17:44:38 2017 - [info] Getting new master's binlog name and position..
Wed Aug  2 17:44:38 2017 - [info]  mysql-bin.000001:119063908
Wed Aug  2 17:44:38 2017 - [info]  All other slaves should start replication from here. Statement should be: CHANGE MASTER TO MASTER_HOST='db2 or 192.168.199.212', MASTER_PORT=3306, MASTER_AUTO_POSITION=1, MASTER_USER='repl', MASTER_PASSWORD='xxx';
Wed Aug  2 17:44:38 2017 - [info] Master Recovery succeeded. File:Pos:Exec_Gtid_Set: mysql-bin.000001, 119063908, 3604995d-6749-11e7-9c03-002564864074:1-154564
Wed Aug  2 17:44:38 2017 - [info] Executing master IP activate script:
Wed Aug  2 17:44:38 2017 - [info]   /usr/local/bin/master_ip_failover --command=start --ssh_user=root --orig_master_host=db1 --orig_master_ip=192.168.199.134 --orig_master_port=3306 --new_master_host=db2 --new_master_ip=192.168.199.212 --new_master_port=3306 --new_master_user='root' --new_master_password='unixfbi'  
Unknown option: new_master_user
Unknown option: new_master_password


IN SCRIPT TEST====/sbin/ifconfig eth0:1 down==/sbin/ifconfig eth0:1 192.168.199.110===

Enabling the VIP - 192.168.199.110 on the new master - db2 
Wed Aug  2 17:44:39 2017 - [info]  OK.
Wed Aug  2 17:44:39 2017 - [info] Setting read_only=0 on db2(192.168.199.212:3306)..
Wed Aug  2 17:44:39 2017 - [info]  ok.
Wed Aug  2 17:44:39 2017 - [info] ** Finished master recovery successfully.
Wed Aug  2 17:44:39 2017 - [info] * Phase 3: Master Recovery Phase completed.
Wed Aug  2 17:44:39 2017 - [info] 
Wed Aug  2 17:44:39 2017 - [info] * Phase 4: Slaves Recovery Phase..
Wed Aug  2 17:44:39 2017 - [info] 
Wed Aug  2 17:44:39 2017 - [info] 
Wed Aug  2 17:44:39 2017 - [info] * Phase 4.1: Starting Slaves in parallel..
Wed Aug  2 17:44:39 2017 - [info] 
Wed Aug  2 17:44:39 2017 - [info] -- Slave recovery on host db3(192.168.199.233:3306) started, pid: 21651. Check tmp log /var/log/masterha/app1/db3_3306_20170802174322.log if it takes time..
Wed Aug  2 17:44:40 2017 - [info] 
Wed Aug  2 17:44:40 2017 - [info] Log messages from db3 ...
Wed Aug  2 17:44:40 2017 - [info] 
Wed Aug  2 17:44:39 2017 - [info]  Resetting slave db3(192.168.199.233:3306) and starting replication from the new master db2(192.168.199.212:3306)..
Wed Aug  2 17:44:39 2017 - [info]  Executed CHANGE MASTER.
Wed Aug  2 17:44:39 2017 - [info]  Slave started.
Wed Aug  2 17:44:40 2017 - [info] End of log messages from db3.
Wed Aug  2 17:44:40 2017 - [info] -- Slave on host db3(192.168.199.233:3306) started.
Wed Aug  2 17:44:40 2017 - [info] All new slave servers recovered successfully.
Wed Aug  2 17:44:40 2017 - [info] 
Wed Aug  2 17:44:40 2017 - [info] * Phase 5: New master cleanup phase..
Wed Aug  2 17:44:40 2017 - [info] 
Wed Aug  2 17:44:40 2017 - [info] Resetting slave info on the new master..
Wed Aug  2 17:44:40 2017 - [info]  db2: Resetting slave info succeeded.
Wed Aug  2 17:44:40 2017 - [info] Master failover to db2(192.168.199.212:3306) completed successfully.
Wed Aug  2 17:44:40 2017 - [info] Deleted server1 entry from /etc/masterha/app1.cnf .
Wed Aug  2 17:44:40 2017 - [info] 

----- Failover Report -----

app1: MySQL Master failover db1 to db2 succeeded

Master db1 is down!

Check MHA Manager logs at monitor:/var/log/masterha/app1/manager.log for details.

Started automated(non-interactive) failover.
Invalidated master IP address on db1.
Selected db2 as a new master.
db2: OK: Applying all logs succeeded.
db2: OK: Activated master IP address.
db3: OK: Slave started, replicating from db2.
db2: Resetting slave info succeeded.
Master failover to db2(192.168.199.212:3306) completed successfully.
Wed Aug  2 17:44:40 2017 - [info] Sending mail..
Unknown option: conf

从上面的输出可以看出整个 MHA 的切换过程,共包括以下几个步骤:
配置文件检查阶段,这个阶段将会检查整个集群配置文件设置;
宕机的 master 处理,这个阶段包括虚拟 IP 摘除操作,主机关机等操作;
复制 dead master 和最新 slave 相差的 relay log ,并保存到 MHA Manager 具体目录下;
识别含有最新更新的 slave;
应用差异的中继日志(relay log)到其他 slave;
应用从 master 保存的二进制日志事件(binlog events);
提升一个 slave 为最新 master;
使其他的 slave 连接新的 master 进行复制;
可以通过 Failover Report 可以看到 Failover 成功,这里需要注意的是:在 db1 宕机之后,进行追赶的时候,虚拟 IP 会暂时保留在原先的地方,只要追赶上去之后 VIP 就会切到最新的 Slave 上 db2。并且进行一次切换,masterha_manager 就自动停止,需要再次手动开启才能再进行监控。上面的测试说明:延迟越久,切换也越久。即使没有延迟也至少要 3s 以上的切换时间,因为在 M 发生宕机的时候需要 3s 的重连验证。

6.查看 vip 的漂移情况

在 db2 机器上查看

[root@db2 ~]# ip add
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN 
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 44:37:e6:19:85:81 brd ff:ff:ff:ff:ff:ff
    inet 192.168.199.212/24 brd 192.168.199.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet 192.168.199.110/24 brd 192.168.199.255 scope global secondary eth0:1
       valid_lft forever preferred_lft forever
    inet6 fe80::4637:e6ff:fe19:8581/64 scope link 
       valid_lft forever preferred_lft forever

VIP 已经漂移到 db2 上了

7.查看 db2 有多少条数据

root@localhost [sbtest]>use sbtest;
Database changed
root@localhost [sbtest]>select count(*) from sbtest1;
+----------+
| count(*) |
+----------+
|   229151 |
+----------+
1 row in set (0.11 sec)

db3
root@localhost [mysql]>use sbtest;
Database changed
root@localhost [sbtest]>select count(*) from sbtest1;
+----------+
| count(*) |
+----------+
|   125887 |
+----------+
1 row in set (0.18 sec)

发现和之前的主库 db1 一致;

注意:一旦发生切换管理进程(Manager)将会退出,无法进行再次测试,需将故障数据库解决掉之后,重新 change 加入到 MHA 环境中来,并且要保证 app1.failover.complete 不存在或则加上--ignore_last_failover 参数忽略,才能再次开启管理进程。

五、手动 Failover

此时 MHA Manager 必须是关闭状态

1.关闭 db1 上的 MySQL

# /etc/init.d/mysqld stop

2.在 monitor 机器上操作如下

把 master 从 db1 切换到 db3

# masterha_master_switch --master_state=dead --conf=/etc/masterha/app1.cnf --dead_master_host=db1 --dead_master_port=3306 --new_master_host=db3  --new_master_port=3306 --ignore_last_failover 

输出日志如下:

--dead_master_ip=<dead_master_ip> is not set. Using 192.168.199.134.
Thu Aug  3 19:42:01 2017 - [warning] Global configuration file /etc/masterha_default.cnf not found. Skipping.
Thu Aug  3 19:42:01 2017 - [info] Reading application default configurations from /etc/masterha/app1.cnf..
Thu Aug  3 19:42:01 2017 - [info] Reading server configurations from /etc/masterha/app1.cnf..
Thu Aug  3 19:42:01 2017 - [info] MHA::MasterFailover version 0.56.
Thu Aug  3 19:42:01 2017 - [info] Starting master failover.
Thu Aug  3 19:42:01 2017 - [info] 
Thu Aug  3 19:42:01 2017 - [info] * Phase 1: Configuration Check Phase..
Thu Aug  3 19:42:01 2017 - [info] 
Thu Aug  3 19:42:02 2017 - [info] Dead Servers:
Thu Aug  3 19:42:02 2017 - [info]   db1(192.168.199.134:3306)
Thu Aug  3 19:42:02 2017 - [info] Checking master reachability via mysql(double check)..
Thu Aug  3 19:42:02 2017 - [info]  ok.
Thu Aug  3 19:42:02 2017 - [info] Alive Servers:
Thu Aug  3 19:42:02 2017 - [info]   db2(192.168.199.212:3306)
Thu Aug  3 19:42:02 2017 - [info]   db3(192.168.199.233:3306)
Thu Aug  3 19:42:02 2017 - [info] Alive Slaves:
Thu Aug  3 19:42:02 2017 - [info]   db2(192.168.199.212:3306)  Version=5.7.18-log (oldest major version between slaves) log-bin:enabled
Thu Aug  3 19:42:02 2017 - [info]     GTID ON
Thu Aug  3 19:42:02 2017 - [info]     Replicating from 192.168.199.134(192.168.199.134:3306)
Thu Aug  3 19:42:02 2017 - [info]     Primary candidate for the new Master (candidate_master is set)
Thu Aug  3 19:42:02 2017 - [info]   db3(192.168.199.233:3306)  Version=5.7.18-log (oldest major version between slaves) log-bin:enabled
Thu Aug  3 19:42:02 2017 - [info]     GTID ON
Thu Aug  3 19:42:02 2017 - [info]     Replicating from 192.168.199.134(192.168.199.134:3306)
Master db1 is dead. Proceed? (yes/NO): yes
Thu Aug  3 19:42:06 2017 - [info] ** Phase 1: Configuration Check Phase completed.
Thu Aug  3 19:42:06 2017 - [info] 
Thu Aug  3 19:42:06 2017 - [info] * Phase 2: Dead Master Shutdown Phase..
Thu Aug  3 19:42:06 2017 - [info] 
Thu Aug  3 19:42:06 2017 - [info] HealthCheck: SSH to db1 is reachable.
Thu Aug  3 19:42:07 2017 - [info] Forcing shutdown so that applications never connect to the current master..
Thu Aug  3 19:42:07 2017 - [info] Executing master IP deactivatation script:
Thu Aug  3 19:42:07 2017 - [info]   /usr/local/bin/master_ip_failover --orig_master_host=db1 --orig_master_ip=192.168.199.134 --orig_master_port=3306 --command=stopssh --ssh_user=root  


IN SCRIPT TEST====/sbin/ifconfig eth0:1 down==/sbin/ifconfig eth0:1 192.168.199.110===

Disabling the VIP on old master: db1 
Thu Aug  3 19:42:07 2017 - [info]  done.
Thu Aug  3 19:42:07 2017 - [warning] shutdown_script is not set. Skipping explicit shutting down of the dead master.
Thu Aug  3 19:42:07 2017 - [info] * Phase 2: Dead Master Shutdown Phase completed.
Thu Aug  3 19:42:07 2017 - [info] 
Thu Aug  3 19:42:07 2017 - [info] * Phase 3: Master Recovery Phase..
Thu Aug  3 19:42:07 2017 - [info] 
Thu Aug  3 19:42:07 2017 - [info] * Phase 3.1: Getting Latest Slaves Phase..
Thu Aug  3 19:42:07 2017 - [info] 
Thu Aug  3 19:42:07 2017 - [info] The latest binary log file/position on all slaves is mysql-bin.000002:194
Thu Aug  3 19:42:07 2017 - [info] Latest slaves (Slaves that received relay log files to the latest):
Thu Aug  3 19:42:07 2017 - [info]   db2(192.168.199.212:3306)  Version=5.7.18-log (oldest major version between slaves) log-bin:enabled
Thu Aug  3 19:42:07 2017 - [info]     GTID ON
Thu Aug  3 19:42:07 2017 - [info]     Replicating from 192.168.199.134(192.168.199.134:3306)
Thu Aug  3 19:42:07 2017 - [info]     Primary candidate for the new Master (candidate_master is set)
Thu Aug  3 19:42:07 2017 - [info]   db3(192.168.199.233:3306)  Version=5.7.18-log (oldest major version between slaves) log-bin:enabled
Thu Aug  3 19:42:07 2017 - [info]     GTID ON
Thu Aug  3 19:42:07 2017 - [info]     Replicating from 192.168.199.134(192.168.199.134:3306)
Thu Aug  3 19:42:07 2017 - [info] The oldest binary log file/position on all slaves is mysql-bin.000002:194
Thu Aug  3 19:42:07 2017 - [info] Oldest slaves:
Thu Aug  3 19:42:07 2017 - [info]   db2(192.168.199.212:3306)  Version=5.7.18-log (oldest major version between slaves) log-bin:enabled
Thu Aug  3 19:42:07 2017 - [info]     GTID ON
Thu Aug  3 19:42:07 2017 - [info]     Replicating from 192.168.199.134(192.168.199.134:3306)
Thu Aug  3 19:42:07 2017 - [info]     Primary candidate for the new Master (candidate_master is set)
Thu Aug  3 19:42:07 2017 - [info]   db3(192.168.199.233:3306)  Version=5.7.18-log (oldest major version between slaves) log-bin:enabled
Thu Aug  3 19:42:07 2017 - [info]     GTID ON
Thu Aug  3 19:42:07 2017 - [info]     Replicating from 192.168.199.134(192.168.199.134:3306)
Thu Aug  3 19:42:07 2017 - [info] 
Thu Aug  3 19:42:07 2017 - [info] * Phase 3.3: Determining New Master Phase..
Thu Aug  3 19:42:07 2017 - [info] 
Thu Aug  3 19:42:07 2017 - [info] db3 can be new master.
Thu Aug  3 19:42:07 2017 - [info] New master is db3(192.168.199.233:3306)
Thu Aug  3 19:42:07 2017 - [info] Starting master failover..
Thu Aug  3 19:42:07 2017 - [info] 
From:
db1 (current master)
 +--db2
 +--db3

To:
db3 (new master)
 +--db2

Starting master switch from db1(192.168.199.134:3306) to db3(192.168.199.233:3306)? (yes/NO): yes
Thu Aug  3 19:42:14 2017 - [info] New master decided manually is db3(192.168.199.233:3306)
Thu Aug  3 19:42:14 2017 - [info] 
Thu Aug  3 19:42:14 2017 - [info] * Phase 3.3: New Master Recovery Phase..
Thu Aug  3 19:42:14 2017 - [info] 
Thu Aug  3 19:42:14 2017 - [info]  Waiting all logs to be applied.. 
Thu Aug  3 19:42:14 2017 - [info]   done.
Thu Aug  3 19:42:14 2017 - [info]  Replicating from the latest slave db2(192.168.199.212:3306) and waiting to apply..
Thu Aug  3 19:42:14 2017 - [info]  Waiting all logs to be applied on the latest slave.. 
Thu Aug  3 19:42:14 2017 - [info]  Resetting slave db3(192.168.199.233:3306) and starting replication from the new master db2(192.168.199.212:3306)..
Thu Aug  3 19:42:15 2017 - [info]  Executed CHANGE MASTER.
Thu Aug  3 19:42:15 2017 - [info]  Slave started.
Thu Aug  3 19:42:15 2017 - [info]  Waiting to execute all relay logs on db3(192.168.199.233:3306)..
Thu Aug  3 19:42:15 2017 - [info]  master_pos_wait(mysql-bin.000002:194) completed on db3(192.168.199.233:3306). Executed 2 events.
Thu Aug  3 19:42:15 2017 - [info]   done.
Thu Aug  3 19:42:15 2017 - [info]   done.
Thu Aug  3 19:42:15 2017 - [info] Getting new master's binlog name and position..
Thu Aug  3 19:42:15 2017 - [info]  mysql-bin.000002:194
Thu Aug  3 19:42:15 2017 - [info]  All other slaves should start replication from here. Statement should be: CHANGE MASTER TO MASTER_HOST='db3 or 192.168.199.233', MASTER_PORT=3306, MASTER_AUTO_POSITION=1, MASTER_USER='repl', MASTER_PASSWORD='xxx';
Thu Aug  3 19:42:15 2017 - [info] Master Recovery succeeded. File:Pos:Exec_Gtid_Set: mysql-bin.000002, 194, 3604995d-6749-11e7-9c03-002564864074:1-154564
Thu Aug  3 19:42:15 2017 - [info] Executing master IP activate script:
Thu Aug  3 19:42:15 2017 - [info]   /usr/local/bin/master_ip_failover --command=start --ssh_user=root --orig_master_host=db1 --orig_master_ip=192.168.199.134 --orig_master_port=3306 --new_master_host=db3 --new_master_ip=192.168.199.233 --new_master_port=3306 --new_master_user='root' --new_master_password='unixfbi'  
Unknown option: new_master_user
Unknown option: new_master_password


IN SCRIPT TEST====/sbin/ifconfig eth0:1 down==/sbin/ifconfig eth0:1 192.168.199.110===

Enabling the VIP - 192.168.199.110 on the new master - db3 
Thu Aug  3 19:42:15 2017 - [info]  OK.
Thu Aug  3 19:42:15 2017 - [info] ** Finished master recovery successfully.
Thu Aug  3 19:42:15 2017 - [info] * Phase 3: Master Recovery Phase completed.
Thu Aug  3 19:42:15 2017 - [info] 
Thu Aug  3 19:42:15 2017 - [info] * Phase 4: Slaves Recovery Phase..
Thu Aug  3 19:42:15 2017 - [info] 
Thu Aug  3 19:42:15 2017 - [info] 
Thu Aug  3 19:42:15 2017 - [info] * Phase 4.1: Starting Slaves in parallel..
Thu Aug  3 19:42:15 2017 - [info] 
Thu Aug  3 19:42:15 2017 - [info] -- Slave recovery on host db2(192.168.199.212:3306) started, pid: 24461. Check tmp log /var/log/masterha/app1/db2_3306_20170803194201.log if it takes time..
Thu Aug  3 19:42:16 2017 - [info] 
Thu Aug  3 19:42:16 2017 - [info] Log messages from db2 ...
Thu Aug  3 19:42:16 2017 - [info] 
Thu Aug  3 19:42:15 2017 - [info]  Resetting slave db2(192.168.199.212:3306) and starting replication from the new master db3(192.168.199.233:3306)..
Thu Aug  3 19:42:16 2017 - [info]  Executed CHANGE MASTER.
Thu Aug  3 19:42:16 2017 - [info]  Slave started.
Thu Aug  3 19:42:16 2017 - [info] End of log messages from db2.
Thu Aug  3 19:42:16 2017 - [info] -- Slave on host db2(192.168.199.212:3306) started.
Thu Aug  3 19:42:16 2017 - [info] All new slave servers recovered successfully.
Thu Aug  3 19:42:16 2017 - [info] 
Thu Aug  3 19:42:16 2017 - [info] * Phase 5: New master cleanup phase..
Thu Aug  3 19:42:16 2017 - [info] 
Thu Aug  3 19:42:16 2017 - [info] Resetting slave info on the new master..
Thu Aug  3 19:42:16 2017 - [info]  db3: Resetting slave info succeeded.
Thu Aug  3 19:42:16 2017 - [info] Master failover to db3(192.168.199.233:3306) completed successfully.
Thu Aug  3 19:42:16 2017 - [info] 

----- Failover Report -----

app1: MySQL Master failover db1 to db3 succeeded

Master db1 is down!

Check MHA Manager logs at monitor for details.

Started manual(interactive) failover.
Invalidated master IP address on db1.
Selected db3 as a new master.
db3: OK: Applying all logs succeeded.
db3: OK: Activated master IP address.
db2: OK: Slave started, replicating from db3.
db3: Resetting slave info succeeded.
Master failover to db3(192.168.199.233:3306) completed successfully.
Thu Aug  3 19:42:16 2017 - [info] Sending mail..
Unknown option: conf

根据日志可以看出已经 master 已经切换到了 db3 上,而且当时 db3 也不是最新的 slave,所以又从 db2 复制数据和日志,然后才把 db3 提升为 master。我们在工作中手动切换的时候一定要查找到日志最新的 slave,这样可以节省很多宝贵的时间。

3.查看 vip 是否切换成功

在 db3 上执行

# ip add
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether b8:ac:6f:3e:a3:c6 brd ff:ff:ff:ff:ff:ff
    inet 192.168.199.233/24 brd 192.168.199.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet 192.168.199.110/24 brd 192.168.199.255 scope global secondary eth0:1
       valid_lft forever preferred_lft forever
    inet6 fe80::baac:6fff:fe3e:a3c6/64 scope link 
       valid_lft forever preferred_lft forever

VIP 已经切换到了 db3 上。

六、MHA 在线切换

在许多情况下, 需要将现有的主服务器迁移到另外一台服务器上。 比如主服务器硬件故障,RAID 控制卡需要重建,将主服务器移到性能更好的服务器上等等。维护主服务器引起性能下降, 导致停机时间至少无法写入数据。 另外, 阻塞或杀掉当前运行的会话会导致主主之间数据不一致的问题发生。 MHA 提供快速切换和优雅的阻塞写入,这个切换过程只需要 0.5-2s 的时间,这段时间内数据是无法写入的。在很多情况下,0.5-2s 的阻塞写入是可以接受的。因此切换主服务器不需要计划分配维护时间窗口。

MHA 在线切换的大概过程:
(1)检测复制设置和确定当前主服务器
(2)确定新的主服务器
(3)阻塞写入到当前主服务器
(4)等待所有从服务器赶上复制
(5)授予写入到新的主服务器
(6)重新设置从服务器

注意,在线切换的时候应用架构需要考虑以下两个问题:

(1)自动识别 master 和 slave 的问题(master 的机器可能会切换),如果采用了 vip 的方式,基本可以解决这个问题。

(2)负载均衡的问题(可以定义大概的读写比例,每台机器可承担的负载比例,当有机器离开集群时,需要考虑这个问题)

为了保证数据完全一致性,在最快的时间内完成切换,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 秒。

1.停掉 MHA 监控

masterha_stop --conf=/etc/masterha/app1.cnf

2.创建脚本

这个脚本可以复制 MHA 自带的,但是需要修改,我是直接借鉴别人的脚本。

# cat /usr/local/bin/master_ip_online_change 
#!/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_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 = '192.168.199.110/24';
my $key = '1';
my $ssh_start_vip = "/sbin/ifconfig eth0:$key $vip";
my $ssh_stop_vip = "/sbin/ifconfig eth0:$key down";
my $orig_master_ssh_port = 22;
my $new_master_ssh_port = 22;
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,
  'orig_master_ssh_port=i'    => \$orig_master_ssh_port,
  'new_master_ssh_port=i'    => \$new_master_ssh_port,
);

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} );
      }

      ## 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
      eval {
      `ssh -p$orig_master_ssh_port $orig_master_ssh_user\@$orig_master_host \" $ssh_stop_vip \"`;
        };
        if ($@) {
            warn $@;
        }
      $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
      `ssh -p$new_master_ssh_port $new_master_ssh_user\@$new_master_host \" $ssh_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;
  }
}

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;
}

3.进行在线操作

模拟在线切换主从操作,原主库 db1 变为 slave,db2 提升为新的 master 库

# masterha_master_switch --conf=/etc/masterha/app1.cnf --master_state=alive --new_master_host=db2 --new_master_port=3306  --orig_master_is_new_slave --running_updates_limit=10000

参数解释:
其中--orig_master_is_new_slave 的意思是将原来 master 变换成 slave,默认 MHA 下不做上述操作。
在--running_updates_limit=10000 切换时,如果原库执行的写入更新操作花费的时间大于默认 1 秒,或者新的主库和老的主库间存在主从延时的秒数大于默认 1s,那么 MHA 进行在线切换时将会失败,加上该参数代表,允许在主从延时的时间小于 running_updates_limit 内核主库执行写入 SQL 大于该参数设定的范围内成功在线切换 MHA。

4.查看日志

[root@monitor /]# masterha_master_switch --conf=/etc/masterha/app1.cnf --master_state=alive --new_master_host=db2 --new_master_port=3306  --orig_master_is_new_slave --running_updates_limit=10000
Thu Aug  3 21:02:49 2017 - [info] MHA::MasterRotate version 0.56.
Thu Aug  3 21:02:49 2017 - [info] Starting online master switch..
Thu Aug  3 21:02:49 2017 - [info] 
Thu Aug  3 21:02:49 2017 - [info] * Phase 1: Configuration Check Phase..
Thu Aug  3 21:02:49 2017 - [info] 
Thu Aug  3 21:02:49 2017 - [warning] Global configuration file /etc/masterha_default.cnf not found. Skipping.
Thu Aug  3 21:02:49 2017 - [info] Reading application default configurations from /etc/masterha/app1.cnf..
Thu Aug  3 21:02:49 2017 - [info] Reading server configurations from /etc/masterha/app1.cnf..
Thu Aug  3 21:02:50 2017 - [info] Current Alive Master: db1(192.168.199.134:3306)
Thu Aug  3 21:02:50 2017 - [info] Alive Slaves:
Thu Aug  3 21:02:50 2017 - [info]   db2(192.168.199.212:3306)  Version=5.7.18-log (oldest major version between slaves) log-bin:enabled
Thu Aug  3 21:02:50 2017 - [info]     GTID ON
Thu Aug  3 21:02:50 2017 - [info]     Replicating from db1(192.168.199.134:3306)
Thu Aug  3 21:02:50 2017 - [info]     Primary candidate for the new Master (candidate_master is set)
Thu Aug  3 21:02:50 2017 - [info]   db3(192.168.199.233:3306)  Version=5.7.18-log (oldest major version between slaves) log-bin:enabled
Thu Aug  3 21:02:50 2017 - [info]     GTID ON
Thu Aug  3 21:02:50 2017 - [info]     Replicating from 192.168.199.134(192.168.199.134:3306)

It is better to execute FLUSH NO_WRITE_TO_BINLOG TABLES on the master before switching. Is it ok to execute on db1(192.168.199.134:3306)? (YES/no): yes
Thu Aug  3 21:02:55 2017 - [info] Executing FLUSH NO_WRITE_TO_BINLOG TABLES. This may take long time..
Thu Aug  3 21:02:55 2017 - [info]  ok.
Thu Aug  3 21:02:55 2017 - [info] Checking MHA is not monitoring or doing failover..
Thu Aug  3 21:02:55 2017 - [info] Checking replication health on db2..
Thu Aug  3 21:02:55 2017 - [info]  ok.
Thu Aug  3 21:02:55 2017 - [info] Checking replication health on db3..
Thu Aug  3 21:02:55 2017 - [info]  ok.
Thu Aug  3 21:02:55 2017 - [info] db2 can be new master.
Thu Aug  3 21:02:55 2017 - [info] 
From:
db1 (current master)
 +--db2
 +--db3

To:
db2 (new master)
 +--db3
 +--db1

Starting master switch from db1(192.168.199.134:3306) to db2(192.168.199.212:3306)? (yes/NO): yes
Thu Aug  3 21:02:57 2017 - [info] Checking whether db2(192.168.199.212:3306) is ok for the new master..
Thu Aug  3 21:02:57 2017 - [info]  ok.
Thu Aug  3 21:02:57 2017 - [info] db1(192.168.199.134:3306): SHOW SLAVE STATUS returned empty result. To check replication filtering rules, temporarily executing CHANGE MASTER to a dummy host.
Thu Aug  3 21:02:57 2017 - [info] db1(192.168.199.134:3306): Resetting slave pointing to the dummy host.
Thu Aug  3 21:02:57 2017 - [info] ** Phase 1: Configuration Check Phase completed.
Thu Aug  3 21:02:57 2017 - [info] 
Thu Aug  3 21:02:57 2017 - [info] * Phase 2: Rejecting updates Phase..
Thu Aug  3 21:02:57 2017 - [info] 
Thu Aug  3 21:02:57 2017 - [info] Executing master ip online change script to disable write on the current master:
Thu Aug  3 21:02:57 2017 - [info]   /usr/local/bin/master_ip_online_change --command=stop --orig_master_host=db1 --orig_master_ip=192.168.199.134 --orig_master_port=3306 --orig_master_user='root' --orig_master_password='unixfbi' --new_master_host=db2 --new_master_ip=192.168.199.212 --new_master_port=3306 --new_master_user='root' --new_master_password='unixfbi' --orig_master_ssh_user=root --new_master_ssh_user=root  
Thu Aug  3 21:02:58 2017 019010 Set read_only on the new master.. ok.
Thu Aug  3 21:02:58 2017 021612 Drpping app user on the orig master..
Thu Aug  3 21:02:58 2017 022089 Waiting all running 2 threads are disconnected.. (max 1500 milliseconds)
{'Time' => '142','db' => undef,'Id' => '57','User' => 'repl','State' => 'Master has sent all binlog to slave; waiting for more updates','Command' => 'Binlog Dump GTID','Info' => undef,'Host' => 'db3:1494'}
{'Time' => '142','db' => undef,'Id' => '58','User' => 'repl','State' => 'Master has sent all binlog to slave; waiting for more updates','Command' => 'Binlog Dump GTID','Info' => undef,'Host' => 'db2:54392'}
Thu Aug  3 21:02:58 2017 523221 Waiting all running 2 threads are disconnected.. (max 1000 milliseconds)
{'Time' => '142','db' => undef,'Id' => '57','User' => 'repl','State' => 'Master has sent all binlog to slave; waiting for more updates','Command' => 'Binlog Dump GTID','Info' => undef,'Host' => 'db3:1494'}
{'Time' => '142','db' => undef,'Id' => '58','User' => 'repl','State' => 'Master has sent all binlog to slave; waiting for more updates','Command' => 'Binlog Dump GTID','Info' => undef,'Host' => 'db2:54392'}
Thu Aug  3 21:02:59 2017 023949 Waiting all running 2 threads are disconnected.. (max 500 milliseconds)
{'Time' => '143','db' => undef,'Id' => '57','User' => 'repl','State' => 'Master has sent all binlog to slave; waiting for more updates','Command' => 'Binlog Dump GTID','Info' => undef,'Host' => 'db3:1494'}
{'Time' => '143','db' => undef,'Id' => '58','User' => 'repl','State' => 'Master has sent all binlog to slave; waiting for more updates','Command' => 'Binlog Dump GTID','Info' => undef,'Host' => 'db2:54392'}
Thu Aug  3 21:02:59 2017 524548 Set read_only=1 on the orig master.. ok.
Thu Aug  3 21:02:59 2017 526965 Waiting all running 2 queries are disconnected.. (max 500 milliseconds)
{'Time' => '143','db' => undef,'Id' => '57','User' => 'repl','State' => 'Master has sent all binlog to slave; waiting for more updates','Command' => 'Binlog Dump GTID','Info' => undef,'Host' => 'db3:1494'}
{'Time' => '143','db' => undef,'Id' => '58','User' => 'repl','State' => 'Master has sent all binlog to slave; waiting for more updates','Command' => 'Binlog Dump GTID','Info' => undef,'Host' => 'db2:54392'}
Thu Aug  3 21:03:00 2017 025399 Killing all application threads..
Thu Aug  3 21:03:00 2017 026118 done.
Thu Aug  3 21:03:00 2017 - [info]  ok.
Thu Aug  3 21:03:00 2017 - [info] Locking all tables on the orig master to reject updates from everybody (including root):
Thu Aug  3 21:03:00 2017 - [info] Executing FLUSH TABLES WITH READ LOCK..
Thu Aug  3 21:03:00 2017 - [info]  ok.
Thu Aug  3 21:03:00 2017 - [info] Orig master binlog:pos is mysql-bin.000003:194.
Thu Aug  3 21:03:00 2017 - [info]  Waiting to execute all relay logs on db2(192.168.199.212:3306)..
Thu Aug  3 21:03:00 2017 - [info]  master_pos_wait(mysql-bin.000003:194) completed on db2(192.168.199.212:3306). Executed 0 events.
Thu Aug  3 21:03:00 2017 - [info]   done.
Thu Aug  3 21:03:00 2017 - [info] Getting new master's binlog name and position..
Thu Aug  3 21:03:00 2017 - [info]  mysql-bin.000002:194
Thu Aug  3 21:03:00 2017 - [info]  All other slaves should start replication from here. Statement should be: CHANGE MASTER TO MASTER_HOST='db2 or 192.168.199.212', MASTER_PORT=3306, MASTER_AUTO_POSITION=1, MASTER_USER='repl', MASTER_PASSWORD='xxx';
Thu Aug  3 21:03:00 2017 - [info] Executing master ip online change script to allow write on the new master:
Thu Aug  3 21:03:00 2017 - [info]   /usr/local/bin/master_ip_online_change --command=start --orig_master_host=db1 --orig_master_ip=192.168.199.134 --orig_master_port=3306 --orig_master_user='root' --orig_master_password='unixfbi' --new_master_host=db2 --new_master_ip=192.168.199.212 --new_master_port=3306 --new_master_user='root' --new_master_password='unixfbi' --orig_master_ssh_user=root --new_master_ssh_user=root  
Thu Aug  3 21:03:00 2017 405371 Set read_only=0 on the new master.
Thu Aug  3 21:03:00 2017 406610 Creating app user on the new master..
Thu Aug  3 21:03:00 2017 - [info]  ok.
Thu Aug  3 21:03:00 2017 - [info] 
Thu Aug  3 21:03:00 2017 - [info] * Switching slaves in parallel..
Thu Aug  3 21:03:00 2017 - [info] 
Thu Aug  3 21:03:00 2017 - [info] -- Slave switch on host db3(192.168.199.233:3306) started, pid: 24710
Thu Aug  3 21:03:00 2017 - [info] 
Thu Aug  3 21:03:01 2017 - [info] Log messages from db3 ...
Thu Aug  3 21:03:01 2017 - [info] 
Thu Aug  3 21:03:00 2017 - [info]  Waiting to execute all relay logs on db3(192.168.199.233:3306)..
Thu Aug  3 21:03:00 2017 - [info]  master_pos_wait(mysql-bin.000003:194) completed on db3(192.168.199.233:3306). Executed 0 events.
Thu Aug  3 21:03:00 2017 - [info]   done.
Thu Aug  3 21:03:00 2017 - [info]  Resetting slave db3(192.168.199.233:3306) and starting replication from the new master db2(192.168.199.212:3306)..
Thu Aug  3 21:03:01 2017 - [info]  Executed CHANGE MASTER.
Thu Aug  3 21:03:01 2017 - [info]  Slave started.
Thu Aug  3 21:03:01 2017 - [info] End of log messages from db3 ...
Thu Aug  3 21:03:01 2017 - [info] 
Thu Aug  3 21:03:01 2017 - [info] -- Slave switch on host db3(192.168.199.233:3306) succeeded.
Thu Aug  3 21:03:01 2017 - [info] Unlocking all tables on the orig master:
Thu Aug  3 21:03:01 2017 - [info] Executing UNLOCK TABLES..
Thu Aug  3 21:03:01 2017 - [info]  ok.
Thu Aug  3 21:03:01 2017 - [info] Starting orig master as a new slave..
Thu Aug  3 21:03:01 2017 - [info]  Resetting slave db1(192.168.199.134:3306) and starting replication from the new master db2(192.168.199.212:3306)..
Thu Aug  3 21:03:02 2017 - [info]  Executed CHANGE MASTER.
Thu Aug  3 21:03:02 2017 - [info]  Slave started.
Thu Aug  3 21:03:02 2017 - [info] All new slave servers switched successfully.
Thu Aug  3 21:03:02 2017 - [info] 
Thu Aug  3 21:03:02 2017 - [info] * Phase 5: New master cleanup phase..
Thu Aug  3 21:03:02 2017 - [info] 
Thu Aug  3 21:03:02 2017 - [info]  db2: Resetting slave info succeeded.
Thu Aug  3 21:03:02 2017 - [info] Switching master to db2(192.168.199.212:3306) completed successfully.

5.查看 VIP 是否切换成功

在 db2 上执行

[root@db2 ~]# ip add
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN 
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 44:37:e6:19:85:81 brd ff:ff:ff:ff:ff:ff
    inet 192.168.199.212/24 brd 192.168.199.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet 192.168.199.110/24 brd 192.168.199.255 scope global secondary eth0:1
       valid_lft forever preferred_lft forever
    inet6 fe80::4637:e6ff:fe19:8581/64 scope link 
       valid_lft forever preferred_lft forever

发现 VIP 已经绑定到了 db2 上。 上面实验操作成功

七、修复宕机的 master

通常情况下自动切换后,原 master 可能已经废弃掉,待原来 master 主机修复后,如果数据完整的情况下,可能想把原来 master 重新作为新主库的 slave,这时我们就需要借助当时自动切换时刻的 MHA 日志来完成对原来 master 的修复。下面是提取相关日志的命令

# grep -i "All other slaves should start replication from" /var/log/masterha/app1/manager.log 
Wed Aug  2 17:44:38 2017 - [info]  All other slaves should start replication from here. Statement should be: CHANGE MASTER TO MASTER_HOST='db2 or 192.168.199.212', MASTER_PORT=3306, MASTER_AUTO_POSITION=1, MASTER_USER='repl', MASTER_PASSWORD='xxx';

获取上述信息后,就可以直接在修 master 上执行 change master to 操作了。

mysql> CHANGE MASTER TO
  MASTER_HOST='192.168.199.212',
  MASTER_USER='repl',
  MASTER_PASSWORD='unixfbi',
  MASTER_PORT=3306,
  MASTER_AUTO_POSITION=1;

mysql> start slave;

八、配置 mha 为守护进程

因为 masterha_manager 进程运行一次后就会自动退出,我们在 MHA 在使用 MHA 过程需要该 MHA 一直是运行状态,所以我们配置为守护进程的方式运行。这里我们是通过 daemontools 软件进行管理进程的

1.安装 daemontools

# wget --no-check-certificate http://cr.yp.to/daemontools/daemontools-0.76.tar.gz
# tar zxf daemontools-0.76.tar.gz
# cd admin/daemontools-0.76/
# sed -i 's/extern int errno;/#include <errno.h>/1' ./src/error.h 
# ./package/install
# echo $?

安装完成之后,会创建 /service、 /command 两个目录

2.配置以 systemd 方式管理 daemontools

只需要创建/etc/systemd/system/daemontools.service 文件即可

# cat /etc/systemd/system/daemontools.service
[Unit]
Description=daemontools Start supervise
After=getty.target
[Service]
Type=simple
User=root
Group=root
Restart=always
ExecStart=/command/svscanboot /dev/ttyS0
TimeoutSec=0
[Install]
WantedBy=multi-user.target

3.配置启动 MHA

使用 daemontools 配置一个服务特别简单:
(1)创建一个目录,目录下放一个 run 脚本
(2)run 脚本执行启动服务的命令
(3)建立/service 下一个链接

创建脚本:

# mkdir /opt/svc/mha/ -pv
# vim /opt/svc/mha/run 
脚本内容:
#!/bin/bash
masterha_manager --conf=/etc/masterha/app1.cnf --remove_dead_master_conf --ignore_last_failover >> /var/log/masterha/app1/manager.log

run 需要有可执行权限

# chmod 755 /opt/svc/mha/run
# ln -s /opt/svc/mha/ /service/

4.启动 daemontools

# systemctl start daemontools.service 

查看状态:

# systemctl status daemontools.service               
● daemontools.service - daemontools Start supervise
   Loaded: loaded (/etc/systemd/system/daemontools.service; disabled; vendor preset: disabled)
   Active: active (running) since 二 2017-08-08 16:54:09 CST; 2min 33s ago
 Main PID: 746 (svscanboot)
   CGroup: /system.slice/daemontools.service
           ├─ 746 /bin/sh /command/svscanboot /dev/ttyS0
           ├─ 748 svscan /service
           ├─ 749 readproctitle service errors: ...vise: fatal: unable to start masterha_app1/run: access denied supervise: fata...
           ├─ 750 supervise masterha_app1
           ├─ 751 supervise mha
           ├─1056 /bin/bash ./run
           └─1057 perl /usr/local/bin/masterha_manager --conf=/etc/masterha/app1.cnf --remove_dead_master_conf --ignore_last_fai...

8 月 08 16:54:09 monitor systemd[1]: Started daemontools Start supervise.
8 月 08 16:54:09 monitor systemd[1]: Starting daemontools Start supervise...

查看启动进程信息:
使用 pstree 命令

# pstree -a -p 746
svscanboot,746 /command/svscanboot /dev/ttyS0
  ├─readproctitle,749 service errors:...
  └─svscan,748 /service
      ├─supervise,750 masterha_app1
      │   └─(supervise,14647)
      └─supervise,751 mha
          └─run,1616 ./run
              └─perl,1617 /usr/local/bin/masterha_manager --conf=/etc/masterha/app1.cnf --remove_dead_master_conf ...

从上面的信息可以看出来,svscanboot 负责启动 svscan 服务,svscan 管理 supervise 进程。而具体的客户进程,是通过 supervise 进程来统一管理的。

九、遇到问题

操作过程中遇到的问题

1.执行 masterha_check_repl 检查复制状态报错

执行# masterha_check_repl --conf=/etc/masterha/app1.cnf
报:
MySQL 高可用-MHA 架构搭建问题原因:
脚本中 95 行

$cmd = 'ssh '.$ssh_user.'@'.$orig_master_ip.' \'./lvs-admin stop\'';  

在前面需要用 my 声明变量

问题解决:
所以脚本中 95 行改为

my $cmd = 'ssh '.$ssh_user.'@'.$orig_master_ip.' \'./lvs-admin stop\'';

参考文档:
http://huhuixuefei.blog.163.com/blog/static/6521130820116135442781

2.检查主从配置是否正常时报错

# masterha_check_repl --conf=/etc/masterha/app1.cnf

执行上面的命令报错
报错:

Fri Jul 28 10:38:23 2017 - [warning] Global configuration file /etc/masterha_default.cnf not found. Skipping.
Fri Jul 28 10:38:23 2017 - [info] Reading application default configurations from /etc/masterha/app1.cnf..
Fri Jul 28 10:38:23 2017 - [info] Reading server configurations from /etc/masterha/app1.cnf..
Fri Jul 28 10:38:23 2017 - [info] MHA::MasterMonitor version 0.56.
Fri Jul 28 10:38:24 2017 - [error][/usr/local/share/perl5/MHA/ServerManager.pm, ln622] Master 192.168.199.134:3306 from which slave db3(192.168.199.233:3306) replicates is not defined in the configuration file!
Fri Jul 28 10:38:24 2017 - [error][/usr/local/share/perl5/MHA/MasterMonitor.pm, ln401] Error happend on checking configurations.  at /usr/local/share/perl5/MHA/MasterMonitor.pm line 306.
Fri Jul 28 10:38:24 2017 - [error][/usr/local/share/perl5/MHA/MasterMonitor.pm, ln500] Error happened on monitoring servers.
Fri Jul 28 10:38:24 2017 - [info] Got exit code 1 (Not master dead).

MySQL Replication Health is NOT OK!

问题原因:
是因为我在这个环境做了一次故障切换,mha 自动把/etc/masterha/app1.cnf 文件里的
[server1]
hostname=db1
port=3306
配置段给删除了。

问题解决:
在/etc/masterha/app1.cnf 配置文件中添加如下内容后就 OK 了

[server1]
hostname=db1
port=3306  

参考文档

http://wubx.net/mha-parameters/
http://www.cnblogs.com/zhoujinyi/p/3808673.html
http://blog.itpub.net/26230597/viewspace-1570798/
http://www.cnblogs.com/gomysql/p/3675429.html
http://www.cnblogs.com/xuanzhi201111/p/4231412.html

posted @ 2018-12-20 17:31  workdsz  阅读(140)  评论(0)    收藏  举报