写在前面的话
主从架构在一般情况下只能满足我们小公司业务并非一刻都不能中断服务。但是对于大型公司而言,对然数据丢失,数据库挂了,我们可以通过技术找回,修复。但是其中修复过程所消耗的时间是不被允许的。此时就需要引入高可用,以保证我们主库在宕机情况下有另外的数据库顶上去,以保证我们的服务 7 x 24 无间断。
数据库基本架构
在日常的小项目中,对于数据库的基本架构一般有以下选型:
1. 一主一从 / 一主多从
2. 多级主从
3. 双主
4. 循环复制
高级一点的高性能架构,也就是需要第三方服务帮助的架构:
1. 读写分离。常见的基于 MySQL Proxy 的有:Atlas / MySQL Router / ProxySQL / MaxScale 等
2. 分布式架构。常见的有:Cobar / TDDL / Mycat 等
最后就是高可用架构:
1. 单活 MMM,谷歌的 mysql-mmm。
2. 单活 MHA,日本人开发的 mysql-master-ha。
3. 多活 MGR,MySQL 5.7.17 以后官方新特性,基于组的复制。
4. 其它的 MariaDB,Percona 自己的 Cluster 架构。
MHA 环境搭建
对于 MHA,可以类比为 Zabbix,拥有 Server 端和 Agent 端,这里就是 Manager 和 Node 端。
整个架构至少包含 3 个数据库,其结构为一主两从,所以这里我们准备了 4 个服务器:
1. 三台服务器都安装 MySQL 数据库并配置好一主两从,具体步骤就不再赘述。
2. 四台服务器都配置 SSH 互信(SSH 免密登录):
# MHA Master 上面生成密钥 rm -rf /root/.ssh ssh-keygen cd /root/.ssh mv id_rsa.pub authorized_keys # 发送密钥到其它主机 scp -r /root/.ssh root@192.168.100.111:/root scp -r /root/.ssh root@192.168.100.112:/root scp -r /root/.ssh root@192.168.100.113:/root # 所有服务器上面测试连接,注意每台都单独执行一次,第一次会有个 yes 确认 ssh root@192.168.100.101 date ssh root@192.168.100.111 date ssh root@192.168.100.112 date ssh root@192.168.100.113 date
3. 下载 MHA:
https://github.com/yoshinorim/mha4mysql-manager/wiki/Downloads
只能下载 RHEL 6 的:
4. 在所有 MySQL 节点安装 Node:
# 安装依赖 yum install -y perl-Config-Tiny epel-release perl-Log-Dispatch perl-Parallel-ForkManager perl-Time-HiRes # 安装 Node rpm -ivh mha4mysql-node-0.56-0.el6.noarch.rpm # MHA manager 节点还要按照 manager rpm -ivh mha4mysql-manager-0.56-0.el6.noarch.rpm
5. 在主库中创建用于 mha 管理的用户:
grant all on *.* to 'mha'@'192.168.100.%' identified by '123';
6. 在 Manager 节点添加配置文件,由于一个 Manager 是可以管理多个 node 集群的,所以最好进行项目区分:
# Manager 节点创建配置和日志目录,这里以 test 项目为例 mkdir -p /etc/mha/test/{conf,logs,data,bin} # 添加配置 vim /etc/mha/test/conf/test.conf
配置详情如下:
[server default] manager_log=/etc/mha/test/logs/manager.log manager_workdir=/etc/mha/test/data master_binlog_dir=/data/logs/mysql/binlog user=mha password=123 ping_interval=2 repl_user=repl repl_password=123 ssh_user=root [server1] hostname=192.168.100.111 port=3306 [server2] hostname=192.168.100.112 port=3306 [server3] hostname=192.168.100.113 port=3306
其中包含 manager 日志路径,工作路径,MySQL binlog 路径,mha 用户,同步用户,ssh 远程用户,以及各个节点。
ping_interval=2 的意思是,每个两秒检测一次,一共检查 3 次,如果三次全失败才算失败。
7. manager 节点测试 SSH 连通性和主从状态:
masterha_check_ssh --conf=/etc/mha/test/conf/test.conf
结果如图:
所有节点 SSH 都是通的。
masterha_check_repl --conf=/etc/mha/test/conf/test.conf
结果如图:
如果有节点是挂的或者不通,则会报异常。
8. 补充说明 MHA 工具包:
命令 | 用途 |
---|---|
masterha_manager | 启动 MHA 命令 |
masterha_check_ssh | SSH 连接检测命令 |
masterha_check_repl | 主从状态检测命令 |
masterha_master_monitor | 检测 Master 是否宕机 |
masterha_check_status | 检测当前 MHA 运行状态 |
masterha_conf_host | 添加或删除 server 配置 |
masterha_stop | 停止 MHA |
9. manager 节点启动 MHA:
nohup masterha_manager --conf=/etc/mha/test/conf/test.conf --remove_dead_master_conf --ignore_last_failover < /dev/null> /etc/mha/test/logs/manager.log 2>&1 &
查看状态:
masterha_check_status --conf=/etc/mha/test/conf/test.conf
结果如图:
显示 MHA 正在运行,且 111 为主库。到此,简单的 MHA 高可用架构就搭建完成。
MHA 架构工作原理
1. 监控原理:
MHA manager 节点通过服务提供的 perl 脚本对主从环境的节点信息,网络,系统,SSH 连通性,主从状态进行监控。
所以需要配置互信,否则监控的时候服务器无法连接过去。
2. 当 Master 节点宕机后,从新选主的规则:
a. 如果 master 和 slave 之间存在延时,则指针或 GTID 最接近主库的成为备选主。
b. 如果两个 slave 的指针或 GTID 相同,则 MHA 配置文件中谁在前面谁成为备选主。
c. 如果配置了权重(candiate_master=1),则按照权重选取:
特殊情况:当 slave 的 relay log 落后主 100M 则权重也不会选他。如果配置了 check_repl_delay=0,则无论如何都选他。
3. 数据补偿机制:
在主库宕机时,并非所以的数据都可能同步完成:
a. SSH 还能连接主库,则立即同步主库 binlog 并应用到新主中(也就是 Node 的 save_binary_logs 脚本,系统自动调用)。
b. SSH 不能连接主库,则对比从库之间的 relaylog(也就是 Node 的 apply_diff_relay_logs 脚本的功能)。
4. Failover:
将备选主身份切换,继续对外提供服务,其余从库和新主从新建立主从关系。
5. 剩下的 VIP / 故障切换通知 / 二次数据补偿后面再说。
模拟主库宕机示例
1. 停止主库,模拟主库宕机:
systemctl stop mysqld
此时查看 MHA manager 状态:
Manager 进程退出,查看状态:
masterha_check_status --conf=/etc/mha/test/conf/test.conf
如图:
查看 MHA 配置文件:
发现宕机的主机配置已经被自动删除掉了。
2. 查看新的主从关系:
登录从库 2 看到主库以及变更成为之前的从库 1 了。
3. 此时如果之前的主库恢复了,如何加入 MHA 集群中去:
简单,由于是 GTID 复制,我们都不需要从新导入数据,直接 change master 到新的主库即可。
这是主从得知识点,这里就不再说明。
4. 添加之前被 MHA 删掉的 server 配置:
[server1] hostname=192.168.100.111 port=3306
5. 再度启动 MHA:
nohup masterha_manager --conf=/etc/mha/test/conf/test.conf --remove_dead_master_conf --ignore_last_failover < /dev/null> /etc/mha/test/logs/manager.log 2>&1 &
查看 MHA 状态:
masterha_check_status --conf=/etc/mha/test/conf/test.conf
如图:
到此,简单的故障切换完成!
MHA 的 VIP
有过运维经验的人此时就发现问题了,而且是大问题。虽然你主从切换了,但是我们业务的服务配置的数据库 IP 地址宕机的那台数据库的。所以我服务还是挂了。这好像并没有解决我们的问题。
于是突发奇想,当初在使用 LVS 或者 Nginx 搭配 Keepalived 使用的时候,有了 VIP(虚拟网卡 IP)的概念,要是能在这使用就好了。
在早期的 MHA 中确实是使用 Keepalived,后来 MHA 自己加入了 VIP 功能,其实就是加入了脚本执行功能,让我们能自己想办法实现。
1. 这里会用到一个脚本,是 perl 语言写的: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 ); # VIP 地址 my $vip = '192.168.100.120/24'; # 虚拟网卡后缀编号 my $key = "1"; # 虚拟网卡使用的网卡 my $ssh_start_vip = "/sbin/ifconfig ens33:$key $vip"; my $ssh_stop_vip = "/sbin/ifconfig ens33:$key down"; GetOptions( 'command=s' => \$command, 'ssh_user=s' => \$ssh_user, 'orig_master_host=s' => \$orig_master_host, 'orig_master_ip=s' => \$orig_master_ip, 'orig_master_port=i' => \$orig_master_port, 'new_master_host=s' => \$new_master_host, 'new_master_ip=s' => \$new_master_ip, 'new_master_port=i' => \$new_master_port, ); exit &main(); sub main { print "\n\nIN SCRIPT TEST====$ssh_stop_vip==$ssh_start_vip===\n\n"; if ( $command eq "stop" || $command eq "stopssh" ) { # $orig_master_host, $orig_master_ip, $orig_master_port are passed. # If you manage master ip address at global catalog database, # invalidate orig_master_ip here. my $exit_code = 1; eval { print "Disabling the VIP on old master: $orig_master_host \n"; &stop_vip(); $exit_code = 0; }; if ($@) { warn "Got Error: $@\n"; exit $exit_code; } exit $exit_code; } elsif ( $command eq "start" ) { # all arguments are passed. # If you manage master ip address at global catalog database, # activate new_master_ip here. # You can also grant write access (create user, set read_only=0, etc) here. my $exit_code = 10; eval { print "Enabling the VIP - $vip on the new master - $new_master_host \n"; &start_vip(); $exit_code = 0; }; if ($@) { warn $@; exit $exit_code; } exit $exit_code; } elsif ( $command eq "status" ) { print "Checking the Status of the script.. OK \n"; `ssh $ssh_user\@$orig_master_host \" $ssh_start_vip \"`; exit 0; } else { &usage(); exit 1; } } # A simple system call that enable the VIP on the new master sub start_vip() { `ssh $ssh_user\@$new_master_host \" $ssh_start_vip \"`; } # A simple system call that disable the VIP on the old_master sub stop_vip() { `ssh $ssh_user\@$orig_master_host \" $ssh_stop_vip \"`; } sub usage { print "Usage: master_ip_failover --command=start|stop|stopssh|status --orig_master_host=host --orig_master_ip=ip --orig_master_port=port --new_master_host=host --new_master_ip=ip --new_master_port=port\n"; }
这里主要注意红色部分!其脚本原理就是使用 ifconfig 给网卡配置 IP 地址。
我这里网卡是 ens33,所以配置虚拟 IP 在 ens33:1 上面。
把 master_ip_failover 脚本上传到 Manager 节点的 /etc/mha/test/bin 下面。
cd /etc/mha/test/bin dos2unix master_ip_failover chmod 755 master_ip_failover
2. 事先给主库配置好虚拟 IP:
ifconfig ens33:1 192.168.100.120/24
查看:
3. 在 MHA 配置文件中加入脚本路径:
master_ip_failover_script=/etc/mha/test/bin/master_ip_failover
4. 重启 MHA:
# 停止 MHA masterha_stop --conf=/etc/mha/test/conf/test.conf # 启动 MHA nohup masterha_manager --conf=/etc/mha/test/conf/test.conf --remove_dead_master_conf --ignore_last_failover < /dev/null> /etc/mha/test/logs/manager.log 2>&1 &
至此,VIP 配置完成!
VIP 故障恢复示例
此时我同样停止主库,模拟故障发生:
systemctl stop mysqld
同时 MHA 会自动退出:
查看当前主从关系:
发现主库重新回到了 111 上面。
同时 VIP 也漂移到了 111 上面!至此,故障 VIP 切换完成。当然,故障恢复和之前一样:
1. 重新建立主从关系。
2. 给 MHA 配置文件加上被删除的配置。
3. 启动 MHA 服务。
故障告警通知
我们在故障发生时,由于自动切换,我们可能并不知道主库已经宕机了,当然特别去关注除外。为了更好的知道线上的服务情况,我们可能会使用一系列的监控手段。比如 Zabbix 这类。
其实 MHA 是能够让我们发生告警消息的,但其实原理和 VIP 切换一样,都是使用执行脚本的方式。
1. 这里以钉钉机器人发消息为例:
#!/bin/bash DB_ADDRESS=$(cat /etc/mha/test/logs/manager.log | grep 'Master failover' | tail -1 | cut -d'(' -f2 | cut -d')' -f1) function SendMessageToDingding(){ Dingding_Url="https://oapi.dingtalk.com/xxxxxxx 这是你自己的钉钉机器人 Token" # 发送钉钉消息 curl "${Dingding_Url}" -H 'Content-Type: application/json' -d " { \"actionCard\": { \"title\": \"$1\", \"text\": \"$2\", \"hideAvatar\": \"0\", \"btnOrientation\": \"0\", \"btns\": [ { \"title\": \"$1\", \"actionURL\": \"\" } ] }, \"msgtype\": \"actionCard\" }" } Subject="数据库主库宕机啦~" Body="新主库:${DB_ADDRESS}" SendMessageToDingding $Subject $Body
至于这个脚本写法,可以参照我之前的博客:
将脚本上传到 /etc/mha/test/bin 下面,我给它命名:send_dingding_message,注意修改它的权限。
2. 在配置文件中加入 report_send:
report_script=/etc/mha/test/bin/send_dingding_message
重启 MHA,然后停止主库模拟故障,这样就能收到钉钉通知了:
当然你也可以以其它方式通知,比如邮件。脚本都很简单。
binlog 远程保存
很多时候主库宕机可能出现一个问题,binlog 未及时同步或者本身设置了延时同步。此时如果主库机器已经坏了,这意味着数据就挂了。
那就得想一个办法,类似于将它的 binlog 保留一份到其他机器专门用于出现这样得情况时候从数据库好去这上面查看 binlog 从而实现二次数据补偿。
这就是这个小节需要实现得东西。我们将 binlog 备份保留到 manager 上面。
1. 由于需要用到 MySQL 命令,所以安装 MySQL,并不需要启动,安装方法参考前面得博客:
2. 在 MHA 配置文件中加入 binlog 备份配置:
[binlog1] no_master=1 hostname=192.168.100.101 master_binlog_dir=/data/backup/mysql/binlog
并创建该目录授权:
mkdir /data/backup/mysql/binlogchown -R mysql.mysql /data/backup/mysql
3. 查看当前从库的 binlog 文件名:
show slave status\G
结果:
4. 去 manager 节点拉去该日志:
cd /data/backup/mysql/binlog/ mysqlbinlog -R --host=192.168.100.111 --user=mha --password=123 --raw --stop-never mysql-bin.000003 &
这比如进入目录。
5. 重启 MHA:
masterha_stop --conf=/etc/mha/test/conf/test.conf nohup masterha_manager --conf=/etc/mha/test/conf/test.conf --remove_dead_master_conf --ignore_last_failover < /dev/null> /etc/mha/test/logs/manager.log 2>&1 &
可以 ps 查看到两个进程:
6. 去主库刷新日志查看:
查看备份:
自动已经开始备份新的 binlog 文件。至此异地备份完成,主库宕机,从库会自动读取这里最终实现数据一致。
恢复说明:
这个恢复有点不一样,当从库从 binlog 恢复完成以后。
1. 重新将故障主机加入主从。
2. 清空 binlog 备份,在 MHA 中重新加入 binlog 配置和 server 配置。
3. 重新启动 MHA 架构恢复。
小结
高可用肯定是数据库的一个核心功能,本文的 MHA 主要从高可用 + VIP + 发送告警 + binlog 备份恢复来完善高可用。最好的搭建时候其实就是感慨是设计架构的时候。当然,高可用的架构阿里云也是有的,阿里云的 RDS 就自带高可用。