关键词:MHA,mysql mha
【1】需求
采用mysql技术,实现MHA高可用主从环境,预计未来数据量几百G
MHA概念参考:MYSQL高可用技术概述
【2】环境技术架构
【2.1】MHA简介
该软件由两部分组成:
- MHA Manager(管理节点)
- MHA Node(数据节点)
MHA Manager可以单独部署在一台独立的机器上管理多个master-slave集群,也可以部署在一台slave节点上。
MHA Node运行在每台MySQL服务器上,MHA Manager会定时探测集群中的master节点,当master出现故障时,它可以自动将最新数据的slave提升为新的master,然后将所有其他的slave重新指向新的master。
整个故障转移过程对应用程序完全透明。
可以将MHA工作原理总结为如下
- 从宕机崩溃的master保存二进制日志事件(binlog events)
- 识别含有最新更新的slave
- 应用差异的中继日志(relay log)到其他的slave;
- 应用从master保存的二进制日志事件(binlog events);
- 提升一个slave为新的master;
- 使其他的slave连接新的master进行复制;
【2.2】MHA工具包
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线程) |
【2.3】基本操作环境与架构
操作系统:5台 centos7.5
数据库版本:mysql5.7.24
MHA 软件 :MHA 0.58
数据库架构:基于MHA 软件实现主从复制,采用GTID+无损同步复制技术,双主多从。
角色 | ip地址 | 主机名 | server_id | 类型 |
Monitor host | 192.168.1.201 | db1 | 监控复制组 | |
master | 192.168.1.202 | db2 | 2023306 | 写入 |
slave1 | 192.168.1.203 | db3 | 2033306 | 读(备用master) |
slave2 | 192.168.1.204 | db4 | 2043306 | 读 |
slave3 | 192.168.1.205 | db5 | 2053306 | 读 |
【3】实践环境准备(搭建GTID+半同步的1主3从)
【3.0】注意事项
(1)不要将read_only=1写进从库的配置文件,因为主库宕机时,从库要提升为主库接受写请求 mysql -e"set global read_only=1"
(2)主从节点复制的过滤规则要相同,即binlog_do_db 与 binlog_ignore_db 参数主从配置需要相同
(3)从节点需要修改配置参数 relay_log_purge=0 ,即关闭中继日志的清除
(4)serverid不能一样
#采用命令方式将从库设为只读,不要将该参数写进配置文件中 mysql -e"set global read_only=1" #关闭中继日志的清除 mysql -e"set global relay_log_purge=0"
【3.1】host
echo "127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4">> /etc/hosts echo "::1 localhost localhost.localdomain localhost6 localhost6.localdomain6" >>/etc/hosts echo "192.168.1.201 db1" >>/etc/hosts echo "192.168.1.202 db2" >>/etc/hosts echo "192.168.1.203 db3" >>/etc/hosts echo "192.168.1.204 db4" >>/etc/hosts echo "192.168.1.205 db5" >>/etc/hosts
【3.2】my.cnf
[client] port = 3306 socket = /mysql/data/3306/mysql.sock default-character-set=utf8 [mysql] disable-auto-rehash #允许通过TAB键提示 default-character-set = utf8 connect-timeout = 10 [mysqld] server-id = 3306 port = 3306 user = mysql socket = /mysql/data/3306/mysql.sock pid-file = /mysql/data/3306/mysql.pid basedir = /mysql/app/mysql/ datadir = /mysql/data/3306/data #bind_address = 10.10.10.11 autocommit = 0 character-set-server=utf8 explicit_defaults_for_timestamp=true lower_case_table_names=1 back_log=103 max_connections=10000 max_connect_errors=100000 table_open_cache=512 external-locking=FALSE max_allowed_packet=32M sort_buffer_size=2M join_buffer_size=2M thread_cache_size=51 query_cache_size=32M #query_cache_limit=4M transaction_isolation=READ-COMMITTED tmp_table_size=96M max_heap_table_size=96M ###***logs long_query_time = 10 slow_query_log = 1 slow_query_log_file=/mysql/log/3306/slow.log log-error_verbosity=3 log-error = /mysql/log/3306/mysql.err log_output = FILE #参数log_output指定了慢查询输出的格式,默认为FILE,你可以将它设为TABLE,然后就可以查询mysql架构下的slow_log表了 #log-queries-not-using-indexes #log-slow-slave-statements #general_log = 0 #general_log_file = /mysql/log/3306/mysql.log #max_binlog_size = 1G #max_relay_log_size = 1G #replication_new log_bin=/mysql/log/3306/mysql-bin #开启binlog log_bin_index=/mysql/log/3306/mysql-bin.index binlog_format=row binlog_rows_query_log_events=on max_binlog_size=2048 bind-address=0.0.0.0 server_id=2013306 #从库务必记得修改 expire_logs_days=7 #超过7天的binlog清理 innodb_support_xa=1 binlog_cache_size=1M log_bin_trust_function_creators=1 #同步存储过程、函数、触发器 innodb_flush_log_at_trx_commit=1 sync_binlog=1 transaction-isolation=read-committed #slave parameter 如果是从库,务必放开 #relay_log=/mysql/log/3306/relaylog/mysql-relay.log #relay_log_purge=0 #read_only=1 #slave-parallel-type=LOGICAL_CLOCK #slave-parallel-workers=4 #master_info_repository=table #master_info 会记录到 mysql.slave_master_info #relay_log_info_repository=table #relay_log 会记录到,mysql.slave_relay_log_info #relay_log_recovery=1 #slave_skip_errors=ddl_exist_errors #slave_preserve_commit_order=1 #5.7的增强半同步 #如果是5.7,参数前面加上loose_,如下列,如果是5.6 则直接使用 rpl_semi_sync_master_enabled=1 之类的就好了。 #我这里是5.7就直接做增强半同步了(loseless Semisynchronous ) plugin_dir=/mysql/app/mysql/lib/plugin/ plugin_load=rpl_semi_sync_master=semisync_master.so;rpl_semi_sync_slave=semisync_slave.so loose_rpl_semi_sync_master_enabled=1 #MySQL开启主的半同步复制(rpl_semi_sync_master_enabled) loose_rpl_semi_sync_slave_enabled=1 #MySQL5.6开启从的半同步复制 loose_rpl_semi_sync_master_timeout=5000 #超时5秒,切回异步 rpl_semi_sync_master_wait_for_slave_count=1 #至少收到1个slave发会的ack rpl_semi_sync_master_wait_point=AFTER_SYNC #MySQL 5.7的方法,AFTER_SYNC(default value,增强半同步) & AFTER_COMMIT(传统半同步) #GTID mode gtid_mode=on enforce_gtid_consistency=1 log-slave-updates=1 binlog_gtid_simple_recovery=1
【3.3】准备测试数据
(主库上跑,即202那台机器)
-- 【3.3.1】创建复制用户 create user 'rpl'@'192.168.1.%' identified by '123456'; grant replication slave on *.* to 'rpl'@'192.168.1.%'; flush privileges; select user,host from mysql.user; -- 【3.3.2】构造测试数据 -- 构造test库和test库下的test1,test2,test3表。test4表用于模拟业务一直在运行 create database test; use test; create table test1(id int); insert into test1 values(1); create table test2(id int); insert into test2 values(2); create table test3(id int); insert into test3 values(3); commit; create table test4(id int); insert into test4 values(4); commit; -- 构造存储过程sp_test4来循环插入test4表,模拟业务运行 use test; drop procedure if exists sp_test4; delimiter $$ create procedure sp_test4() begin declare n int; set n=11; while(n<=20) do insert into test.test4 values(n); commit; set n=n+1; end while; end $$ delimiter ; -- 构造事件,来调度sp_test4过程 use test; set global event_scheduler=1; delimiter $$ create event if not exists event_test4 on schedule every 5 second on completion preserve enable do begin call sp_test4(); end $$ delimiter ; -- 为了防止测试数据量累计导致卡顿,我这里5小时做一次truncate delimiter $$ create event if not exists event_truncate_test4 on schedule every 5 hour on completion preserve enable do begin truncate table test.test4; end $$ delimiter ;
【3.4】基于xtrabackup的备份恢复初始化
-- 在202 主库上备份 innobackupex --defaults-file=/etc/my.cnf -uroot -p123456 --no-timestamp /mysql/backup/full.bak -- 传输到从库 scp -r /mysql/backup/full.bak root@192.168.1.203:/mysql/backup/ scp -r /mysql/backup/full.bak root@192.168.1.204:/mysql/backup/ scp -r /mysql/backup/full.bak root@192.168.1.205:/mysql/backup/ #在从服务器还原 innobackupex --apply-log /mysql/backup/full.bak service mysql stop cd /mysql/data/3306 mkdir data innobackupex --defaults-file=/etc/my.cnf --copy-back /mysql/backup/full.bak chown -R mysql:mysql /mysql chmod -R 755 /mysql
【3.5】主从配置
#在从服务器上执行 stop slave; reset slave; reset master; SET @MYSQLDUMP_TEMP_LOG_BIN = @@SESSION.SQL_LOG_BIN; SET @@SESSION.SQL_LOG_BIN= 0; set global gtid_purged='2c8b1813-e26f-11e9-adce-000c29658c19:1-417'; -- 这个从/mysql/backup/full.bak/xtrabackup_info 中获得 SET @@SESSION.SQL_LOG_BIN = @MYSQLDUMP_TEMP_LOG_BIN; show master status; change master to master_host='192.168.1.202', master_user='rpl', master_password='123456', master_port=3306, master_auto_position=1; start slave;
【3.6】核验主从同步
(1)show slave statusG
(2)show processlist;
(3)select count(1) from test.test4;
【4】MHA
安装参考:https://www.cnblogs.com/winstom/p/11022014.html
概念:高可用架构方案 中的【3】MHA
【4.1】软件下载与依赖参考
mha的安装包下载地址: https://github.com/yoshinorim/mha4mysql-manager/releases https://github.com/yoshinorim/mha4mysql-node/releases
【4.2】准备perl环境,安装 mha node 与 manager节点
需要在5台机器上都安装mha node,在monitor节点上安装 manager节点
#【4.2.1】安装依赖包: 监控节点manager(201)必须配置好网络yum源与epel源。其他的节点只需要本地yum源即可。 参考:yum源配置
#monitor节点 一定要先安装epel源
yum install epel-release
yum install -y perl-ExtUtils-CBuilder perl-ExtUtils-MakeMaker perl-CPAN perl-DBD-MySQL perl-Config-Tiny perl-Log-Dispatch perl-Parallel-ForkManager perl-Time-HiRes
#【4.2.2】开始在所有节点安装node节点(这个可以只有操作系统的yum源)
cd mha4mysql-node-0.58 perl Makefile.PL make && make install
#【4.2.2】/usr/local/bin/ 目录下会出现4个工具(node)
-r-xr-xr-x. 1 root root 17639 Oct 20 23:21 apply_diff_relay_logs -r-xr-xr-x. 1 root root 4807 Oct 20 23:21 filter_mysqlbinlog -r-xr-xr-x. 1 root root 8337 Oct 20 23:21 purge_relay_logs -r-xr-xr-x. 1 root root 7525 Oct 20 23:21 save_binary_logs Node脚本说明:(这些工具通常由MHA Manager的脚本触发,无需人为操作) save_binary_logs //保存和复制master的二进制日志 apply_diff_relay_logs //识别差异的中继日志事件并将其差异的事件应用于其他的slave filter_mysqlbinlog //去除不必要的ROLLBACK事件(MHA已不再使用这个工具) purge_relay_logs //清除中继日志(不会阻塞SQL线程)
#【4.2.3】开始在201安装manager(【注意查看【4.2】的yum依赖包安装,否则该步骤会部分操作失败】)
cd mha4mysql-manager-0.58 perl Makefile.PL make && make install
#【4.2.4】把上面生成的工具命令所在目录添加到环境变量
echo "export PATH=${PATH}:/usr/local/bin">>/etc/profile source /etc/profile
#201机器,安装完node和manager节点之后,/usr/local/bin 目录如下
【4.3】秘钥互信及复制mysql共享库到系统库
(要MHA故障自动转移必须要这个)
(1)秘钥互信
配置所有机器相互之间root用户秘钥互信 在所有机器上执行: 生成密钥对 ssh-keygen -t dsa -f ~/.ssh/id_rsa -P "" #推送公钥 ssh-copy-id -i /root/.ssh/id_rsa.pub root@192.168.1.201 ssh-copy-id -i /root/.ssh/id_rsa.pub root@192.168.1.202 ssh-copy-id -i /root/.ssh/id_rsa.pub root@192.168.1.203 ssh-copy-id -i /root/.ssh/id_rsa.pub root@192.168.1.204 ssh-copy-id -i /root/.ssh/id_rsa.pub root@192.168.1.205 #此时所有的机器之间以完成互信,无需密码等即可ssh登陆
(2)复制mysql客户端共享库到Linux系统库下
cp /mysql/app/mysql/lib/libmysqlclient.so.20 /lib64/
【4.4】MHA配置及relay_log清理脚本(4.4.3)
在monitor,即201机器上配置
#【4.4.1】复制监控节点配置文件
mkdir -p /etc/masterha
cp /soft/mha4mysql-manager-0.58/samples/conf/app1.cnf /etc/masterha/
#默认引用配置文件位置为:/etc/masterha_default.cnf
#【4.4.2】修改监控节点配置文件
mkdir -p /var/log/masterha/app1
touch /var/log/masterha/app1/manager.log
chmod -R 777 /var/log/masterha/
vim /etc/masterha/app1.cnf
[server default] manager_workdir=/var/log/masterha/app1 #管理节点工作目录 manager_log=/var/log/masterha/app1/manager.log #管理节点日志 master_binlog_dir=/mysql/log/3306 #数据库Binlog所在日志 master_ip_failover_script=/usr/local/bin/master_ip_failover #自动故障转移的脚本 master_ip_online_change_script=/usr/local/bin/master_ip_online_change #手动在线切换主节点的脚本 password=123456 #mysql数据库监控密码 user=root #mysql数据库监控用户 ping_interval=1 #发送ping间隔包的时间,默认3S,这里1是1S,如果3次没有响应则故障切换 remote_workdir=/tmp #mysql切换的时候binlog保存路径 repl_password=123456 #mysql复制的mysql用户密码 repl_user=rpl #mysql复制的Mysql用户账户 report_script=/usr/local/bin/send_report #发生切换之后的报警报告脚本 secondary_check_script=/usr/local/bin/masterha_secondary_check -s db2 -s db3 -s db4 -s db5 #检测哪个库是最新的,以便故障转移时为新主库 shutdown_script="" #关闭的脚本 ssh_user=root #SSH通信的用户 [server1] hostname=192.168.1.202 port=3306 [server2] hostname=192.168.1.203 port=3306 candidate_master=1 #设置为备用主库,当多个SERVER都有设置它,用最新的做主库 check_repl_delay=0 #切换的时候忽略复制延迟,
#默认情况下如果一个slave落后master 100M的relay logs的话,MHA将不会选择该slave作为一个新的master,因为对于这个slave的恢复需要花费很长时间,通过设置check_repl_delay=0,MHA触发切换在选择一个新的master的时候将会忽略复制延时,这个参数对于设置了candidate_master=1的主机非常有用,因为这个候选主在切换的过程中一定是新的master
[server3] port=3306 hostname=192.168.1.204 [server4] hostname=192.168.1.205 port=3306 no_master=1 #不会成为主库
直接可用配置参数代码:
[server default] manager_workdir=/var/log/masterha/app1 manager_log=/var/log/masterha/app1/manager.log master_binlog_dir=/mysql/log/3306 master_ip_failover_script=/usr/local/bin/master_ip_failover master_ip_online_change_script=/usr/local/bin/master_ip_online_change password=123456 user=root ping_interval=1 remote_workdir=/tmp repl_password=123456 repl_user=rpl report_script=/usr/local/bin/send_report secondary_check_script=/usr/local/bin/masterha_secondary_check -s db2 -s db3 -s db4 -s db5 shutdown_script="" ssh_user=root [server1] hostname=192.168.1.202 port=3306 candidate_master=1 [server2] hostname=192.168.1.203 port=3306 candidate_master=1 check_repl_delay=0 [server3] port=3306 hostname=192.168.1.204 [server4] hostname=192.168.1.205 port=3306 no_master=1
#配置文件说明
MHA主要配置文件说明 manager_workdir=/var/log/masterha/app1.log:设置manager的工作目录 manager_log=/var/log/masterha/app1/manager.log:设置manager的日志文件 master_binlog_dir=/data/mysql:设置master 保存binlog的位置,以便MHA可以找到master的日志 master_ip_failover_script= /usr/local/bin/master_ip_failover:设置自动failover时候的切换脚本 master_ip_online_change_script= /usr/local/bin/master_ip_online_change:设置手动切换时候的切换脚本 user=root:设置监控mysql的用户 password=dayi123:设置监控mysql的用户,需要授权能够在manager节点远程登录 ping_interval=1:设置监控主库,发送ping包的时间间隔,默认是3秒,尝试三次没有回应的时候自动进行railover remote_workdir=/tmp:设置远端mysql在发生切换时binlog的保存位置 repl_user=repl :设置mysql中用于复制的用户密码 repl_password=replication:设置mysql中用于复制的用户 report_script=/usr/local/send_report:设置发生切换后发送的报警的脚本 shutdown_script="":设置故障发生后关闭故障主机脚本(该脚本的主要作用是关闭主机放在发生脑裂,这里没有使用) ssh_user=root //设置ssh的登录用户名 candidate_master=1:在节点下设置,设置当前节点为候选的master slave check_repl_delay=0 :在节点配置下设置,默认情况下如果一个slave落后master 100M的relay logs的话,MHA将不会选择该slave作为一个新的master;这个选项对于对于设置了candidate_master=1的主机非常有用
secondary_check_script=/usr/local/bin/masterha_secondary_check -s db2 -s db3 -s db4 -s db5 #检测哪个库是最新的,以便故障转移的时候确认信主库
【4.4.3】设置定期清理relay脚本(node节点服务器,即202~205)
#在202机器上执行
弄之前,先给主库,再加个IP网卡 :/sbin/ifconfig ens34:1 192.168.1.210/24,然后作为VIP使用
mysql -uroot -p123456 -e"set global relay_log_purge=0;" #所有202~205都执行 vim /usr/local/bin/purge_relay_log.sh
#新建/修改如下
#!/bin/bash user=root #mysql的账户密码 password=123456 port=3306 socket=/mysql/data/3306/mysql.sock log_dir='/var/log/masterha/log' work_dir='/mysql/log/3306/relaylog' purge='/usr/local/bin/purge_relay_logs' if [ ! -d $log_dir ] then mkdir -p $log_dir fi $purge --user=${user} --password=${password} -S ${socket} --host=localhost --disable_relay_log_purge --port=${port}
#复制到202~205上的每一台机器上去
scp root@192.168.1.202:/usr/local/bin/purge_relay_log.sh /usr/local/bin
#添加到crontab,每4小时执行一次
chmod +x /usr/local/bin/purge_relay_log.sh
crontab -e
0 4 * * * /bin/bash /usr/local/bin/purge_relay_log.sh
#4个小时清理一次
#手工执行以下查看该脚本是否报错
【4.5】VIP配置
这些官方有脚本,但是这是比较新的脚本,我们还是用我们自己的老脚本
(1)手工用脚本实现
【4.5.1】自动故障转移VIP脚本(master_ip_failover)
【4.5.2】手动故障转移VIP配置脚本(master_ip_online_change)
(2)keepalived
手动配置VIP
#在主库上,手动添加vip地址
nohup ping -c 2 192.168.1.210 if [ $? != 0 ];then /sbin/ifconfig ens34:1 192.168.1.210/24 fi #在主、备主上,根据情况脚本设置开机自启 cat << eof >>/etc/rc.d/rc.local nohup ping -c 2 192.168.1.210 if [ $? != 0 ];then /sbin/ifconfig ens34:1 192.168.1.210/24 fi eof
如何删掉这个手动添加的VIP地址?
ifconfig ens34:1 down
#ip addr del 192.168.1.210 dev ens34
#开启
ifconfig ens34:1 up
ifup ifcfg-ens34:1
也可以通过构建一个新的网口网卡:http://www.lwops.cn/forum.php?mod=viewthread&tid=311&fromuid=1&tdsourcetag=s_pctim_aiomsg
大致步骤:
(1)在主、备主上,复制 cp /etc/sysconfig/network-script/ifcfg-ens34 /etc/sysconfig/network-script/ifcfg-ens34:1 ,配置好IP地址为VIP
(2)先在主库上启动这个网卡 ifup ifcfg-ens34:1 ,(注意不同主和备注 两个同事开启,会IP地址冲突的)
(3)在两台机器上都添加下面脚本,并添加到开机启动,以防有机器宕机重启后 脚本不执行了。 避免配置问题不要使用 systemctl restart network 以防有问题直接远程都连不上,单独开启这个网卡用这个 ifup ifcfg-ens34:1
#在主、备主上,根据情况脚本设置开机自启 cat << eof >>/etc/rc.d/rc.local nohup ping -c 2 192.168.1.210 if [ $? != 0 ];then /sbin/ifconfig ens34:1 192.168.1.210/24 fi eof
手动故障转移等脚本配置
【4.5.1】MHA自动故障转移VIP脚本(master_ip_failover)
弄之前,先给主库,再加个IP网卡 :/sbin/ifconfig ens34:1 192.168.1.210/24,然后作为VIP使用
#修改 master_ip_failover脚本,使用脚本管理VIP
vim /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.1.210/24'; my $key = '1'; my $ssh_start_vip = "/sbin/ifconfig ens34:$key $vip"; my $ssh_stop_vip = "/sbin/ifconfig ens34:$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 " IN SCRIPT TEST====$ssh_stop_vip==$ssh_start_vip=== "; if ( $command eq "stop" || $command eq "stopssh" ) { my $exit_code = 1; eval { print "Disabling the VIP on old master: $orig_master_host "; &stop_vip(); $exit_code = 0; }; if ($@) { warn "Got Error: $@ "; 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 "; &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 "; exit 0; } else { &usage(); exit 1; } } sub start_vip() { `ssh $ssh_user@$new_master_host " $ssh_start_vip "`; } sub stop_vip() { return 0 unless ($ssh_user); `ssh $ssh_user@$orig_master_host " $ssh_stop_vip "`; } sub usage { print "Usage: master_ip_failover --command=start|stop|stopssh|status --orig_master_host=host --orig_master_ip=ip --orig_master_port=port --new_master_host=host --new_master_ip=ip --new_master_port=port "; }
【4.5.2】手动故障转移VIP配置脚本(master_ip_online_change)
弄之前,先给主库,再加个IP网卡 :/sbin/ifconfig ens34:1 192.168.1.210/24,然后作为VIP使用
vim /usr/local/bin/master_ip_online_change
(1)官方版
#!/usr/bin/env perl use strict; use warnings FATAL => 'all'; use Getopt::Long; use MHA::DBHelper; use MHA::NodeUtil; use Time::HiRes qw( sleep gettimeofday tv_interval ); use Data::Dumper; my $_tstart; my $_running_interval = 0.1; my ( $command, $orig_master_host, $orig_master_ip, $orig_master_port, $orig_master_user, $new_master_host, $new_master_ip, $new_master_port, $new_master_user, ); my $vip = '192.168.1.210/24'; # Virtual IP my $key = "1"; my $ssh_start_vip = "/sbin/ifconfig ens34:$key $vip"; my $ssh_stop_vip = "/sbin/ifconfig ens34:$key down"; my $ssh_user = "root"; my $new_master_password='123456'; my $orig_master_password='123456'; GetOptions( 'command=s' => $command, #'ssh_user=s' => $ssh_user, 'orig_master_host=s' => $orig_master_host, 'orig_master_ip=s' => $orig_master_ip, 'orig_master_port=i' => $orig_master_port, 'orig_master_user=s' => $orig_master_user, #'orig_master_password=s' => $orig_master_password, 'new_master_host=s' => $new_master_host, 'new_master_ip=s' => $new_master_ip, 'new_master_port=i' => $new_master_port, 'new_master_user=s' => $new_master_user, #'new_master_password=s' => $new_master_password, ); exit &main(); sub current_time_us { my ( $sec, $microsec ) = gettimeofday(); my $curdate = localtime($sec); return $curdate . " " . sprintf( "%06d", $microsec ); } sub sleep_until { my $elapsed = tv_interval($_tstart); if ( $_running_interval > $elapsed ) { sleep( $_running_interval - $elapsed ); } } sub get_threads_util { my $dbh = shift; my $my_connection_id = shift; my $running_time_threshold = shift; my $type = shift; $running_time_threshold = 0 unless ($running_time_threshold); $type = 0 unless ($type); my @threads; my $sth = $dbh->prepare("SHOW PROCESSLIST"); $sth->execute(); while ( my $ref = $sth->fetchrow_hashref() ) { my $id = $ref->{Id}; my $user = $ref->{User}; my $host = $ref->{Host}; my $command = $ref->{Command}; my $state = $ref->{State}; my $query_time = $ref->{Time}; my $info = $ref->{Info}; $info =~ s/^s*(.*?)s*$/$1/ if defined($info); next if ( $my_connection_id == $id ); next if ( defined($query_time) && $query_time < $running_time_threshold ); next if ( defined($command) && $command eq "Binlog Dump" ); next if ( defined($user) && $user eq "system user" ); next if ( defined($command) && $command eq "Sleep" && defined($query_time) && $query_time >= 1 ); if ( $type >= 1 ) { next if ( defined($command) && $command eq "Sleep" ); next if ( defined($command) && $command eq "Connect" ); } if ( $type >= 2 ) { next if ( defined($info) && $info =~ m/^select/i ); next if ( defined($info) && $info =~ m/^show/i ); } push @threads, $ref; } return @threads; } sub main { if ( $command eq "stop" ) { ## Gracefully killing connections on the current master # 1. Set read_only= 1 on the new master # 2. DROP USER so that no app user can establish new connections # 3. Set read_only= 1 on the current master # 4. Kill current queries # * Any database access failure will result in script die. my $exit_code = 1; eval { ## Setting read_only=1 on the new master (to avoid accident) my $new_master_handler = new MHA::DBHelper(); # args: hostname, port, user, password, raise_error(die_on_error)_or_not $new_master_handler->connect( $new_master_ip, $new_master_port, $new_master_user, $new_master_password, 1 ); print current_time_us() . " Set read_only on the new master.. "; $new_master_handler->enable_read_only(); if ( $new_master_handler->is_read_only() ) { print "ok. "; } else { die "Failed! "; } $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.. "; #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) ", current_time_us(), $#threads + 1, $time_until_read_only * 100; if ( $#threads < 5 ) { print Data::Dumper->new( [$_] )->Indent(0)->Terse(1)->Dump . " " 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. "; } else { die "Failed! "; } ## 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) ", current_time_us(), $#threads + 1, $time_until_kill_threads * 100; if ( $#threads < 5 ) { print Data::Dumper->new( [$_] )->Indent(0)->Terse(1)->Dump . " " foreach (@threads); } } sleep_until(); $_tstart = [gettimeofday]; $time_until_kill_threads--; @threads = get_threads_util( $orig_master_handler->{dbh}, $orig_master_handler->{connection_id} ); } print "Disabling the VIP on old master: $orig_master_host "; &stop_vip(); ## Terminating all threads print current_time_us() . " Killing all application threads.. "; $orig_master_handler->kill_threads(@threads) if ( $#threads >= 0 ); print current_time_us() . " done. "; #$orig_master_handler->enable_log_bin_local(); $orig_master_handler->disconnect(); ## After finishing the script, MHA executes FLUSH TABLES WITH READ LOCK $exit_code = 0; }; if ($@) { warn "Got Error: $@ "; 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. "; $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.. "; #FIXME_xxx_create_app_user($new_master_handler); #$new_master_handler->enable_log_bin_local(); $new_master_handler->disconnect(); ## Update master ip on the catalog database, etc print "Enabling the VIP - $vip on the new master - $new_master_host "; &start_vip(); $exit_code = 0; }; if ($@) { warn "Got Error: $@ "; exit $exit_code; } exit $exit_code; } elsif ( $command eq "status" ) { # do nothing exit 0; } else { &usage(); exit 1; } } # A simple system call that enable the VIP on the new master sub start_vip() { `ssh $ssh_user@$new_master_host " $ssh_start_vip "`; } # A simple system call that disable the VIP on the old_master sub stop_vip() { `ssh $ssh_user@$orig_master_host " $ssh_stop_vip "`; } sub usage { print "Usage: master_ip_online_change --command=start|stop|status --orig_master_host=host --orig_master_ip=ip --orig_master_port=port --new_master_host=host --new_master_ip=ip --new_master_port=port "; die; }
(2)简单版
#!/bin/bash source /root/.bash_profile vip=`echo '192.168.1.210/24'` #设置VIP key=`echo '1'` command=`echo "$1" | awk -F = '{print $2}'` orig_master_host=`echo "$2" | awk -F = '{print $2}'` new_master_host=`echo "$7" | awk -F = '{print $2}'` orig_master_ssh_user=`echo "${12}" | awk -F = '{print $2}'` new_master_ssh_user=`echo "${13}" | awk -F = '{print $2}'` #要求服务的网卡识别名一样 stop_vip=`echo "ssh root@$orig_master_host /usr/sbin/ifconfig bond0:$key down"` start_vip=`echo "ssh root@$new_master_host /usr/sbin/ifconfig bond0:$key $vip"` if [ $command = 'stop' ] then echo -e " **************************** " echo -e "Disabled thi VIP - $vip on old master: $orig_master_host " $stop_vip if [ $? -eq 0 ] then echo "Disabled the VIP successfully" else echo "Disabled the VIP failed" fi echo -e "*************************** " fi if [ $command = 'start' -o $command = 'status' ] then echo -e " ************************* " echo -e "Enabling the VIP - $vip on new master: $new_master_host " $start_vip if [ $? -eq 0 ] then echo "Enabled the VIP successfully" else echo "Enabled the VIP failed" fi echo -e "*************************** " fi
【4.5.3】授权
chmod +x /usr/local/bin/master_ip_failover
chmod +x /usr/local/bin/master_ip_online_change
【4.5.4】keepalive(待写)
【4.6】配置检查(在201上,即manager节点上)
【4.6.1】检查SSH配置
#检查mha manager 到所有的 MHA Node 的SSH链接状态
masterha_check_ssh --conf=/etc/masterha/app1.cnf
【4.6.2】检查整个复制环境状态
#通过 masterha_check_repl 脚本查看整个集群的状态
masterha_check_repl --conf=/etc/masterha/app1.cnf
【4.6.3】检查 MHA Manager 的状态
masterha_check_status --conf=/etc/masterha/app1.cnf
【4.6.4】手动启动MHA监控
mkdir -p /var/log/masterha/app1
chmod -R 777 /var/log/masterha/app1
#启动MHA监控
nohup masterha_manager --conf=/etc/masterha/app1.cnf --ignore_laster_failover &
#--remove_dead_master_conf &
(1)--remove_dead_master_conf #当发生故障切换后,老的主库配置会从配置文件中移除掉(比如发生故障切换了,202主库切换到了203,那么在app1.cnf配置文件中,[server1]描述的ip为192.168.1.202内容会被清除)
(2)--manger_log #管理节点的日志位置
(3)--ignore_last_failover
#在默认情况下,如果mha检查到主库连续发现宕机,且两次宕机间隔不超过8小时,则不会发生继续切换。(会生成一个文件,判断文件存在就不允许继续故障转移切换)
#加了这个参数的话,就是来避免上述情况的,以便可以无限制的故障切换。
# 停止MHA监控
masterha_stop --conf=/etc/masterha/app1.cnf
【4.7】自动故障转移切换测试
切换之后,注意修改my.cnf,比如read_only等等
【4.7.1】直接关闭主库202机器的mysql
systemctl stop mysql #service mysql stop
【4.7.2】查看日志了解故障转移切换原理
#查看日志 /var/log/masterha/app1/manager.log
#这里只筛选了一下步骤 ** Phase 1: Configuration Check Phase completed. * Phase 2: Dead Master Shutdown Phase.. * Phase 2: Dead Master Shutdown Phase completed. * Phase 3: Master Recovery Phase.. * Phase 3.1: Getting Latest Slaves Phase.. * Phase 3.3: Determining New Master Phase.. * Phase 3.3: New Master Recovery Phase.. * Phase 3: Master Recovery Phase completed. * Phase 4: Slaves Recovery Phase.. * Phase 4.1: Starting Slaves in parallel.. * Phase 5: New master cleanup phase..
由此可以看出,MHA故障转移详细步骤;
(1)配置文件检查阶段:通过检查MHA的配置文件,获取相关的MHA集群机器信息,故障转移脚本信息等等
(2)宕机主库关闭阶段:将VIP删除
(3)复制宕机master库的binlog与最新slave库的差异relay log,保存到monitor节点下。
(4)推选识别含有最新数据的slave库,提升为master库。
(5)新master库应用(3)中保存下来的二进制日志
(6)将其他的slave库连接到新的master库,进行复制
【4.7.3】核验数据
(1)打开 schedure_event,查看test.test4
(2)查看show slave status
(3)建立一个库、表、数据,查看是否同步
(4)如何查看vip是否切换成功?
mysql -uroot -p123456 -h192.168.1.210 #通过VIP登录
show variables like 'hostname';
最终发现,VIP还是切换过来了。
【4.7.4】MHA 官方的 BUG
对于 201 机器 monitor ,发生故障转移后,manager进程直接死掉了,即MHA监控脚本已经自动停止。
这个时候如果再次发生故障,就无法自动故障转移切换了。详情如下图:
解决办法:
官方的回复是,不想让这个进程死掉,就放到后台运行,但我们是后台启动的,并没有效果;
我们可以写一个脚本,监控进程是否存在,如果进程不存在则启动它。感觉就如同mysqld_safe一样,守护着mysql,mysql挂了就自动拉起来。
vim /usr/local/bin/manager_status_check
#!/bin/bash while true do mha_check=`ps -ef|grep masterha_manager|grep -v grep|wc -l` if [ ${mha_check} -eq 0 ];then nohup masterha_manager --conf=/etc/masterha/app1.cnf --ignore_laster_failover & #--remove_dead_master_conf & else echo "MHA manager start" fi sleep 5 done
#授权及加入开机自启
#chmod u+x /usr/local/bin/manager_status_check
#echo "nohup /usr/local/bin/manager_status_check">>/etc/rc.d/rc.local
【4.7.5】原本宕机的主节点重新加入回
(1)如果掉线时间长,宕机时间段内数据量大,建议操作如下(或者直接进行备份还原操作)
增强半同步从库宕机如何重新连入主库? 1. 此2个参数rpl_semi_sync_master_enabled 和rpl_semi_sync_slave_enabled 不要直接写入到my.cnf配置文件开启。 2.在slave库上先 stop slave io_thread ;set global rpl_semi_sync_slave_enabled=0 关闭此参数。 然后start slave io_thread 或者start slave 开启异步复制,让slave库追赶上master库。 3.然后在slave库 set global rpl_semi_sync_slave_enabled=1 ;stop slave io_thread;start slave io_thread;
(2)否则直接运行下列脚本重新制定主从即可
现在主库是203啦。
change master to master_host='192.168.1.203', master_user='rpl', master_password='123456', master_port=3306, master_auto_position=1;
注意,如果monitor 启动的时候加了
--remove_dead_master_conf
那么配置文件会把之前宕机的机器信息(也就是202)清除掉,所以我们需要修改一下配置文件,把202加回来
左边修改前,右边修改后。
【4.8】手工切换主库(宕机切换与在线切换,masterha_master_switch 与 master_ip_online_change)
切换之后,注意修改my.cnf
做这块操作之前,必须要先关掉 masterha_manger.
ps -ef|grep manager*
【4.8.1】 masterha_master_switch
#在线切换(当前主库是203,想切换到202为主库)masterha_master_switch --conf=/etc/masterha/app1.cnf --master_state=alive --new_master_host=192.168.1.202 --new_master_port=3306 --orig_master_is_new_slave --running_updates_limit=10000
#其实会调用到 master_ip_online_change 脚本
#master_ip_online_change 具体文件配置参考【4.5】VIP配置
【4.8.1】核验
参考:【4.7.3】核验数据
记得把监控启动起来
【4.8.2】MHA在线切换的考虑与基本原理
在许多情况下, 需要将现有的主服务器迁移到另外一台服务器上。 比如主服务器硬件故障,RAID 控制卡需要重建,将主服务器移到性能更好的服务器上等等。维护主服务器引起性能下降, 导致停机时间至少无法写入数据。 另外, 阻塞或杀掉当前运行的会话会导致主主之间数据不一致的问题发生。 MHA 提供快速切换和优雅的阻塞写入,这个切换过程只需要 0.5-2s 的时间,这段时间内数据是无法写入的。在很多情况下,0.5-2s 的阻塞写入是可以接受的。因此切换主服务器不需要计划分配维护时间窗口。
MHA在线切换的大概过程:
- 检测复制设置和确定当前主服务器
- 确定新的主服务器
- 阻塞写入到当前主服务器
- 等待所有从服务器赶上复制
- 授予写入到新的主服务器
- 重新设置从服务器
注意,在线切换的时候应用架构需要考虑以下两个问题:
- 自动识别master和slave的问题(master的机器可能会切换),如果采用了vip的方式,基本可以解决这个问题。
- 负载均衡的问题(可以定义大概的读写比例,每台机器可承担的负载比例,当有机器离开集群时,需要考虑这个问题)
为了保证数据完全一致性,在最快的时间内完成切换,MHA的在线切换必须满足以下条件才会切换成功,否则会切换失败。
- 所有slave的IO线程都在运行
- 所有slave的SQL线程都在运行
- 所有的show slave status的输出中Seconds_Behind_Master参数小于或者等于running_updates_limit秒,如果在切换过程中不指定running_updates_limit,那么默认情况下running_updates_limit为1秒。
- 在master端,通过show processlist输出,没有一个更新花费的时间大于running_updates_limit秒。
【5】增删节点
【5.1】增加节点
(1)新节点机器 安装agent节点环境,初始化mysql数据库
(2)新节点机器 异步复制跟上
(3)新节点机器 半同步复制跟上
(4)与manager 秘钥互信
(5)修改manager配置文件
(6)重启manager服务
(7)复制检查
【5.2】删除节点
(1)关闭删除节点复制
(2)停掉删除节点服务/清除slave 连接 master信息(reset slave)
(3)修改配置文件
(4)重启manager
(5)复制检查
附录:故障解决
(1)perl-Log-Dispatch no available packages
perl-Parallel-ForkManager no available packages
解决办法:
yum -y install epel-release ,安装了之后, 再重新
yum install -y perl-ExtUtils-CBuilder perl-ExtUtils-MakeMaker perl-CPAN perl-DBD-MySQL perl-Config-Tiny perl-Log-Dispatch perl-Parallel-ForkManager perl-Time-HiRes
(2)手动VIP如何在重启后正确的重新设置?
cat << eof >>/etc/rc.d/rc.local nohup ping -c 2 192.168.1.210 if [ $? != 0 ];then /sbin/ifconfig ens34:1 192.168.1.210/24 fi eof
#判断主库 select * from information_schema.processlist where state like 'Master%';
#判断从库 select * from information_schema.processlist where state like 'Slave%';
(3)官方BUG,故障转移之后,monitor服务器下的manager程序死掉了
详情见本文【4.7.4】
(4) masterha_check_repl(1130,1045) :Access denied for user 'root'@'mha1'
故障原因:
此账号没有权限登录到对应的机器上
处理方法:
为对应的用户授权即可
(5)masterha_check_repl(Can't exec "mysqlbinlog"):从当前环境变量中找不到binlog
故障原因:
从当前的环境变量中找不到 mysqlbinlog 命令
解决方法:
将 mysqlbinlog 的路径添加到 环境变量中
(6) masterha_check_repl(rep no exist or does not have REPLICATIONSLAVE privilege)
故障原因
缺少 REPLICATION SLAVE 权限
解决方法:
为同步账号添加 REPLICATION SLAVE 权限即可, 注意,是所有节点都添加, 保证主从切换后都可以正常使用。
(7)event_scheduler导致常连接问题
故障原因:
这个是由于部属的 mha 版本没有跟上 数据库的版本. 在检测长连接时, 由于系统新增加了event_scheduler 功能,且属于打开的状态,那么此用户会一直存在, mha 检测时将其列为长连接,所以出现上面错误
解决方法:
临时解决方法: 禁用 event_scheduler, set global event_scheduler = 0;
长久之计,按下面方式修改源码:
(8) mha 管理 vip, 节点之间的网卡名不一样,切换会失败
解决方法:
* 改网卡名
* 改切换脚本
2.6、 mha 管理 vip, ssh 默认端口非22
切换会失败
解决方法:
* 改默认端口
* 改切换脚本
注: 在线切换 和 故障切换脚本QQ群中提供 群号:748415432
(9) 使用 GTID 时切换的坑(gtid_mode=1; auto_position=0)
gtid_mode=1; auto_position=0 模式, 配置 binlog server 选项
虽然打开了 GTID, 但同步依旧使用的是log_file + position 模式同步数据, 切换时依旧自动转成 auto_position=1 模式, 转换后很有可能出来 1236 同步错误. 下面两段代码解释了为什么会依旧使用 auto_position=1 模式 .
2.8、 gtid_mode=1; auto_position=0模式,
配置 binlog server 选项, 同时配置了 use_gtid_auto_pos=0
看似解决了上面的问题, 但引入了一个最大的问题, 不补尝原主实例的差异数据了, 这就是说, 原主库任何情况下出现异常都属于机器挂的情况‘
2.9、 gtid_mode=1; auto_position=1模式,
没有配置 binlog server 选项, 依旧补不了日志。
2.7 ~ 2.9 解决方案:
开启 gitd 后, 最好的方案就是 基于 gtid 同步, 且使用 auto_position=1, 同时配置 binlog server 选项。
(10)如果新实例,则需要执行一个事务,才可以被识别为开启 了 GTID 模式
[server default] # 这边是 连接 MySQL 的账号与密码, 如果端口发生改变, 也要写上相应的端口, 默认为 330 port=3306 user=rootpassword=123456# 这边是连接机器的 ssh 用户,密码使用互信方式实现 ssh_user=root# 这边复制账号 repl_user=repl repl_password=123456
master_binlog_dir= /data/mysql/mysqldata3306/binlog master_ip_failover_script= /etc/mha/scripts/ master_ip_failover_new master_ip_online_change_script= /etc/mha/scripts/master_online_change_new manager_workdir=/etc/mha/app1manager_log=/etc/mha/log/mha/manager.log
[server1] hostname=192.168.1.20 candidate_master=1 master_binlog_dir= /data/mysql/mysqldata3306/binlog
[server2] hostname=192.168.1.2 1candidate_master=1 master_binlog_dir= /data/mysql/mysqldata3306/binlog
[server3] hostname=192.168.1.22 candidate_master=1 master_binlog_dir= /data/mysql/mysqldata3306/binlog
[server4] hostname=192.168.1.23 candidate_master=1 master_binlog_dir= /data/mysql/mysqldata3306/binlog
[binlog1] hostname=192.168.1.20 [binlog2] hostname=192.168.1.21 [binlog3] hostname=192.168.1.22 [binlog4] hostname=192.168.1.23
结尾
想要完美的避开上面的坑, 建议:
* 使用高版本的 MHA, 可以解决上面切换的坑.
* 如果打开了 GTID 模式,则使用 auto_position=1 同步模式,同时 MHA 的配置文件中 配置[binlog1] 选项, 地址写上原主库地址就好, 不需要真实配置一个 binlog server 服务器
本文分享自微信公众号 - 3306pai(pai3306)
参考文献
参考:一步一个坑搭建MHA http://www.ttlsa.com/mysql/step-one-by-one-deploy-mysql-mha-cluster/
参考:MHA官网 https://code.google.com/p/mysql-master-ha/
参考:比较详细的MHA部署搭建:https://blog.csdn.net/qq_35209838/article/details/86497864
软件下载:https://cbs.centos.org/koji/buildinfo?buildID=1261
安装参考:https://www.cnblogs.com/winstom/p/11022014.html
概念:高可用架构方案 中的【3】MHA
mysql故障应用参考:https://cloud.tencent.com/developer/article/1339797