MySQL MGR高可用架构详解:从Paxos原理到三节点部署实战 1. MGR到底解决什么问题先聊一个场景。某天下午某公司的机房巡检值班同事发现主库的磁盘报警紧接着副本延迟从几十秒飙到几分钟等你去查的时候已经快追上业务高峰了。更麻烦的是这个节点一旦彻底挂掉只读流量直接断因为半同步虽然有副本但主库故障切换还要靠脚本去提升备库提升过程中写服务是停的。这套流程跑了一年多中间出过两次切换失败一次是因为备库回放落后太多一次是脚本本身有bug两次都折腾了大半个小时才恢复。后来我在这套环境里引入了基于MySQL的组复制也就是常说的MGR。MGR不是一个新的存储引擎它是MySQL官方提供的一种高可用解决方案建立在InnoDB存储引擎之上用Paxos协议把多个MySQL实例组织成一个复制组。这个组里的数据是强一致的任何事务只有在组内多数成员都确认之后才算提交成功谁挂了、谁加进来、组里有多少个节点是健康的这些信息由组自己维护不需要外部脚本再去探测、抢锁、做角色切换。简单说MGR把高可用从主备结构 外部调度变成了组成员自治 内部协商系统自己知道谁是主、谁可以接管天然规避了脑裂问题。适合用在那些希望读写不中断、切换不依赖人工脚本、又不想引入额外中间件的业务场景。对DBA来说MGR需要理解的核心东西不多一条Paxos链路加一套组管理机制剩下的还是那些熟悉的MySQL参数和复制概念。这篇文章就用三节点的例子把MGR从选型、配置、启动到日常运维中踩过的坑过一遍基于常见实践的思路没有平台或版本上的绑定纯数据库层面的实操经验。2. 架构设计核心思路2.1 从异步复制到MGR的演进逻辑传统主从复制是单向的binlog从主库流到从库从库IO线程拉日志、SQL线程回放。这个模型最大的痛点是延迟不可控和角色提升需要人工干预。即便加了半同步也只能保证事务提交时至少有一个备库收到了binlog但备库回放多少、能不能马上接管半同步管不了。MGR在数据复制层面没有推翻binlog那一套底层依然是基于binlog和relay log在流转数据。它真正改变的是谁来做决策这件事。MGR在组内引入了Paxos协议作为共识引擎事务在组内成员之间的传播和确认走的是这条协议而所有成员对组内的成员列表、主节点是谁、哪个节点加入或退出都通过分布式的组编排机制来同步。所以从架构设计的视角看MGR并不是一个全新的数据库它是给传统的MySQL复制加了一个管理壳和一致性协议。理解这一点对所有后续配置都有帮助——你需要关注的参数一部分是为了让MySQL本身能够进入组复制模式比如开启GTID、开启binlog另一部分才是MGR自己的管理参数比如组名、是否引导启动、单主还是多主。2.2 单主模式与多主模式的取舍MGR支持两种模式单主模式即Single Primary Mode多主模式即Multi Primary Mode。生产环境绝大多数情况我会建议用单主。单主模式下整个组只有一个节点可写也就是主节点其他节点只提供读的扩展。这个主节点不是某个配置文件写死的而是组根据成员角色动态选出来的老主退出后剩余成员会自动选出新主。因为这个选举过程在组内部通过协议完成不需要额外的监控程序。多主模式下组内所有节点都可写。听起来很诱人但代价是每个节点都要开启行级冲突检测detect conflicts而且碰到并发更新同一行数据后提交的事务会被回滚。业务端没有做好流量拆分和冲突规避的话很容易一手奶一手毒。从我实际经验看除非业务有明确的分片写入策略否则多主模式只会增加排查问题的复杂度单主模式已经把具备自动切换能力和业务无需改造这两点都满足了。另外一个常见的架构问题是MGR要不要和Proxy组合使用。MGR本身不提供连接层的能力像读写分离、连接复用这类事情需要业务端配置或者引入中间层。在MGR之上套一层对外提供统一入口的中间件应用只感知一个逻辑地址写流量永远打到主节点读流量可以分摊到各个节点这个组合在生产里比让业务自己去感知多节点要实用得多。3. 关键参数和部署前置条件3.1 底层参数为什么这么重要在配置MGR之前有个特别容易忽略的问题MySQL实例当前的初始状态。你的MySQL版本、数据目录里已有的数据、binlog有没有开启都会直接影响MGR能不能顺利启动。MGR要求组内成员的GTID必须完全一致如果只是装了个单机实例再改了配置就想直接加入组大概率会卡在恢复阶段报错。搭建MGR的基本前提包括下面这些条件每一项都需要在配置里明确体现存储引擎必须为InnoDBMGR不支持MyISAM作为组内表的存储引擎开启GTIDgtid_modeON且enforce_gtid_consistencyON开启binloglog_bin参数和log_slave_updates都要配置binlog格式建议使用ROW事件校验和设置为NONE事务隔离级别建议使用READ COMMITTED避免间隙锁引发的不必要冲突组内所有成员的server_id必须唯一且不能为0这些条件看着是基础配置但每一条都对应一个潜在的坑。比如说binlog_checksum如果设置成CRC32部分版本在恢复阶段会报日志校验不一致比如在没有开启GTID的实例上直接做组复制报错日志里会出现一堆关于GTID不一致的提示。这部分往往是最花时间的因为不是MGR本身的问题而是你的MySQL还没有达到可以被组复制接管的状态。3.2 MGR基座参数的解读确认基础条件满足后下面的参数是真正和组复制直接相关的部分。plugin-load-addgroup_replication.so group_replication_group_nameaaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa group_replication_start_on_bootoff group_replication_local_address 192.168.1.1:33061 group_replication_group_seeds 192.168.1.1:33061,192.168.1.2:33061,192.168.1.3:33061 group_replication_bootstrap_groupoffgroup_replication_group_name是组复制的唯一标识格式上必须是一个UUID不能乱写。同一个组里所有成员的组名必须一致。很多人会在这里踩坑——三个节点写三个不同的UUID启动时互相找不到对方报错信息却不会直接告诉你是组名错了只会提示连接失败或者超时。group_replication_local_address是当前成员和其他成员通信时的内部地址格式是IP:端口但这个端口不是说MySQL实例的3306端口而是组复制专用的通信端口。这个端口专门跑Paxos通信和成员间的消息复制必须和数据库服务端口区分开。防火墙只放通了3306漏了33061节点之间在服务层是通的但组复制层永远起不来。group_replication_group_seeds是组内成员的初始接入列表。注意seeds只承担引导加入组时联系其他成员的作用一旦成员成功加入组组内成员名单就会变成动态维护的视图后续加入的新成员不再依赖seeds列表。group_replication_bootstrap_group这个参数极其危险它只应该在一个组第一次创建、且没有任何成员在线时使用。如果每次启动都在所有节点上配置成ON会导致多个节点各自引导出不同的组大家以为在同一个组里实际互相不在一个视图。生产规范里我的要求是引导完第一个节点后立刻把bootstrap_group关闭或者只在单独的命令行里执行引导语句。还有一个重要参数是group_replication_single_primary_mode它在MySQL 5.7.17及以后版本默认值是ON也就是单主模式。如果想用多主需要显式设置成OFF。但前面说了多主意味着更多冲突检测的负荷单主是更稳妥的默认选项。3.3 恢复阶段存储引擎和事务层面的要求很多新人以为MGR只要参数配置对了就行其实还有一个被藏得很深的环节——分布式恢复阶段。当一个节点加入组时它并不是拿着binlog从头开始读而是要先和组内已存在的成员建立连接获取缺失的事务数据。这个过程分为两类数据一类是在线事务日志的增量同步一类是通过状态传输完成全量初始化。这个阶段对存储层面的兼容性有要求。MGR在事务执行时会在组内做冲突检测行数据对应的主键必须存在否则在反向扫描检测阶段会出问题。同时表上必须有主键否则在冲突检测或回放时可能报Table has no primary key之类的错误。虽然某些场景下能容忍无主键表但MGR的运行机制决定了它对无主键表的态度是零容忍的。此外由于分布式恢复阶段会用到多种通道如果之前手动清理过binlog文件或者gtid_purged被设置得不正常会导致新节点无法追平主节点的GTID集合报错里会出现ERROR 1777或类似的GTID缺失提示。这种情况下与其去修补GTID不如让新节点以空实例身份加入组再通过组内成员的备份进行初始化思路会清晰很多。4. 三节点MGR部署实操4.1 环境准备我用三个节点做演示各自配置如下。节点A192.168.1.11端口3306组通信端口33061 节点B192.168.1.12端口3306组通信端口33061 节点C192.168.1.13端口3306组通信端口33061三台机器的MySQL版本保持一致这里用8.0也就是目前生产环境常见的版本MGR在8.0里已经比较稳定很多在5.7里需要额外处理的权限模型问题也得到了改善。操作系统层面需要保证的是节点间网络互通3306用于MySQL服务协议连接33061用于组内成员通信防火墙或者安全组策略里必须显式放行。另外要提醒一下MGR节点之间的网络延迟和稳定性直接决定了组复制的表现。组复制是强一致的每次事务提交都要等待组内成员确认这个确认过程在网络抖动时会被放大成可感知的延迟甚至会触发成员疑似失败和重新选举。部署MGR的网络要求比普通主从要高同一机房内网是底线跨机房部署需要仔细评估延迟对写性能的影响。4.2 MySQL配置文件核心段三台节点的my.cnf核心内容一致只有server_id和report_host不同。[mysqld] server_id11 gtid_modeON enforce_gtid_consistencyON binlog_formatROW binlog_checksumNONE log_binbinlog log_slave_updatesON transaction_write_set_extractionXXHASH64 loose-group_replication_group_nameaaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa loose-group_replication_start_on_bootOFF loose-group_replication_local_address192.168.1.11:33061 loose-group_replication_group_seeds192.168.1.11:33061,192.168.1.12:33061,192.168.1.13:33061 loose-group_replication_bootstrap_groupOFF loose-group_replication_single_primary_modeON loose-group_replication_enforce_update_everywhere_checksON这里解释几个为什么。transaction_write_set_extraction设置成XXHASH64是为MGR的冲突检测服务的它会把每行事务的写集合提取成哈希信息然后和其他成员同步用于判断是否存在写冲突。不设置这个参数启动组复制会直接报错。loose前缀主要是为了在MySQL实例里加载插件但还没有配置完整时相关参数不会被当成未知系统变量导致启动失败这个细节在5.7时代非常有价值8.0里我依然保留它作为一种防御性写法。4.3 创建复制账号MGR成员之间的数据复制和恢复都是通过复制通道来做的。这个复制账号既不能太弱又不能只给最小权限而漏掉恢复阶段所需的能力。常见的授权方式如下。CREATE USER repl% IDENTIFIED BY REPL_PASSWORD; GRANT BACKUP_ADMIN, REPLICATION SLAVE ON *.* TO repl%;注意BACKUP_ADMIN权限在分布式恢复阶段是必须的因为新成员加入时需要请求远程数据并应用传输快照这比传统主从复制要求更高的权限。很多人在这一步只授予REPLICATION SLAVE结果新节点启动组复制时卡在恢复阶段报错提示无法执行某些Binlog相关操作。4.4 插件安装与组创建所有节点都需要加载组复制插件。INSTALL PLUGIN group_replication SONAME group_replication.so;然后依次执行启动流程。先启动节点A并在节点A上执行引导命令。SET GLOBAL group_replication_bootstrap_groupON; START GROUP_REPLICATION; SET GLOBAL group_replication_bootstrap_groupOFF;节点A执行完START GROUP_REPLICATION后它会以第一个成员的身份初始化一个组并且因为组里只有自己一个成员它会给自己分配主节点角色。然后节点B和节点C执行普通的启动命令不需要引导。START GROUP_REPLICATION;这里有一个非常容易踩的坑启动顺序。如果节点B在节点A还没有成功引导时就执行START GROUP_REPLICATION节点B会去尝试连接seeds列表但由于组还没有创建出来连不上或者等待超时。正确流程是必须保证第一个节点彻底完成引导并进入ONLINE状态之后其他节点再逐个加入。4.5 验证组状态通过performance_schema可以查看组复制视图。SELECT * FROM performance_schema.replication_group_members;期望看到三个成员都显示为ONLINE对于单主模式其中有一个成员的MEMBER_ROLE为PRIMARY其余为SECONDARY。再检查一下复制通道状态SELECT channel_name, service_state FROM replication_group_member_stats;这套验证下来正常就可以把业务写流量逐渐切到PRIMARY节点上了。5. 常用运维操作和注意事项5.1 组复制日常维护的正确姿势MGR日常运维中最需要适应的一点是你不能再以传统主从的思路去操作节点比如手动改read_only、手动执行change master。组内角色的切换是自动协商的外部干预反而容易扰乱视图一致性。节点需要重启时正确的步骤是先停业务流量再在该节点上STOP GROUP_REPLICATION然后停止MySQL实例。重启后START GROUP_REPLICATION即可重新加入组。不要直接kill -9除非你想测试副本故障恢复的极限场景。需要移除一个节点时不要直接从组里剔除而是在该节点上执行STOP GROUP_REPLICATION它会先通知组内其他成员自己正在离开组内视图会自动更新。如果节点故障到已经无法通信其他成员会在一定时间后通过多数派机制把故障节点从视图里移除这是MGR自动完成的。5.2 成员加回和数据追平分布式恢复机制允许节点在短暂离线后重新追平数据。假设节点B因为断电离线恢复后启动MySQL再执行START GROUP_REPLICATION它会通过组内通信通道从其他成员获取离线期间缺失的事务日志并回放。只要离线期间的binlog没有被清理掉就能顺利追平。如果离线太久其他节点的binlog已经被清理节点B就需要重新全量初始化。全量初始化的思路是选择一个在线成员用备份工具备份后恢复到离线节点上恢复时注意保留GTID信息然后重新启动组复制。干净的备份和明确的GTID范围是MGR处理长时间离线节点回来的最好方式。5.3 MGR和备份策略的组合MGR节点和传统主从一样需要备份而且备份建议打到SECONDARY节点上避免主节点在承担业务写入的同时还要承受备份带来的额外I/O压力。比较实用的方式是选一个SECONDARY节点用开源的备份工具基于物理备份方式做全量备份这个备份可以用于任意节点的恢复也可以用于长时间离线节点的追平。恢复时的要点是要保留备份中的GTID执行信息MGR节点重新加入组时依赖这个集合判断自己缺哪些事务。如果不保留或丢失了GTID节点恢复后会出现一种很尴尬的状态数据文件可以正常启动但组复制一直认为这个成员的GTID集合和组内不一致拒绝让它进入ONLINE状态。5.4 监控指标关注清单MGR的监控和传统主从有很大区别。传统主从主要看seconds_behind_masterMGR则看这些指标说明关注原因MEMBER_STATE成员是否ONLINE一旦变为RECOVERING或ERROR需要立即介入MEMBER_ROLE是主还是备单主模式下必须只有一个主group_replication_primary_member当前主节点切换后业务连接感知需要更新count of transactions committed组内提交事务数各节点差异过大说明回放有问题flow control状态是否被流控流控抑制会导致慢查询和写入超时MGR在很多环节会出现流控比如某个节点严重落后时组会限制新事务提交来让落后节点追平。这个设计是保证一致性的但很多人会遇到写入变慢不是因为数据库慢而是被流控压制了。排查时需要看是否有成员状态异常。6. MGR常见问题排查实录6.1 启动组复制时报错Cant start group replication这个报错是范围最大、原因最多的一类。我遇到过的原因有下面几种排查时可以按顺序走插件没有正确加载确认INSTALL PLUGIN已经执行配置了binlog_checksumCRC32改回NONE再重启实例没有开启GTID检查gtid_mode和enforce_gtid_consistencyserver_id为0或者重复本机数据目录里已经有非GTID环境遗留的旧事务数据排查时可以查看MySQL错误日志或通过SHOW GROUP_REPLICATION STATUS相关字段定位到具体阶段。大部分情况在日志里的提示都比较明确只是很多人懒得看日志。6.2 节点状态一直在RECOVERING这是MGR部署中最常见的卡壳环节。节点加入组后需要经历分布式恢复如果恢复异常或失败就会一直显示RECOVERING。第一步看错误日志常见的是无法连接复制源、复制账号没有权限、GTID缺失。第二步检查各节点之间的33061端口连通性。第三步确认节点B的复制账号是否和主节点一致——不同的MGR节点实际上用的是同一个复制账号连接彼此授权必须一致。还有一个隐藏较深的情况新节点原本是一个独立运行的MySQL实例数据目录里残留着和组内完全无关的事务此时就算GTID已经开启也无法和组内的GTID集合对齐会一直处于RECOVERING直到选择重新初始化。6.3 网络抖动引发的成员自动退出MGR通过组内成员间的通信消息来判断其他节点是否存活。如果某个节点在设定的时间内没有响应会被其他成员标记为可疑进而从组成员视图中移除。注意这个判定基于的是Paxos协议的通讯超时和MySQL层面的连接超时不是一回事。遇到成员自动退出的情况先不要急着拉起节点先检查这段时间网络是否有丢包、CPU是否被打满、磁盘I/O是否有尖刺。MGR对网络和硬件资源的敏感程度远高于普通主从生产环境里我见过不少次因为网络瞬时抖动导致成员被误判退出最后靠STOP和START组复制操作让节点重新加回。6.4 单主模式下切换到新主后业务连不上单主模式的主节点是动态选举的。老主故障后新的主节点会被选出来。如果业务端连接的是固定IP此时就会出现连不上的情况因为连接串指向的还是老主。这不是MGR的问题而是连接层配套问题。解决办法是在前面放一个软路由、域名切换或代理中间层让业务永远指向一个逻辑地址。MGR负责底层的数据一致性和角色切换外部这一层负责让业务感知不到切换。把这两层混淆就会误以为MGR没有做好高可用。6.5 常见问题速查表现象可能原因处理方式启动报错无法加载插件插件未安装或路径不对检查INSTALL PLUGIN状态RECOVERING复制账号权限不足或GTID不匹配核对复制账号、重置GTID成员反复加入退出网络抖动或通信端口阻塞放行组通信端口、检查网络写入变慢流控触发或节点落后查看流控状态、定位落后节点事务被杀多主模式冲突检测单主模式下一般不会发生多主需检查业务并发冲突无法引导组组名不一致或bootstrap未关闭统一UUID、按顺序引导7. 实操体会和一点扩展建议MGR上手最困难的地方不在配置项多而在于需要转变一个观念它不再是一个手动喊口令的高可用方案而是一个自带共识协议的自治系统。适应这一点后日常运维会轻松很多。我个人的习惯是把MGR当成一个带自愈能力的同步复制集群来用它不适合去解决所有问题。比如它不能帮你解决SQL慢查询不能挽救因为业务设计缺陷造成的死锁风暴也不会自动处理无主键大表的性能灾难。它是数据高可用层面的底座不是业务性能的救世主。再分享一个小技巧在MGR的标准配置里加一条监控规则重点盯MEMBER_STATE和流控状态。具体来说在监控系统里对所有节点做指标采集发现非ONLINE状态持续超过30秒就告警流控超过50%持续5分钟也告警。这样做看起来简单实际上能规避掉大多数成员已离线但没人注意的情况。从长期维护角度看后续如果业务增长导致某个SECONDARY节点的读压力过大可以考虑给该节点额外增加传统的异步从库把只读流量继续往下甩。MGR成员和它下挂的异步从库共存没有原则性冲突但要注意下挂从库的binlog配置保留完整以便从库和MGR成员之间的延迟可控。这个方向如果是刚从主从复制迁移过来建议小范围试点几个业务再逐步铺开。MGR本身的调试周期通常不会超过一天真正需要时间的是让团队习惯它的运维方式。把三节点、单主、自动切换这套标配跑稳了再考虑多主或者跨机房扩展就会顺很多。