☰
MySQL高可用实战:MMM+ATLAS架构搭建与故障演练
2026/10/19 11:26:31 网站建设 项目流程

做数据库的人,最怕半夜接电话。这句话我在不同场合说了不下十遍,但每次都有新的人点头。单机MySQL跑得好好的,突然磁盘报警、进程僵死、机房断电,业务方电话一个接一个打过来,而你只能一边顶着压力一边手工切换主从、改连接配置。说实话,这种经历有过一次,你就再也不想回到那种状态。

所以当要在生产环境搭一套高可用数据库方案时,我第一反应就是MySQL+ATLAS+MMM这套组合。三个组件都不算新:MySQL负责数据存储和主主同步,MMM专职盯实例健康状态、在故障时漂移VIP,ATLAS作为应用入口做读写分离。整套方案解决的核心问题就两个:数据库不因单点故障而整体停摆,数据库的读写压力能被分摊而不是全压在一台实例上。

这篇文章适合两类人。一类是正在做数据库高可用选型的运维或架构师,想搞清楚这套经典组合的搭建细节和适用边界;另一类是刚接触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_agentd
db2MySQL实例2192.168.1.12mysqld + mmm_agentd
monitorMMM监控端192.168.1.13mmm_mond,不装MySQL
proxyATLAS代理192.168.1.14mysql-proxy,应用访问入口
写VIPwriter虚拟IP192.168.1.100跟随主库漂移
读VIPreader虚拟IP192.168.1.101/102按角色分发到可用节点

操作系统我用的是CentOS 7.9,MySQL版本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 = 7

db2的配置除了server-id改成12、auto_increment_offset改成2之外,其他保持一致。这样db1生成的自增ID永远是奇数,db2永远是偶数,互不冲突。binlog_format我建议直接用ROW,因为有些业务表没有主键,STATEMENT格式在双向复制下很容易出现数据不一致,ROW虽然binlog体积大一点,但安全性和可追溯性都好很多。

2.3 复制账号与初始化链路

在db1和db2上都执行同样的建账号操作:

CREATE USER 'replication'@'192.168.1.%' IDENTIFIED BY 'SlavePass@2024'; GRANT REPLICATION SLAVE ON *.* TO 'replication'@'192.168.1.%'; FLUSH PRIVILEGES;

然后在两台实例上分别指向对方,拿db2举例:

CHANGE MASTER TO MASTER_HOST='192.168.1.11', MASTER_USER='replication', MASTER_PASSWORD='SlavePass@2024', MASTER_LOG_FILE='mysql-bin.000001', MASTER_LOG_POS=154; START SLAVE;

这里有一个非常关键的问题:主从复制的起始位点怎么定?如果是两个全新的空实例,直接看master status的File和Position就行。但如果是从已有数据的环境搭建,必须先mysqldump全量导出并记录binlog位点,再用导出文件初始化对端,否则复制链路会从错误的位置开始,出现数据不一致。这个坑我见得太多了,十个人里有三四个栽在这上面。

2.4 复制状态验证

搭建完不要急着往下一层走,先把复制链路验证扎实。用下面命令查看状态:

SHOW SLAVE STATUS\G

重点看三个字段:Slave_IO_Running是否为Yes,Slave_SQL_Running是否为Yes,Seconds_Behind_Master是否为0。IO线程负责拉取binlog,SQL线程负责重放,任何一个不是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角色对应一个写VIP,reader角色对应多个读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 SlavePass@2024 agent_user mmm_agent agent_password MonPass@123 </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_agent'@'192.168.1.%' IDENTIFIED BY 'MonPass@123';

还有一个容易踩的坑是脚本路径。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 = Admin@123 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指向哪台,写就落在哪台。读后端填的是两台物理机的IP,ATLAS会定期检测后端存活,并按负载均衡策略分发读请求。为什么两种后端要区分填写?因为写后端走VIP,MMM切换后VIP自动漂移,代理层面永远不用改配置;读后端直连物理IP,是因为reader VIP的分布本身不固定,让代理自己做负载均衡反而更灵活。这个组合方式我在多个环境验证过,稳定性和可维护性都很好。

4.3 应用账号加密与启动验证

ATLAS不直接在存储层建应用账号,而是在配置文件里通过pwds字段声明。密码需要先加密,ATLAS自带encrypt工具,在bin目录下执行:

cd /usr/local/mysql-proxy/bin ./encrypt -b MyAppPass@2024

工具会输出一串加密后的字符串,把它填进配置文件的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 -pAdmin@123 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,进一步提升读能力。如果对数据一致性要求更高,可以引入半同步复制插件,把切换时的丢数据风险降到最低。如果量级再往上走,就该认真考虑分库分表中间件或者分布式数据库了,这套组合可以做过渡期的稳定底座,但不适合无限堆叠。

最后再分享一个我自己的体会。这套组合最值钱的地方,不是任何一个单独的组件,而是三者配合之后形成的"应用无感知"能力。我见过不少团队花大价钱买商业高可用方案,却连一次故障演练都不敢做;反而这套开源组合,只要把切换链路多练几遍,稳定性是真的扛得住。搭建只是开始,定期演练、持续改进,才是高可用架构真正的底气。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询