MySQL高可用实战:MMM+ATLAS架构搭建与故障演练

📅 发布时间:2026/10/19 11:26:36
MySQL高可用实战:MMM+ATLAS架构搭建与故障演练
做数据库的人最怕半夜接电话。这句话我在不同场合说了不下十遍但每次都有新的人点头。单机MySQL跑得好好的突然磁盘报警、进程僵死、机房断电业务方电话一个接一个打过来而你只能一边顶着压力一边手工切换主从、改连接配置。说实话这种经历有过一次你就再也不想回到那种状态。所以当要在生产环境搭一套高可用数据库方案时我第一反应就是MySQLATLASMMM这套组合。三个组件都不算新MySQL负责数据存储和主主同步MMM专职盯实例健康状态、在故障时漂移VIPATLAS作为应用入口做读写分离。整套方案解决的核心问题就两个数据库不因单点故障而整体停摆数据库的读写压力能被分摊而不是全压在一台实例上。这篇文章适合两类人。一类是正在做数据库高可用选型的运维或架构师想搞清楚这套经典组合的搭建细节和适用边界另一类是刚接触MySQL高可用没多久的开发者想通过一个完整的实战案例把这些概念串起来。我会按实际部署的顺序写从原理、配置到故障演练把我踩过的坑也一并倒出来。1. 整体设计三个组件怎么分工配合1.1 没有这套架构之前运维在经历什么单机数据库的问题不需要多讲服务器磁盘挂掉、内存故障、系统崩溃、机房断电任何一个环节出问题数据库就断了。主从复制能解决一部分读压力但主库挂了以后DBA需要人工把从库提升为主库改应用连接配置再把服务拉起来。这个过程少则十几分钟多则一两个小时而且半夜处理极易出错。即使加了主从复制新的问题也会冒出来。主从延迟导致读到的数据是旧的主库压力大但从库闲着VIP切换需要运维手动执行。这些问题的本质是复制解决了数据冗余但没有解决访问入口的动态切换和读写流量的合理分配。于是就需要更高一层的组件来统一调度。这套架构把数据库高可用这件事拆成了三层来解决。MySQL主主复制保证两台实例互为主从数据实时双向同步任何一台挂掉另一台都有完整数据MMM专门监控实例和复制状态一旦主库失联自动把写VIP漂移到存活的主库上ATLAS作为应用访问数据库的统一入口把读请求分散到多个节点写请求固定打到主节点后端拓扑怎么变应用完全无感知。1.2 为什么选MMM而不是MHA很多朋友会问MHA不是更主流吗MHA确实不错能做到秒级的主从切换但它只做故障转移不做VIP管理通常要配合LVS或者Keepalived才能实现对外服务的无缝切换链路更长、要维护的东西更多。MMM的方案自带VIP漂移机制写VIP只有一个读VIP按角色分发配合ATLAS这种代理非常自然。另外MMM的架构是监控端代理端分离的。监控端独立部署代理端跑在每台MySQL机器上通过心跳和脚本检查掌握整个集群的状态。这种设计让故障判断不依赖MySQL自身——哪怕数据库进程已经完全僵死监控端也能感知到并触发切换。这一点在我后面的演练里会体现得很明显。1.3 这套方案的适用边界说实话任何方案都有适用边界这套组合也不例外。我看过有人硬把它套在超大业务上结果并不理想。比较适合它的场景是读多写少、数据量在单机可承受范围内、对切换时间要求不是极致的业务。MMM的故障检测周期默认在5秒左右加上脚本执行和VIP漂移时间整体对外不可用的窗口大约是10到30秒。大多数业务能接受这个范围但如果你连几秒钟抖动都扛不住建议考虑MHA配合半同步复制或者直接上分布式数据库。这里还要说清楚一点MMM默认跑的是异步复制在主库宕机的极端情况下最后一批没来得及同步的事务可能会丢。它保证的是可用性不是零丢失。想要数据零丢失就得引入半同步复制或者组复制那是另一个层面的方案了。我在搭建前一定要和业务方确认这个容忍度免得事后被追责。2. 数据层搭建MySQL主主复制的细节2.1 服务器拓扑与网络规划我习惯用4台机器来部署这套架构角色分配如下表。注意所有节点最好都在同一个二层网络里因为VIP漂移依赖ARP协议通告跨网段会让实现复杂很多。节点角色IP部署内容db1MySQL实例1192.168.1.11mysqld mmm_agentddb2MySQL实例2192.168.1.12mysqld mmm_agentdmonitorMMM监控端192.168.1.13mmm_mond不装MySQLproxyATLAS代理192.168.1.14mysql-proxy应用访问入口写VIPwriter虚拟IP192.168.1.100跟随主库漂移读VIPreader虚拟IP192.168.1.101/102按角色分发到可用节点操作系统我用的是CentOS 7.9MySQL版本5.7系列。这套组合在MySQL 8.0上也能跑但5.7的资料和踩坑经验最丰富生产环境求稳的话5.7是更务实的选择。每台机器的防火墙规则要提前放行MySQL 3306端口、MMM代理端9988端口、ATLAS的3307和2345端口别等到切换时才发现包进不来。2.2 主主复制的核心参数主主复制的本质是两台实例各自开启binlog各自把对方的binlog拉过来重放到本地。听起来简单但有几个必须注意的点。第一两台实例的server-id不能一样否则复制链路建立不起来。第二必须开启log_slave_updates因为每台实例在作为对方从库重放binlog后还要把变更再写一份到自己的binlog里不然另一台拉不到自己产生的数据变更。第三也是最容易忽略的自增字段的冲突问题。两个主库都可能写入数据如果不做处理自增主键会撞车。解决办法是让两台实例错开自增步长。db1的配置如下[mysqld] server-id 11 log-bin mysql-bin binlog_format ROW log_slave_updates ON auto_increment_offset 1 auto_increment_increment 2 expire_logs_days 7db2的配置除了server-id改成12、auto_increment_offset改成2之外其他保持一致。这样db1生成的自增ID永远是奇数db2永远是偶数互不冲突。binlog_format我建议直接用ROW因为有些业务表没有主键STATEMENT格式在双向复制下很容易出现数据不一致ROW虽然binlog体积大一点但安全性和可追溯性都好很多。2.3 复制账号与初始化链路在db1和db2上都执行同样的建账号操作CREATE USER replication192.168.1.% IDENTIFIED BY SlavePass2024; GRANT REPLICATION SLAVE ON *.* TO replication192.168.1.%; FLUSH PRIVILEGES;然后在两台实例上分别指向对方拿db2举例CHANGE MASTER TO MASTER_HOST192.168.1.11, MASTER_USERreplication, MASTER_PASSWORDSlavePass2024, MASTER_LOG_FILEmysql-bin.000001, MASTER_LOG_POS154; START SLAVE;这里有一个非常关键的问题主从复制的起始位点怎么定如果是两个全新的空实例直接看master status的File和Position就行。但如果是从已有数据的环境搭建必须先mysqldump全量导出并记录binlog位点再用导出文件初始化对端否则复制链路会从错误的位置开始出现数据不一致。这个坑我见得太多了十个人里有三四个栽在这上面。2.4 复制状态验证搭建完不要急着往下一层走先把复制链路验证扎实。用下面命令查看状态SHOW SLAVE STATUS\G重点看三个字段Slave_IO_Running是否为YesSlave_SQL_Running是否为YesSeconds_Behind_Master是否为0。IO线程负责拉取binlogSQL线程负责重放任何一个不是Yes都说明链路有问题。我搭完之后习惯做一个冒烟测试在db1上建一个test库、一张t_user表插三条数据到db2上查确认数据在然后在db2上更新一条回db1上查确认也同步过来了。双向都能通才说明主主复制真正建立起来了。顺便还能验证一下刚才的自增配置——连续插两条数据看ID是不是按奇偶规律分配。3. 高可用管理层MMM安装与配置3.1 MMM的运行机制MMM由两个守护进程组成。mmm_mon是监控守护进程跑在独立的监控机上负责对每个MySQL节点做健康检查、复制状态检查并决定VIP的归属。mmm_agent是代理守护进程跑在每台MySQL节点上接收监控端的指令执行VIP绑定和解绑、MySQL启停等实际操作。监控端和代理端通过TCP端口9988通信。监控端做检查时调用的是MMM自带的脚本一个是check_mysql_online本质上是ping MySQL另一个是check_mysql_replication检查复制状态和延迟。MMM维护了一组角色writer角色对应一个写VIPreader角色对应多个读VIP。正常状态下writer VIP绑定在其中一台主库上reader VIP按配置分发到各台可用节点。故障发生时监控端会先把故障机上的VIP全部摘掉再把writer VIP绑定到另一台还活着的主库上。3.2 mmm_common.conf核心配置MMM的公共配置文件是mmm_common.conf所有节点共用。下面是我的一个生产环境实例active_master_role writer host default cluster_interface eth0 pid_path /var/run/mmm_agentd.pid bin_path /usr/lib/mysql-mmm/scripts/ replication_user replication replication_password SlavePass2024 agent_user mmm_agent agent_password MonPass123 /host host db1 ip 192.168.1.11 mode master peer db2 /host host db2 ip 192.168.1.12 mode master peer db1 /host role writer ips 192.168.1.100 mode exclusive /role role reader ips 192.168.1.101,192.168.1.102 mode balanced /role几个参数值得展开说。active_master_role指定当前写角色是谁MMM只会把这个角色对应的VIP放在主库上。host定义里mode master和peer db2是成对出现的MMM据此知道这是一对主主关系。writer角色的mode是exclusive表示这个VIP同一时刻只能绑定在一台机器上reader角色的mode是balanced读VIP会在可用节点之间分发实现读流量的负载均衡。这里要提醒一下MMM的设计思路是一主一备的语义默认只允许一个writer角色虽然两台主库都在接收复制但同一时刻只有一台对外提供写服务。一定要和业务方讲清楚别让他们以为双主可以同时写。双主同时写入需要更复杂的冲突控制机制MMM不负责这个。3.3 监控端与代理端配置mmm_mon.conf放在监控机上内容如下monitor_pid_path /var/run/mmm_mond.pid monitor_bin_path /usr/lib/mysql-mmm/monitor/ monitor_interface eth0 mmm_agent_offline_timeout 5 ping_hosts 192.168.1.11,192.168.1.12,192.168.1.13,192.168.1.14 auto_set_online 60每台MySQL节点上的mmm_agent.conf则只需要两行以db1为例include mmm_common.conf this db1配置完成后分别启动服务MySQL节点上启动mmm_agentd监控机上启动mmm_mond。启动完用mmm_control show查看集群状态正常的话应该看到db1和db2都处于ONLINE状态writer VIP绑定在其中一台reader VIP分配明确。3.4 账号权限和脚本路径问题MMM不像其他工具那样自带账号初始化脚本你需要手动在MySQL里建mmm_agent用户并授权不然监控端和代理端通信没问题但后续执行检查时权限会不够。我的授权语句如下GRANT PROCESS, SUPER, REPLICATION CLIENT ON *.* TO mmm_agent192.168.1.% IDENTIFIED BY MonPass123;还有一个容易踩的坑是脚本路径。MMM的check脚本路径在编译时可能被写死如果你用自定义安装路径编译参数必须加对否则启动监控端时会报找不到脚本。我建议编译前先确认好目标路径统一规划不要装完再到处挪。4. 接入层代理ATLAS读写分离部署4.1 ATLAS在网络拓扑中的位置ATLAS的本质是一个基于MySQL协议的反向代理它伪装成MySQL服务端接收应用的连接请求然后把SQL转发到后端真实的MySQL实例上。它的设计目标很明确让应用层无感知地完成读写分离。部署位置上我推荐独立部署一台代理机所有应用统一连接方便管理和扩容。ATLAS本身没有状态可以横向扩展前面挂LVS或者DNS轮询都没问题。我见过有人把ATLAS和应用部署在同一批机器上省了一两台机器但后续维护时你要在每台应用机器上调整配置反而更麻烦。4.2 配置文件的几个关键参数ATLAS解压到/usr/local/mysql-proxy后主配置写在conf/mysql-proxy.cnf。我用过的一份生产配置如下[mysql-proxy] admin-username admin admin-password Admin123 proxy-backend-addresses 192.168.1.100 proxy-read-only-backend-addresses 192.168.1.11,192.168.1.12 proxy-address 0.0.0.0:3307 admin-address 0.0.0.0:2345 daemon true keepalive true event-threads 4 log-level message log-path /usr/local/mysql-proxy/log sql-log ON这里有一个选型细节我想单独说明。写后端proxy-backend-addresses填的是MMM维护的写VIP 192.168.1.100写流量永远走这个地址VIP指向哪台写就落在哪台。读后端填的是两台物理机的IPATLAS会定期检测后端存活并按负载均衡策略分发读请求。为什么两种后端要区分填写因为写后端走VIPMMM切换后VIP自动漂移代理层面永远不用改配置读后端直连物理IP是因为reader VIP的分布本身不固定让代理自己做负载均衡反而更灵活。这个组合方式我在多个环境验证过稳定性和可维护性都很好。4.3 应用账号加密与启动验证ATLAS不直接在存储层建应用账号而是在配置文件里通过pwds字段声明。密码需要先加密ATLAS自带encrypt工具在bin目录下执行cd /usr/local/mysql-proxy/bin ./encrypt -b MyAppPass2024工具会输出一串加密后的字符串把它填进配置文件的pwds字段pwds appuser:3yb5jEku5k4多个应用账号用逗号分隔。首次配置完记得检查文件权限不要把明文密码留在可以被任意用户读取的地方。然后启动代理/usr/local/mysql-proxy/bin/mysql-proxy --defaults-file/usr/local/mysql-proxy/conf/mysql-proxy.cnf用mysql客户端验证代理是否工作mysql -h192.168.1.14 -P3307 -uappuser -p能正常执行SQL说明代理转发正常。接着验证读写分离是否生效最简单的办法是先在主库上执行SHOW PROCESSLIST观察连接来源再让应用发几个查询看连接是落在db1还是db2。4.4 管理接口动态操作后端ATLAS的管理端口支持动态操作不需要重启代理就能调整后端状态。连接管理口mysql -h192.168.1.14 -P2345 -uadmin -pAdmin123 SELECT * FROM backends;执行后能看到每台后端实例的地址、类型和状态。如果某台后端需要下线维护可以执行SET OFFLINE后面跟backends表里对应的序号维护完成后SET ONLINE再拉回来。每次变更后建议重新SELECT一次确认状态符合预期再离开。实测下来当MMM完成故障切换后写VIP已经指向新的主库ATLAS不需要重启就能继续转发写请求。但如果代理里缓存了旧连接短时间可能会有报错这需要应用端的连接池配置自动重连。所以部署ATLAS时一定要和开发确认连接池参数比如连接超时时间、重试次数这些细节决定了切换那一刻应用会不会跟着出问题。5. 故障演练与常见问题排查5.1 主库宕机切换演练实录任何高可用方案不演练等于没有。我每次搭完这套架构都会做一次完整的主库宕机演练。下面是db1宕机的全过程记录。第一步在db1上执行systemctl stop mysqld模拟最常见的主库宕机。第二步大约10到15秒后监控端日志出现db1状态变更的记录mmm_control show输出里db1变为OFFLINE。第三步登录db2查看IP会发现192.168.1.100这个写VIP已经绑定上来。第四步应用通过ATLAS继续写入数据验证写入实际落在db2。演练中我发现的一个典型现象是ATLAS的写连接池里还有部分指到旧后端的连接MySQL断开后这些连接会失效客户端重连后恢复正常。如果应用层没有设置自动重连会有一小部分请求报错。所以故障切换不只是数据库层的事应用层的连接池策略必须同步到位。5.2 故障恢复的正确流程主库恢复不能直接把mysqld拉起来就完事要按照正确顺序把db1加回复制链路。首先确认db1上的mysqld进程恢复然后查看当前binlog位点在db1上执行START SLAVE让它从db2追数据。等到Seconds_Behind_Master变成0再用mmm_control set_online db1把它加回集群。恢复过程最忌讳的是两个主库同时写入。主主环境下必须保证同一时间只有一个写角色接收写请求MMM切换后原主库恢复上线时它的写入状态是暂停的应该让它先在slave模式下追平数据再通过set_online重新加入。如果操作顺序反了两个库各写各的数据立刻分叉后面合并恢复的成本会高到你怀疑人生。5.3 常见问题速查表下面这张表是我这些年排查问题的经验汇总按出现频率排序可以直接当作运维手册用。现象可能原因处理方式mmm_mon启动失败Perl依赖缺失或路径写错按依赖清单补齐模块检查monitor_bin_path主从IO线程为No网络不通或复制账号错误ping测试重新执行change master主从SQL线程为No中继日志中的SQL执行失败查看last_error修复数据或跳过该事务复制延迟持续增大大事务或慢查询优化SQL适当调整并行复制参数ATLAS连接被拒绝代理进程未启动或防火墙拦截检查进程、监听端口、防火墙规则写VIP不漂移监控端和代理端通信异常检查9988端口连通性确认agent在线切换后应用报错连接池未重连或IP白名单未放行配置连接池重试检查新主的访问控制5.4 那些让我折腾到深夜的坑第一个坑是双主配置里的自增参数方向写反。db1和db2的auto_increment_offset必须一个为1一个为2配套auto_increment_increment都设为2。有人图省事把两台都设成默认值结果高并发写入时主键冲突复制链路直接断掉。排查这种问题要同时看两个库的错误日志非常费神。第二个坑是监控机和代理机的时间不同步。MMM判断节点是否在线依赖心跳超时如果机器间时间差太大会出现误判——节点明明是好的却被标记为OFFLINE触发不必要的切换。所以整个集群必须统一用NTP或者chrony做时间同步这不是可选项是必选项。第三个坑是ATLAS后端地址填错。有人把写后端填成了物理IP而不是VIP导致MMM切换完成后应用发起的写请求仍然打到已经故障的原主库上。这个问题的隐蔽性在于平时一切正常根本察觉不到只有故障切换时才会暴露。我的建议是搭好之后专门做一次改错后端地址再恢复的试验确保自己清楚每个配置项的含义。第四个坑是测试环境永远只kill mysqld进程不做整机断电演练。VIP漂移依赖ARP协议如果交换机层面的二层网络不通VIP怎么飘都是白搭。我后来养成的习惯是每个季度至少做一次整机级别的断电演练而且要在业务低峰期做让整个链路真正经受考验。6. 监控运维与架构演进建议6.1 外部监控和告警怎么补MMM自身只提供状态查看命令和日志不会主动发通知。生产环境必须叠加外部监控我按三层来补。进程层监控MySQL进程、mmm_agentd、mmm_mond、mysql-proxy是否存活数据层监控主从复制状态、Seconds_Behind_Master、VIP绑定状态链路层监控应用连接成功率、ATLAS日志里的错误SQL。我的做法是写一个shell脚本每分钟执行一次mmm_control show解析输出并和预期状态对比发现问题就推送到值班群。这个脚本不复杂但价值很高因为很多线上故障并不是瞬间发生的复制延迟、VIP异常往往提前就有苗头只是没人盯着。6.2 备份与高可用是两件事这套架构解决的是可用性问题不是数据安全问题。万一发生误删除、逻辑错误主从会一起遭殃高可用集群也救不了你。所以我一直强调备份链路必须独立存在每天凌晨全量备份binlog定期归档并保留至少7天备份文件异地留存绝不能和数据库放在同一块磁盘上。我第一次做恢复演练时发现备份文件竟然存在本地磁盘演练过程中把数据目录搞坏了备份也跟着没了那一刻真是后背发凉。从那以后备份文件一律同步到独立的存储节点并且每个季度做一次真实的恢复验证确认备份真能还原而不是只看到备份任务显示成功。6.3 业务增长后的演进方向如果业务继续增长这套架构有几个自然的演进方向。ATLAS前面可以再加一层负载均衡做成多代理集群消除代理单点。后端MySQL可以从两台扩展到多主多从由MMM管理多个读VIP进一步提升读能力。如果对数据一致性要求更高可以引入半同步复制插件把切换时的丢数据风险降到最低。如果量级再往上走就该认真考虑分库分表中间件或者分布式数据库了这套组合可以做过渡期的稳定底座但不适合无限堆叠。最后再分享一个我自己的体会。这套组合最值钱的地方不是任何一个单独的组件而是三者配合之后形成的应用无感知能力。我见过不少团队花大价钱买商业高可用方案却连一次故障演练都不敢做反而这套开源组合只要把切换链路多练几遍稳定性是真的扛得住。搭建只是开始定期演练、持续改进才是高可用架构真正的底气。