1. 为什么手动搭建MHA依然值得学
1.1 从一次主库宕机说起
凌晨两点,监控告警响了,主库连接数直接掉零。当时我们那套业务用的是单点MySQL,没有做任何高可用。运维同学从被窝里爬起来,手动切从库、改配置、重启服务,前后折腾了四十多分钟。那四十分钟里,所有写操作全部失败,订单堆积、用户投诉、客服电话被打爆。事后复盘,问题不在于“有没有备份”,而在于“切换太慢、太依赖人”。
这件事之后我开始认真研究MySQL的高可用方案。市面上方案不少,但MHA(Master High Availability)是我认为最适合中小团队手动搭建、也最能让人真正理解主从切换底层逻辑的一套架构。它不像某些商业方案那样是个黑盒,你能清楚看到每一步在干什么,出了问题也知道从哪儿查。
这篇文章就是把我自己搭MHA的完整过程拆开讲。MHA的核心价值在于:主库挂了之后,能在几十秒内自动完成故障检测、日志补齐、从库提升、其他从库重新指向新主库这一整套动作,把原本需要人工介入几十分钟的活儿压缩到半分钟以内。适合谁看?有一定Linux和MySQL基础、想给自己的业务加一层高可用保障、又不想一上来就上复杂分布式方案的开发和运维同学。小白也能跟,因为我会把每个参数为什么这么配都讲清楚。
1.2 MHA到底解决了什么问题
先把概念说透。MHA是一套由日本开发者维护的开源工具集,它本身不是MySQL的插件,而是运行在管理节点上的一组Perl脚本,配合每个MySQL节点上的agent程序工作。它的工作模式分两块:Manager节点负责监控和决策,Node节点负责执行具体的切换动作。
它解决的核心问题是主库单点故障。传统主从架构里,主库一挂,从库虽然数据还在,但没人告诉应用该连哪儿,也没人把从库提升成新主库。MHA做的事情就是:持续探测主库心跳,一旦发现主库不可达,立刻从所有从库中挑一个数据最全的(通过对比relay log和binlog的位点),把它提升为新主库,然后把其余从库重新挂到新主库下面,最后(可选)尝试拉起原主库作为新从库。
这里有个关键点很多人忽略:MHA能最大程度保证数据不丢。它会在切换前,从宕机主库上(如果SSH还能连上)把还没同步到从库的binlog差异部分抓下来,补到新主库上。这个动作叫“日志补齐”,是MHA区别于简单脚本切换的核心竞争力。当然,如果主库彻底宕机、SSH都连不上,那这部分差异就补不回来了,这也是为什么MHA官方建议配合半同步复制使用。
2. 搭建前的架构设计与环境规划
2.1 节点角色怎么分配
动手之前先把机器规划好,不然后面改来改去很痛苦。我这次用的是四台机器(虚拟机即可),角色分配如下:
| 角色 | 主机名 | 说明 |
|---|---|---|
| Manager管理节点 | mha-manager | 跑MHA Manager,负责监控和切换 |
| 主库 | mysql-master | 初始主库,写节点 |
| 从库1 | mysql-slave1 | 候选主库,优先级高 |
| 从库2 | mysql-slave2 | 候选主库,优先级低 |
为什么Manager要单独一台?因为Manager本身如果和MySQL混部,主库挂了可能把Manager一起带崩,监控就失效了。管理节点必须独立,这是铁律。如果资源紧张,Manager可以和某台从库混部,但绝不能和主库混部。
从库数量建议至少两个。一个从库也能切,但没有冗余,切换后新架构依然脆弱。两个从库的好处是:一个提升为主库,另一个继续做从库,架构立刻恢复成一主一从,还有余量。
2.2 版本选择与依赖清单
版本这块我踩过坑,必须说清楚。MHA对MySQL版本和操作系统都有要求:
- MySQL:建议5.7或8.0。MHA 0.58版本对8.0支持较好,但要注意8.0默认的caching_sha2_password认证插件MHA可能不认,需要改成mysql_native_password。
- 操作系统:CentOS 7或Rocky Linux 8都行,我用的是Rocky 8。关键是Perl环境要全,MHA是Perl写的,缺模块会直接报错。
- SSH互信:Manager到所有Node、Node之间都要配SSH免密。MHA靠SSH来远程执行命令和拷贝binlog,这个不通后面全白搭。
依赖清单我列一下,装的时候一次性搞定:
# 所有节点都需要的依赖 yum install -y perl-DBD-MySQL perl-Config-Tiny perl-Log-Dispatch \ perl-Parallel-ForkManager perl-Time-HiRes perl-ExtUtils-MakeMaker \ perl-Data-Dumper perl-Digest-MD5注意:
perl-Parallel-ForkManager这个模块特别容易漏,MHA Manager启动时会用它做并行操作,缺了会报“Can't locate Parallel/ForkManager.pm”。我第一次搭就是卡在这儿,查了半天。
2.3 网络与端口规划
端口方面,MySQL用默认3306,MHA Manager本身不监听端口,它是主动去连MySQL和SSH。但要注意防火墙:Manager要能访问所有Node的3306和22端口,Node之间也要能互相访问3306和22。我图省事直接把内网网段全放通了,生产环境建议按需开放。
主机名解析也要配好,/etc/hosts里把四台机器的IP和主机名对应写上。MHA配置里用主机名比用IP更清晰,切换日志也好读。
3. 主从复制环境的手动搭建
3.1 主库配置与账号准备
MHA是建立在主从复制基础上的,所以第一步得先把一主两从的复制跑通。先改主库的my.cnf:
[mysqld] server-id=1 log-bin=mysql-bin binlog_format=ROW gtid_mode=ON enforce_gtid_consistency=ON log_slave_updates=ON这里几个参数解释一下。binlog_format=ROW是MHA推荐的,因为行复制在数据一致性上更可靠,切换时位点计算也更准。gtid_mode=ON开启GTID,好处是切换后从库找位点不用再算binlog文件名和position,直接靠GTID自动定位,极大降低切换出错概率。log_slave_updates=ON是给从库用的,让从库也把收到的更新写进自己的binlog,这样从库才能作为候选主库继续向下游同步。
改完重启MySQL,然后创建复制账号和MHA管理账号:
-- 复制账号 CREATE USER 'repl'@'%' IDENTIFIED BY 'Repl_2024#Pass'; GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%'; -- MHA管理账号,需要较大权限 CREATE USER 'mha'@'%' IDENTIFIED BY 'Mha_2024#Pass'; GRANT ALL PRIVILEGES ON *.* TO 'mha'@'%' WITH GRANT OPTION; FLUSH PRIVILEGES;提示:MHA管理账号权限要给足,因为它要执行
CHANGE MASTER TO、RESET SLAVE、SET GLOBAL read_only等操作。生产环境可以细化权限,但搭建阶段先给全,跑通再收紧。
3.2 从库配置与复制启动
两个从库的my.cnf类似,只是server-id不同:
[mysqld] server-id=2 # slave1用2,slave2用3 log-bin=mysql-bin binlog_format=ROW gtid_mode=ON enforce_gtid_consistency=ON log_slave_updates=ON read_only=ONread_only=ON让从库只读,防止误写。但注意这个参数对拥有SUPER权限的用户无效,MHA切换时会临时改这个值,所以别用super_read_only,否则MHA改不动。
配置好后,在两个从库上分别执行:
CHANGE MASTER TO MASTER_HOST='mysql-master', MASTER_USER='repl', MASTER_PASSWORD='Repl_2024#Pass', MASTER_AUTO_POSITION=1; START SLAVE;MASTER_AUTO_POSITION=1就是启用GTID自动定位。执行完用SHOW SLAVE STATUS\G检查,确认Slave_IO_Running和Slave_SQL_Running都是Yes,Seconds_Behind_Master为0。
3.3 验证复制与SSH互信
复制跑通后,在主库建个测试库写点数据,看从库能不能同步过来。这一步别省,我见过配置看着都对但实际不同步的情况,多半是防火墙或账号权限问题。
SSH互信配置:Manager节点生成密钥,公钥分发到所有Node;同时每个Node也生成密钥,互相分发。简单点可以直接在Manager上对所有节点做免密,Node之间也做。验证方法就是ssh mysql-master不输密码能进去。
# Manager上执行 ssh-keygen -t rsa -N '' for host in mysql-master mysql-slave1 mysql-slave2; do ssh-copy-id $host done4. MHA Manager与Node的安装配置
4.1 安装MHA软件包
MHA分两部分:Node节点装mha4mysql-node,Manager节点装mha4mysql-node加mha4mysql-manager。官方没有现成的yum源,得下源码包编译或找rpm包。我用的是源码编译,虽然慢点但兼容性好。
# 所有节点装node tar -zxvf mha4mysql-node-0.58.tar.gz cd mha4mysql-node-0.58 perl Makefile.PL make && make install # Manager节点额外装manager tar -zxvf mha4mysql-manager-0.58.tar.gz cd mha4mysql-manager-0.58 perl Makefile.PL make && make install装完检查一下命令在不在:which masterha_manager、which masterha_check_ssh。这两个命令后面要用。
4.2 编写Manager配置文件
这是整个搭建的核心。在Manager节点上建/etc/mha/app1.cnf:
[server default] manager_workdir=/var/log/mha/app1 manager_log=/var/log/mha/app1/manager.log user=mha password=Mha_2024#Pass ssh_user=root repl_user=repl repl_password=Repl_2024#Pass ping_interval=2 master_binlog_dir=/var/lib/mysql [server1] hostname=mysql-master candidate_master=1 check_repl_delay=0 [server2] hostname=mysql-slave1 candidate_master=1 check_repl_delay=0 [server3] hostname=mysql-slave2 no_master=1逐项解释。ping_interval=2是每2秒探测一次主库,探测越频繁切换越快,但网络抖动时容易误判,2秒是个平衡点。master_binlog_dir告诉MHA去哪个目录找binlog,路径必须和实际一致,否则日志补齐会失败。
candidate_master=1表示这个节点可以作为候选主库。我给两个从库都设了,但slave1的优先级更高(MHA按配置顺序选)。check_repl_delay=0是关闭复制延迟检查,意思是即使这个从库有延迟也允许它当主库。这个参数有争议:设0切换快但可能丢数据,设1更安全但延迟大时切不了。我建议只在延迟可控的环境设0,否则还是保守点。
no_master=1表示slave2永远不参与主库竞选,纯做从库。这样架构更清晰。
4.3 配置校验与启动
配置写完别急着启动,先用MHA自带的检查工具验证:
# 检查SSH互信 masterha_check_ssh --conf=/etc/mha/app1.cnf # 检查复制状态 masterha_check_repl --conf=/etc/mha/app1.cnfcheck_ssh会遍历所有节点两两测试,全绿才行。check_repl会检查主从复制、账号权限、binlog格式等,输出“MySQL Replication Health is OK”才算过。
这两个检查我强烈建议每次都跑。我遇到过check_repl报“binlog format is not ROW”的情况,回头查才发现有个从库的my.cnf没改全。这种问题不检查直接启动,切换时才发现就晚了。
启动Manager:
nohup masterha_manager --conf=/etc/mha/app1.cnf \ --remove_dead_master_conf \ --ignore_last_failover \ > /var/log/mha/app1/manager.out 2>&1 &--remove_dead_master_conf表示切换后把死掉的主库配置从文件里删掉,避免下次启动报错。--ignore_last_failover是忽略上次的failover记录,否则MHA默认8小时内不重复切换。这两个参数在测试阶段很有用。
5. 故障切换实测与日志解读
5.1 模拟主库宕机
搭好了不测等于没搭。测试方法很简单:在Manager运行状态下,直接把主库的MySQL进程杀掉,或者更彻底点关机。
# 在mysql-master上 systemctl stop mysqld然后盯着Manager日志/var/log/mha/app1/manager.log。正常的话几秒内就会看到“Ping(SELECT) failed”之类的探测失败记录,接着开始failover流程。
5.2 切换过程逐步拆解
MHA的切换流程大致分这几个阶段,日志里能清楚看到:
- 检测阶段:连续多次ping失败后,判定主库宕机。
- 日志补齐阶段:尝试SSH连原主库,把差异binlog拷到Manager,再应用到新主库。如果SSH连不上,跳过这步并记录警告。
- 选主阶段:从候选从库中选一个,对比GTID或位点,选数据最新的。
- 提升阶段:对新主库执行
STOP SLAVE、RESET SLAVE ALL、SET GLOBAL read_only=OFF,把它变成可写主库。 - 重定向阶段:其余从库执行
CHANGE MASTER TO指向新主库,重新启动复制。 - 收尾阶段:写failover记录,Manager进程退出(MHA Manager默认切换后自己停掉,需要配合守护脚本重新拉起或手动重启)。
整个流程实测下来,从杀进程到新主库可写,大概20到40秒。比人工快太多了。
5.3 切换后验证与恢复
切换完成后,登录新主库(原来的slave1)确认:
SHOW MASTER STATUS; SELECT @@read_only; -- 应该是0再登录slave2,SHOW SLAVE STATUS\G确认它已经指向新主库且复制正常。
原主库修好后,可以手动把它重新加入集群作为从库。步骤是:启动MySQL,执行CHANGE MASTER TO指向新主库,START SLAVE。注意原主库上可能有新主库没有的数据(宕机前没同步的),所以重新加入前最好确认数据一致性,或者干脆用备份重建。
注意:MHA切换后Manager进程会退出,这是设计如此。生产环境要用
masterha_manager配合systemd或supervisor做守护,让它退出后自动重启,否则第二次故障就没人管了。
6. 常见问题排查与避坑经验
6.1 高频报错速查表
| 报错信息 | 原因 | 解决 |
|---|---|---|
| Can't locate Parallel/ForkManager.pm | 缺Perl模块 | 装perl-Parallel-ForkManager |
| SSH connection failed | 互信没配好 | 重新分发公钥,检查authorized_keys权限 |
| binlog format is not ROW | binlog格式不对 | 改my.cnf并重启MySQL |
| MySQL Replication Health is NOT OK | 复制断了或账号错 | 查SHOW SLAVE STATUS,核对账号密码 |
| Failed to get master binlog | binlog路径配错 | 核对master_binlog_dir与实际路径 |
| Authentication plugin 'caching_sha2_password' | MySQL8认证插件问题 | 改用户为mysql_native_password |
6.2 几个容易翻车的细节
第一个坑:SSH互信只配了单向。MHA不仅Manager要连Node,Node之间也要互相连(比如新主库要从原主库拷binlog)。我第一次只配了Manager到Node,切换时报“SSH to mysql-master failed”。后来补全了所有节点两两互信才通过。
第二个坑:GTID和位点混用。如果主从用GTID,MHA配置里也要相应调整,否则切换时位点计算会出错。我建议要么全GTID,要么全位点,别混。
第三个坑:read_only和super_read_only。前面提过,MHA切换要改read_only,如果开了super_read_only,连SUPER用户都写不了,切换会卡住。生产环境如果非要开super_read_only,得在MHA配置里做特殊处理。
第四个坑:Manager单点。MHA Manager本身也是单点,它挂了就没人监控了。生产环境可以考虑用两个Manager做互备,或者用keepalived给Manager做VIP。这个进阶话题以后可以单独聊。
6.3 我的实操心得
搭MHA最耗时的不是配置,而是排查环境问题。我的建议是:先把主从复制跑稳,再装MHA;装完先跑check_ssh和check_repl,两个都过了再启动;启动后立刻做一次切换测试,别等真出事才发现切不了。
另外,MHA的日志非常详细,出问题先看manager.log,里面会告诉你卡在哪一步。我大部分问题都是靠日志定位的,比瞎猜快得多。
还有一点,MHA不是银弹。它能解决主库宕机的自动切换,但解决不了数据延迟导致的丢失(除非配合半同步),也解决不了脑裂(需要额外机制)。把它当成高可用体系里的一环,而不是全部。
最后分享个小技巧:测试切换时,可以用masterha_master_switch --master_state=alive做在线切换,不用真杀进程。这个命令能在主库活着的时候演练切换,对生产环境做维护窗口特别有用。