脚本部署MHA集群最近在帮一个朋友梳理MySQL高可用方案他那边业务量不大但对数据可靠性要求挺高主库宕机超过五分钟就要被领导约谈那种。调研了一圈最后还是选了MHAMaster High Availability这套方案。不少人在这一步直接卡住觉得MHA配置繁琐、节点多、手工操作容易漏。其实只要把部署过程脚本化半小时建一套集群完全不是问题。这篇文章就把我整理的一键部署脚本思路、核心配置和踩过的坑全部盘一遍给正在折腾MySQL高可用的同学一个可以抄作业的参考。先明确一下MHA的核心价值它是一套成熟的MySQL主从架构自动故障切换方案在主库发生故障时能自动从多个从库中选出数据最完整的一个补全差异日志提升为新主库同时让其余从库重新指向新主库整个切换过程对业务方透明应用只需要感知VIP漂移或连接串切换。相比MGR、Orchestrator这些方案MHA的优势在于对已有主从架构改动极小不需要改表结构、不需要插件支持非常适合基于异步复制搭建的高可用环境。1. 内容整体设计与思路拆解1.1 为什么选择脚本化部署MHA手工部署MHA要经历的过程大致是这样在所有节点安装依赖包、安装node包、管理节点安装manager包、配置SSH互信、写app1.cn配置文件、编写VIP切换脚本、启动manager进程、验证切换流程。每一步都依赖上一步成功而且中间穿插大量容易出错的环节比如Perl模块缺失、SSH权限不对、配置文件路径写错。多节点环境下这种手工操作很容易出现这台上一步忘了装之类的低级问题。脚本化改造之后整个流程被固化成标准化步骤重复执行稳定可靠机器多了也不会漏环节。另外一个实际业务场景是现在很多公司数据库节点是批量交付的初始化一批新环境时脚本跑一遍和一上午手工敲命令的效率差距不是一星半点。脚本还能保证所有环境配置一致减少开发环境和生产环境配置漂移导致的诡异问题。1.2 MHA故障切换机制与脚本逻辑的对应关系在写脚本之前得先把MHA的工作机制吃透。MHA Manager进程通过周期性探测主库的MySQL连通性来判断主库是否宕机默认ping间隔是3秒连续多次ping失败就触发切换流程。触发切换后MHA会做这几件事确认主库不可用锁定集群进入切换状态。从所有从库中筛选数据最新的节点这个过程会对比各从库的relay log中继日志差异。对候选主库应用缺失的日志使其数据追上原主库最后的写入位置。将候选主库提升为新的主库清理read_only标记。让其他从库重新change master到新主库。如果配置了VIP漂移脚本则执行VIP切换到新主库。脚本部署给出的动作序列需要严格对齐这套机制环境准备对应安装和依赖SSH互信对应MHA各节点之间通信的基础配置对应选主和切换参数VIP脚本对应最后一步的网络切换。想清楚这个对应关系写脚本时就知道每一步是为了支撑哪个环节不会盲目照搬。1.3 脚本化方案的适用边界一个要提前说清楚的点MHA解决的是主库宕机后的故障切换它不等于数据零丢失方案。在异步复制下如果主库突然断电极少数最近提交的事务还没来得及同步到从库切换后这部分数据会丢。如果业务对数据零丢失有强需求需要开半同步复制semi-sync replication配合或者在应用层做双写补偿。我这边给出的脚本默认基于异步复制但会预留半同步的配置开关因为生产环境我遇到过因为半同步超时导致可用性下降的案例这个后面细说。另外MHA不适合跨地域的大延迟场景。如果主从机房距离超过几十毫秒RTTMHA的日志补偿过程会非常耗时切换RTORecovery Time Objective会拉长。脚本部署面向的是同机房或者同城双机房的低延迟内网场景。2. 部署环境准备与主机规划2.1 节点角色与推荐规格一个最小可用环境至少需要三台机器一台主库、一台从库、一台独立的管理节点也可以复用从库部署Manager但生产环境建议独立避免切换时管理进程所在的从库被提升导致服务重叠。下面是我建议的规划表角色MySQL实例MHA组件推荐配置备注主库节点读写实例mha4mysql-node4C8GSSD业务主写入节点从库节点1只读实例mha4mysql-node4C8GSSD切换时的候选主库从库节点2只读实例mha4mysql-node4C8GHDD只读扩展节点管理节点不部署mha4mysql-node manager2C4G故障切换控制点版本搭配上MySQL使用5.7版本PERCONA分支或者官方社区版均可MHA使用目前社区仍在维护的分支。原版MHA从2018年后基本停止更新社区fork版本修复了很多Perl兼容性问题强烈建议不要用老掉牙的0.56原版往新系统上装编译安装时你会被各种依赖折磨到怀疑人生。2.2 MySQL主从复制前置状态检查脚本跑起来之前手工确认几个前置条件避免脚本执行到一半才发现基础环境有问题主库binlog已经开启且格式为ROWbinlog_row_imageFULL。各从库server_id唯一主从复制已正常工作Seconds_Behind_Master为0。MySQL有专门用于MHA的账号权限为对所有库的SELECT、SUPER、REPLICATION SLAVE、REPLICATION CLIENT。各节点之间防火墙放行MHA需要的端口MySQL 3306和Manager通信端口默认22用于SSH。/etc/hosts里所有节点主机名和IP映射一致这个很少有人注意但极其重要MHA内部按主机名通信解析不一致直接报错。提示建议在所有节点上统一使用内网主机名而不是IP来做MHA配置里的标识。后续切换日志里主机名比IP更容易定位问题节点。2.3 依赖包准备各节点都需要安装Perl相关模块CentOS 7系列的机器需要开启epel源否则部分CPAN模块拉不下来。核心依赖包括DBD::mysql、Config::Tiny、Log::Dispatch、File::Basename、Parallel::ForkManager等。这些模块看着不起眼缺一个都可能导致manager起不来或者切换脚本半路报错。脚本里我会分步执行第一步就是安装这些依赖。如果你在一个离线内网环境提前打包Perl模块的RPM或用CPAN镜像源会很省心。别问我为什么强调这个我曾经在一个金融客户的内网环境装MHA因为外网隔离光解决Perl模块依赖就折腾了两个多小时。3. 核心脚本实现与关键函数拆解3.1 脚本整体框架我习惯把部署脚本拆成三级结构主入口脚本、配置模板、功能函数库。这样一个跑批脚本可以多次重复执行还方便维护。主入口负责读取配置文件里的节点信息和部署模式然后按顺序调用函数库里的安装、配置、启动函数。#!/bin/bash # deploy_mha.sh - MHA集群一键部署脚本 set -o errexit set -o nounset set -o pipefail # 加载配置文件 source ./mha_cluster.conf LOG_FILE${LOG_PATH}/deploy_$(date %Y%m%d_%H%M%S).log exec (tee -a ${LOG_FILE}) 21 # 基础检查 function check_precondition() { echo [1/7] 检查基础环境 # 检查各节点SSH连通性 for host in ${ALL_NODES[]}; do ssh -o ConnectTimeout5 -o StrictHostKeyCheckingno ${host} echo ok done # 检查MySQL连通性 for port in ${MYSQL_PORT[]}; do mysqladmin -h ${host} -P ${port} ping done }这个函数的设计逻辑是快速失败如果前置检查发现某个节点SSH不上直接终止后续步骤节省排查时间。3.2 SSH互信配置函数MHA Manager需要在所有节点之间执行SSH操作比如在从库上拉取relay log差异、在候选主库上应用日志、在其余从库上执行change master命令这些动作默认走SSH通道。没有互信的话切换过程中会频繁要求输密码实际上做不到自动化。function setup_ssh_trust() { echo [2/7] 配置SSH互信 if [ ! -f ~/.ssh/id_rsa ]; then ssh-keygen -t rsa -b 2048 -N -f ~/.ssh/id_rsa fi for host in ${ALL_NODES[]}; do ssh-copy-id -i ~/.ssh/id_rsa.pub ${SSH_USER}${host} done }细节提醒部署脚本里我固定使用deployer这个专用系统账号来做SSH互信和MHA管理不建议直接用root。ssh-copy-id第一次连接可能需要输密码这种半自动化交互用expect或者sshpass包处理即可脚本里我会写一个带expect的变体纯交互会让批处理跑不下去。3.3 MHA依赖安装函数MHA的perl模块安装是坑最多的环节特别是DBD::mysql编译时需要mysql客户端库。很多人在这个步骤报错提示找不到mysql_config就是因为系统里没有安装mysql-devel包。function install_dependencies() { echo [3/7] 安装MHA依赖 yum install -y perl-DBD-MySQL perl-Config-Tiny perl-Log-Dispatch perl-Parallel-ForkManager perl-Time-HiRes perl-ExtUtils-MakeMaker # 如果Perl模块版本过旧从cpan源补装 cpan install DBD::mysql }值得注意的是部分系统预装的老版本DBD::mysql只支持老的MySQL认证协议连接MySQL 8.0或5.7的caching_sha2_password账号时会报认证失败。务必升级DBD::mysql到4.050以上版本且编译时指定mysql_config路径确保模块链接到正确的客户端库。3.4 MHA安装包构建与分发MHA分两个包node包需要在所有MySQL节点安装manager包只在管理节点安装。node包的安装本质上只是放一些Perl脚本到系统目录里不需要编译manager包同样也是Perl脚本为主。function install_mha() { echo [4/7] 安装MHA node/manager包 if [ ! -d ${PACKAGE_DIR}/mha4mysql-node ]; then git clone -b master ${MHA_REPO_NODE} ${PACKAGE_DIR}/mha4mysql-node fi cd ${PACKAGE_DIR}/mha4mysql-node perl Makefile.PL make make install # 仅在管理节点执行 if [ ${HOSTNAME} ${MANAGER_HOST} ]; then cd ${PACKAGE_DIR}/mha4mysql-manager perl Makefile.PL make make install fi }这个函数里我用git clone的方式拉取社区维护版代码而不是下载老旧的release tarball。社区维护版修复了在新版本Perl下编译失败和部分脚本不兼容的问题。脚本也兼容本地已有软件包的情况用户可以把rpm或tar包放到指定目录脚本检测到就跳过clone。4. MHA核心配置解析与VIP切换脚本4.1 app1.cn配置文件详解MHA切换的核心配置是一个类似下面这样的自定义配置文件管理节点启动时通过--conf参数指定路径。这个文件内容直接决定了选主策略、日志应用方式和切换行为必须理解每个字段的含义。[server default] manager_workdir/var/log/masterha/app1 manager_log/var/log/masterha/app1/manager.log master_binlog_dir/data/mysql/logs passwordyour_password ping_interval3 remote_workdir/var/log/masterha/app1 repl_userrepl repl_passwordrepl_password secondary_check_script/usr/local/bin/masterha_secondary_check -s ip1 -s ip2 shutdown_script ssh_userdeployer [server1] hostnamedb1 candidate_master1 [server2] hostnamedb2 candidate_master1 check_repl_delay0 [server3] hostnamedb3 no_master1关键字段拆解ping_interval主库健康检查间隔默认3秒如果主库负载很高建议加大到5-10秒否则主库瞬时抖动会频繁触发误切换。candidate_master标记该从库有资格被提升为新主库通常性能较好的从库才设置。check_repl_delay0让MHA在选主时忽略从库复制延迟的检查。这个参数要慎用如果延迟特别大切换后会有明显数据缺口。我一般建议生产环境保持默认1只有在从库性能特别好的情况下才关闭检查。no_master1强制该节点不能被提升为主库比如纯灾备节点或者低配从库。secondary_check_script第二路健康检查通过另一台机器去探测主库状态避免网络分区导致误判。这是一个非常重要的优化项不加它单点网络抖动就可能引发一次完整切换。4.2 VIP切换脚本编写要点MHA的核心切换动作是数据库层面的但业务访问MySQL通常通过VIP或域名。VIP漂移脚本的好坏直接决定了切换RTO。一个典型的master_ip_failover脚本挂在master_ip_failover_script参数上执行逻辑如下#!/usr/bin/env perl use strict; use warnings FATAL all; my $vip 192.168.56.200; my $ssh_start_vip /sbin/ip addr add $vip/24 dev eth0 label eth0:0; my $ssh_stop_vip /sbin/ip addr del $vip/24 dev eth0 label eth0:0; # 在旧主库上删除VIP my $exit_code 0; # 从参数中获取old_ip和new_ip # 在当前新主库上绑定VIP system(ssh $new_host \$ssh_start_vip\);注意几个实操点脚本必须在管理节点上能免密执行因为故障切换时是manager进程通过SSH驱动远程主机执行VIP绑定。VIP漂移除了绑IP还需要考虑ARP广播问题。有些网络环境下VIP漂移后业务端ARP缓存不更新导致连接仍然发往旧主库。解决办法是执行arping -U -I eth0 -c 3 vip广播一下脚本里要把这个动作加进去。如果数据库架构接入了LVS或SLBVIP漂移脚本就不是绑IP而是调用负载均衡接口摘除旧节点、挂载新节点。这种情况下接口调用失败要在脚本里重试三次以上避免切换成功但流量没切过去。4.3 健康检查脚本配置MHA默认的健康检查是从管理节点直连主库的3306端口这种方式在管理节点和主库之间网络中断时会有误判风险。更好的做法是配置多个检查点。我这里的部署脚本生成一个secondary_check脚本从两个不同的从库节点分别去ping主库只有两个从库都连不上主库才判定主库真的挂了。这个设计在真实切换中非常实用实测下来能过滤掉90%以上的网络抖动误触发。5. 实操验证与常见问题排查实录5.1 部署完成后的集群状态自检脚本全部跑完之后不要急着上线先做一次全面的状态自检在管理节点执行masterha_check_ssh --conf/etc/masterha/app1.cn检查SSH互信是否OK。执行masterha_check_repl --conf/etc/masterha/app1.cn检查复制链路是否健康。启动managermasterha_manager --conf/etc/masterha/app1.cn --daemon --remove_dead_master_conf --ignore_last_failover。观察manager.log确认监控进程正常启动没有报连接或认证错误。这一步经常出现的问题是check_repl阶段报Binlog is not empty on db1或者Relative log position 多半是主库的binlog没有被正确清理或者某个从库的relay log存在异常。逐个节点执行show slave status\G查看Exec_Master_Log_Pos确认三个节点的位点一致。5.2 主库宕机切换实测部署脚本交付后我建议至少做一次完整的故障切换演练这比任何配置检查都更能暴露问题。演练方法很直接手工停掉主库MySQL进程观察MHA是否自动完成切换。推荐用下面的命令监控切换过程while true; do mysql -h 192.168.56.200 -u mha_user -p*** -e select hostname, now() 2/dev/null sleep 1 done这个循环每一秒通过VIP连接一次输出当前真实的主机名。切换发生时你能直接看到hostname从db1跳到了db2。我在实测环境里停掉主库后大约10秒完成切换VIP漂移后业务恢复。切换日志的顺序大致是连续ping失败判定主库不可用。获取所有从库的relay log进行差异对比。将db2的relay log补齐到最新位点。在db2上执行stop slave; reset slave all;清除只读提升为主库。在db3上执行新的change master指向db2。执行master_ip_failover脚本VIP漂移到db2。5.3 老大难的relay log不完整问题切换过程中最常碰到的一个报错是relay log is incomplete根源在于部分从库的relay log没同步完全MHA在尝试补齐时发现日志有空洞。排查思路如下检查该从库的relay_log_recovery参数建议打开这样MySQL重启时会自动丢弃不完整的relay log并重新拉取。MHA版本过旧时对relay log的解析经常出问题换到社区维护版分支后问题大幅减少。从库磁盘满了导致relay log写不进去也是常见诱因检查show slave status里的Last_IO_Error和磁盘使用率。5.4 Manager进程意外退出的复盘还有一个典型生产事故manager进程以daemon方式跑着某天后端同学反馈切换没生效查看日志发现manager早在两天前就退出了原因是--ignore_last_failover参数没加。这个参数表示忽略上一次切换记录不加的话MHA会在一次切换后自动退出防止短时间内重复切换。但如果manager因为异常退出重启时发现last_failover记录存在会直接拒绝启动。解决办法是在启动命令里显式加上--ignore_last_failover同时用systemd服务守护manager进程进程退出后自动拉起。脚本里我会生成对应的systemd unit文件[Unit] DescriptionMHA Manager for app1 Afternetwork.target [Service] Typeforking ExecStart/usr/local/bin/masterha_manager --conf/etc/masterha/app1.cn --daemon --ignore_last_failover ExecStop/usr/local/bin/masterha_stop --conf/etc/masterha/app1.cn Restartalways RestartSec10 [Install] WantedBymulti-user.target注意Typeforking要和--daemon配合使用systemd会handle住masterha_manager自己fork出的子进程。Restartalways保证异常退出后10秒自动拉起这样即使manager进程崩溃也不会留下无人值守的空窗期。5.5 部署脚本的幂等性设计运维工具最忌讳跑一半报错修复后从头再来的情况。整个部署脚本我在设计时坚持幂等原则每个函数执行前先判断目标状态安装依赖前检查rpm是否已存在。SSH互信前检查是否已经能免密登录。配置文件写入前备份原文件。service启动前检查进程是否已经在运行。这样脚本可以在任意步骤失败后修复问题再跑不用担心重复执行导致配置错乱。对带状态的数据库节点脚本还专门做了保护如果检测到某个节点已经是主库角色不会误执行从库初始化操作。6. 部署后的调优与扩展经验6.1 切换RTO的优化空间默认配置下一次主库宕机到业务恢复的RTO大概在10-20秒之间主要时间花在relay log对比和日志应用上。如果希望进一步压缩把ping_interval调低到1秒但要注意误判风险会增加配合secondary_check使用才安全。预选候选主库并保证候选主库和主库的数据延迟尽量低。比如在从库上设置sync_binlog1和innodb_flush_log_at_trx_commit1牺牲一点从库写入性能换取切换时的日志差距更小。启用并行复制MTS从库在追赶relay log时速度更快切换时日志补偿耗时明显缩短。我实测过开启并行复制后相同压力下日志追平速度提升3-5倍。6.2 半同步复制与MHA配合的权衡异步复制下MHA存在很小的数据丢失窗口。我前面提到半同步复制这里展开说一下。打开半同步后主库提交事务时至少要等一个从库ACK这个过程中如果从库长时间不响应主库会退化为异步模式。脚本里我封装了一个setup_semisync函数自动在master和candidate slave上安装插件并设置超时时间。生产经验是半同步的rpl_semi_sync_master_timeout不要设太大我习惯设到3000毫秒以内超过就自动降级为异步。否则从库出现性能抖动时主库的写事务会一直阻塞等待ACK整个业务写入链路被拖垮这就本末倒置了。6.3 扩展从MHA平滑演进到其他高可用方案装配了脚本化部署之后你会发现整个高可用体系的核心是资源编排能力。这套脚本稍加改造就能扩展支持其他高可用组件的部署。比如我后来把类似的脚本结构迁移到了服务发现和配置管理上把节点列表放到配置中心实现动态扩容。MHA虽然是老牌方案但它的设计思想很值得借鉴分层监控、多级检查、日志补偿、自动切换、可编排。理解这套思想后无论以后用Orchestrator还是基于Raft的MGR你都能快速上手。脚本部署的价值不只在当下把MHA装好更在于它逼着你去梳理整个系统的依赖关系、故障转移路径和恢复SOP。最后再分享一个小技巧部署脚本跑完后建议把生成的配置文件和日志目录做一次归档标注好版本号和部署日期。后续排障或者做配置审计时翻这些归档比翻聊天记录靠谱得多。生产环境里文档和脚本本身就是高可用的一部分这句话一点都不夸张。
阅读完成 · 觉得有帮助?