MySQL高可用架构:MHA

MHA(Master HA)是一款开源的 MySQL 的高可用程序,它为 MySQL 主从复制架构提供了 automating master failover 功能。MHA 在监控到 master 节点故障时,会提升其中拥有最新数据的 slave 节点成为新的master 节点,在此期间,MHA 会通过于其它从节点获取额外信息来避免一致性方面的问题。MHA 还提供了 master 节点的在线切换功能,即按需切换 master/slave 节点。 MHA 是由日本人 yoshinorim(原就职于DeNA现就职于FaceBook)开发的比较成熟的 MySQL 高可用方案。MHA 能够在30秒内实现故障切换,并能在故障切换中,最大可能的保证数据一致性。目前淘宝也正在开发相似产品 TMHA, 目前已支持一主一从。

一、MHA的组成

(一)MHA中的角色

MHA 服务有两种角色, MHA Manager(管理节点)和 MHA Node(数据节点):

Node 节点(执行层,含 Master/Slave)

Node 节点是 MySQL 数据存储与复制的载体,同时提供 MHA 切换所需的数据修复、binlog 管理工具,核心工具及功能如下:

  • MHA Manager:通常单独部署在一台独立机器上管理多个 master/slave 集群(组),每个master/slave 集群称作一个application,用来管理统筹整个集群。

  • MHA Node:运行在每台 MySQL 服务器上(master/slave/manager),它通过监控具备解析和清理 logs 功能的脚本来加快故障转移。主要是接收管理节点所发出指令的代理,代理需要运行在每一个 mysql 节点上。简单讲node就是用来收集从节点服务器上所生成的bin-log。对比打算提升为新的主节点之上的从节点的是否拥有并完成操作,如果没有发给新主节点在本地应用后提升为主节点。

    (二)MHA提供的工具

    MHA(Master High Availability)是 MySQL 高可用解决方案,通过 Manager 节点(管理节点)和 Node 节点(数据节点,含 Master、Slave)协同工作,实现 MySQL 主从集群的故障检测、自动切换与数据一致性保障,两者依赖不同工具完成核心任务,具体如下:

    Manager 节点(控制中心)

    Manager 节点是 MHA 的调度核心,负责监控集群、检测故障、触发切换,核心工具及功能如下:

  • masterha_manager :MHA 主进程,是 Manager 节点的 "心脏"。持续监控 Master/Slave 存活状态(通过 SSH 心跳 + MySQL 连接)、主从复制延迟,发现 Master 故障后自动触发切换流程,同时将监控日志输出到指定路径(如 /var/log/mha)。

  • masterha_check_ssh:部署前必执行工具,检查 Manager 与所有 Node 节点的 SSH 免密登录连通性。只有确保 Manager 能无密码访问所有 Node,后续切换时才能远程操作 Node 节点,避免 SSH 问题导致切换失败。

  • masterha_check_repl:检查 MySQL 主从复制健康状态,包括复制链路是否通畅、Slave 延迟是否过大、binlog 一致性等。可用于部署前验证复制正常性,也可在日常维护中排查复制异常(如 Slave 报错)。

  • masterha_master_switch:手动触发主从切换,支持 "故障切换"(Master 宕机后指定新 Master)和 "在线切换"(Master 正常时主动切换角色,如主节点升级)两种模式。

  • masterha_conf_host :动态修改 MHA 配置文件,支持在线添加 / 删除 Slave 节点、更新复制参数,无需停止 masterha_manager 进程,适配集群动态扩容 / 缩容场景。

  • masterha_stop :停止 Manager 节点的 masterha_manager 监控进程,多用于集群维护(如重启 MySQL)时临时停止监控,避免误触发切换。

  • save_binary_logs:Master 故障时,在 Master 节点(或其备份)上保存未传输的 binlog。若 Master 宕机前有 binlog 未同步到 Slave,此工具可通过 SSH 提取剩余 binlog,后续用于 Slave 数据补全,避免数据丢失。

  • apply_diff_relay_logs:在 Slave 节点修复数据差异。切换后需确保所有 Slave 数据一致,此工具会对比 Slave 中继日志与 Master 剩余 binlog,自动补全数据差异,保障新 Master 与 Slave 数据同步。

  • filter_mysqlbinlog :过滤 MySQL binlog 中的无效语句(如 CREATE DATABASEDROP TABLE 等)。在数据修复过程中排除无需同步的操作,避免 Slave 执行无效语句报错。

  • purge_relay_logs:清理 Slave 节点的中继日志(relay log)。按配置自动保留指定时间 / 大小的中继日志,兼顾数据安全性(避免误删有用日志)和磁盘利用率(防止日志占满磁盘)。

二、MHA的工作原理

  • 从宕机崩溃的master保存二进制日志事件(binlog events);

  • 识别含有最新更新的 slave ;

  • 应用差异的中继日志(relay log) 到其他 slave ;

  • 应用从 master 保存的二进制日志事件(binlog events);

  • 提升一个 slave 为新 master ;

  • 使用其他的 slave 连接新的 master 进行复制。

三、部署MHA

(一)准备实验环境

  • 三台主机:master、slave1、slave2

  • 操作系统:CentOS

  • 配置主机名(/etc/hosts)

  • 配置免密码登录

  • 每台主机安装MySQL

(二)搭建主从环境

  • 每台MySQL开启binlog,设置server-id

vi /etc/my.cnf

mysqld

server-id=1 # 注意:每台MySQL设置不同的server-id

log-bin=mysql-bin # 启用二进制日志

log-slave-updates=true

gtid_mode=ON

enforce_gtid_consistency=ON

#设置3306端口

port=3306

设置mysql的安装目录

basedir=/usr/local/mysql

设置mysql数据库的数据的存放目录

datadir=/usr/local/mysql/mysqldb

允许最大连接数

max_connections=10000

允许连接失败的次数。这是为了防止有人从该主机试图攻击数据库系统

max_connect_errors=10

服务端使用的字符集默认为UTF8

character-set-server=utf8

创建新表时将使用的默认存储引擎

default-storage-engine=INNODB

默认使用"mysql_native_password"插件认证

default_authentication_plugin=mysql_native_password

mysql

设置mysql客户端默认字符集

client

设置mysql客户端连接服务端时默认使用的端口

port=3306

  • 在所有机器上,创建主从复制的账号

create user 'repl'@'10.1.1.%' identified by 'master';

grant replication slave on *.* to 'repl'@'10.1.1.%';

flush privileges;

  • 在所有机器上,创建管理账号

create user 'myadmin'@'10.1.1.%' identified by 'master';

grant all privileges on *.* to 'myadmin'@'10.1.1.%';

flush privileges;

  • 在主库和从库上,启用GTID

set @@GLOBAL.ENFORCE_GTID_CONSISTENCY = ON;

set @@GLOBAL.GTID_MODE = OFF_PERMISSIVE;

set @@GLOBAL.GTID_MODE = ON_PERMISSIVE;

set @@GLOBAL.GTID_MODE = ON;

参数说明: GTID是MySQL 5.6引入的新特性,其全称是Global Transaction Identifier,可简化MySQL的主从切换以及Failover。GTID用于在binlog中唯一标识一个事务。当事务提交时,MySQL Server在写binlog的时候,会先写一个特殊的Binlog Event,类型为GTID_Event,指定下一个事务的GTID,然后再写事务的Binlog。主从同步时GTID_Event和事务的Binlog都会传递到从库,从库在执行的时候也是用同样的GTID写binlog,这样主从同步以后,就可通过GTID确定从库同步到的位置了。也就是说,无论是级联情况,还是一主多从情况,都可以通过GTID自动找点儿,而无需像之前那样通过File_name和File_position找点儿了。

  • 在从库上分别配置主从复制命令并开启主从同步

change master to master_host='master',master_user='repl',\

master_password='123456',master_auto_position=1;

start slave;

  • 在从库上查看主从复制的状态

show slave status\G;

  • 测试主从复制

(三)安装MHA

  • 所有节点安装node软件依赖包和node软件

yum -y install perl-DBD-MySQL

rpm -ivh mha4mysql-node-0.58-0.el7.centos.noarch.rpm

  • 在master上安装安装manager软件依赖包

yum install -y perl-Config-Tiny

yum install -y epel-release

yum install -y perl-Log-Dispatch

yum install -y perl-Parallel-ForkManager

yum install -y perl-Time-HiRes

  • 在master上安装安装manager软件

rpm -ivh mha4mysql-manager-0.58-0.el7.centos.noarch.rpm

#创建配置文件目录、日志目录

mkdir -p /etc/mha

mkdir -p /var/log/mha/log

#编辑mha配置文件 vi /etc/mha/mha.cnf

server default

manager_log=/var/log/masterha/manager.log

manager_workdir=/var/log/masterha

master_binlog_dir=/usr/local/mysql/mysqldb/

user=myadmin

password=12345678

ping_interval=2

repl_user=repl

repl_password=12345678

ssh_user=root

master_ip_failover_script=/etc/mha/master_ip_failover

server1

hostname=master

port=3306

server2

hostname=slave1

port=3306

server3

hostname=slave2

port=3306

(五)检查状态,并开启MHA

  • 检查互信

masterha_check_ssh --conf=/etc/mha/mha.cnf

  • 检查主从复制状态

masterha_check_repl --conf=/etc/mha/mha.cnf

出现的问题:

1、需要注释三台主机中my.cnf中 [client]下的字符配置;

2、配置三台主机中MySQL的环境变量;

3、如果之前测试过故障自动转移,需要将/var/log/mha/log文件夹下的对应的 complete文件删除

以上处理完成之后再进行检测集群是否健康。

  • 开启MHA-manager

nohup masterha_manager --conf=/etc/mha/mha.cnf > /var/log/mha/log/manager.log < /dev/null 2>&1 &

  • 查看MHA状态

masterha_check_status --conf=/etc/mha/mha.cnf

  • 测试HA高可用的自动切换

在master上,执行shutdown操作

mysqladmin -uroot -p12345678 shutdown

MHA会自动进行主从切换。切换完成后,MHA进程会自动停止运行。

在slave1和slave2上观察,执行下面的语句:

show slave status\G;

四、MHA + VIP 部署

(一)修改mha.cnf配置文件

vim /etc/mha/mha.cnf

在server-default下添加如下代码

master_ip_failover_script=/etc/mha/master_ip_failover

(二) master_ip_failover代码如下:

#!/bin/bash

完整的 MHA VIP 管理脚本(支持自动切换 VIP)

配置参数

VIP="10.1.1.26" # VIP 地址

NETMASK="24" # 子网掩码

INTERFACE="ens33" # 网络接口(修正拼写错误:end33 → ens33)

ARPING_COUNT=3 # ARP 广播次数

LOG_FILE="/var/log/mha/vip_scripts.log"

SSH_OPTS="-o StrictHostKeyChecking=no -o ConnectTimeout=10" # SSH 选项

解析参数

command=""

ssh_user=""

orig_master_host=""

orig_master_ip=""

orig_master_port=""

new_master_host=""

new_master_ip=""

new_master_port=""

while [[ $# -gt 0 ]]; do

case "$1" in

--command=*)

command="${1#*=}"

shift

;;

--ssh_user=*)

ssh_user="${1#*=}"

shift

;;

--orig_master_host=*)

orig_master_host="${1#*=}"

shift

;;

--orig_master_ip=*)

orig_master_ip="${1#*=}"

shift

;;

--orig_master_port=*)

orig_master_port="${1#*=}"

shift

;;

--new_master_host=*)

new_master_host="${1#*=}"

shift

;;

--new_master_ip=*)

new_master_ip="${1#*=}"

shift

;;

--new_master_port=*)

new_master_port="${1#*=}"

shift

;;

*)

shift

;;

esac

done

日志函数

log() {

local level=$1

local message=$2

local timestamp=$(date '+%Y-%m-%d %H:%M:%S')

echo "timestamp \[level] message" \>\> "LOG_FILE"

同时输出到控制台(便于调试)

echo -e "[timestamp\] \[level] $message"

}

执行 SSH 命令并返回结果

ssh_exec() {

local host=$1

local cmd=$2

log "INFO" "在 host 执行命令: cmd"

执行 SSH 命令并捕获输出和退出码

local output=(ssh SSH_OPTS "ssh_user@host" "$cmd" 2>&1)

local exit_code=$?

if [ $exit_code -ne 0 ]; then

log "ERROR" "SSH 命令执行失败(host): output"

else

log "INFO" "SSH 命令执行成功(host): output"

fi

return $exit_code

}

添加 VIP 到新主库

add_vip() {

local host=$1

log "INFO" "开始在新主库 host 添加 VIP: VIP/$NETMASK"

1. 检查网络接口是否存在

local check_interface="ip link show $INTERFACE > /dev/null 2>&1"

if ! ssh_exec "host" "check_interface"; then

log "ERROR" "网络接口 INTERFACE 在 host 上不存在"

return 1

fi

2. 添加 VIP

local add_cmd="ip addr add VIP/NETMASK dev INTERFACE label {INTERFACE}:0"

if ! ssh_exec "host" "add_cmd"; then

log "ERROR" "在 $host 上添加 VIP 失败"

return 1

fi

3. 发送 ARP 广播(更新网络缓存)

local arp_cmd="arping -c ARPING_COUNT -A -I INTERFACE $VIP > /dev/null 2>&1"

if ! ssh_exec "host" "arp_cmd"; then

log "WARN" "ARP 广播发送失败,但 VIP 已添加"

fi

log "INFO" "VIP 已成功添加到 $host"

return 0

}

从原主库移除 VIP

remove_vip() {

local host=$1

log "INFO" "开始从原主库 host 移除 VIP: VIP"

1. 检查 VIP 是否存在

local check_vip="ip addr show INTERFACE \| grep -q 'VIP/$NETMASK'"

if ! ssh_exec "host" "check_vip"; then

log "WARN" "VIP VIP 在 host 上不存在,跳过移除"

return 0

fi

2. 移除 VIP

local del_cmd="ip addr del VIP/NETMASK dev $INTERFACE"

if ! ssh_exec "host" "del_cmd"; then

log "ERROR" "从 $host 上移除 VIP 失败"

return 1

fi

3. 发送 ARP 广播(通知网络更新)

local arp_cmd="arping -c ARPING_COUNT -A -I INTERFACE $VIP > /dev/null 2>&1"

ssh_exec "host" "arp_cmd"

log "INFO" "VIP 已成功从 $host 移除"

return 0

}

主逻辑

main() {

初始化日志目录

mkdir -p "(dirname "LOG_FILE")"

log "INFO" "===== VIP 管理脚本启动 ====="

log "INFO" "解析到的参数: command=command, 原主库=orig_master_host, 新主库=$new_master_host"

case "$command" in

"start")

新主库启动 VIP

if [ -z "$new_master_host" ]; then

log "ERROR" "start 命令缺少新主库参数"

exit 1

fi

if add_vip "$new_master_host"; then

log "INFO" "VIP 切换到新主库 $new_master_host 成功"

exit 0

else

log "ERROR" "VIP 切换到新主库 $new_master_host 失败"

exit 1

fi

;;

"stop"|"stopssh")

从原主库移除 VIP

if [ -z "$orig_master_host" ]; then

log "ERROR" "stop 命令缺少原主库参数"

exit 1

fi

if remove_vip "$orig_master_host"; then

log "INFO" "从原主库 $orig_master_host 移除 VIP 成功"

exit 0

else

log "ERROR" "从原主库 $orig_master_host 移除 VIP 失败"

exit 1

fi

;;

"status")

MHA 状态检查(必须返回 0)

log "INFO" "脚本状态正常,支持 VIP 管理"

exit 0

;;

*)

log "ERROR" "未知命令: $command(支持的命令: start/stop/stopssh/status)"

exit 1

;;

esac

}

执行主函数

main "$@"

注意:

1.需要修改master_ip_failover的权限:chmod +x master_ip_failover

2.注意虚拟IP的网段:10.1.1.26需要根据自己的IP网段修改

(三)测试

测试状态检查(应返回成功)

/etc/mha/master_ip_failover --command=status --ssh_user=root

测试添加 VIP 到 slave1

/etc/mha/master_ip_failover --command=start --ssh_user=root --new_master_host=slave1

测试从 slave1 移除 VIP

/etc/mha/master_ip_failover --command=stop --ssh_user=root --orig_master_host=slave1

其他:

添加虚拟IP:

ifconfig ens33:0 110.1.1.26/24

删除虚拟IP:

ip addr del 10.1.1.26/24 dev ens33

相关推荐
罗光记2 小时前
苹果内部 AI聊天机器人“Asa”曝光,为零售员工打造专属A
数据库·经验分享·百度·微信公众平台·新浪微博
zz-zjx3 小时前
进程与线程详解, IPC通信与RPC通信对比,Linux前台与后台作业
linux·网络协议·rpc
li35744 小时前
从“内存操作”到“原子更新”:一次代码思维的跃迁
数据库·oracle
大筒木老辈子4 小时前
Linux笔记---计算机网络概述
linux·笔记·计算机网络
瀚高PG实验室4 小时前
执行select * from a where rownum<1;,数据库子进程崩溃,业务中断。
数据库·sql·瀚高数据库
小白考证进阶中4 小时前
终于赶在考试券过期前把Oracle OCP证书考下来了!
运维·数据库·oracle·dba·开闭原则·数据库管理员
天若有情6735 小时前
作为软件专业学生,我眼中新架构实践的‘稳’与‘进’
架构
DCTANT5 小时前
【报错记录】OpenGauss/磐维数据库连接报:org.postgresql.util.PSQLException: 致命错误: 账户被锁定
数据库·postgresql
KING BOB!!!5 小时前
Leetcode高频 SQL 50 题(基础版)题目记录
sql·mysql·算法·leetcode