要不要先讲个段子。你见过那种跑着主从复制,凌晨三点主库磁盘突然塞满,把数据库进程整个卡死,然后因为备用库的IO线程连着重连多次,从库也直接跪了的场景吗?我见过不止一次。传统主从复制最大的问题不是“复制不了”,而是“出了事得人醒着”——要么靠半同步加一套自己写的脚本强撑着,要么靠MHA这种老将硬撑。而MySQL Group Replication(MGR)之所以被越来越多人搬进生产环境,就是因为它把“故障检测、选主、故障转移”这件事从人工脚本升级成了组内成员的集体决策。它不是一个人说了算,而是大家投票、协商、推进共识,最后自动选出一个新的主节点继续干活。这篇文章我从机制原理讲到实操参数,再讲坑,从头到尾把MGR这套故障处理链路拆开揉碎,适合被传统主从“坑”过、又不想频繁熬夜的DBA,也适合做高可用方案选型的架构师。
1. MGR到底是什么:它解决的不是复制,是“治理”
1.1 为什么传统主从复制不够用
传统主从复制就像一条单行线:主库写binlog,从库拉binlog,然后串行应用。这套方案最成熟、最普及,但它的致命弱点在于“谁做主”的问题没有自动化的共识机制。主库挂了,从库不会主动跳出来说“我来干”,它们只会一直在那边傻傻重连,等着有人手动执行change master。就算接了MHA、Orchestrator这类调度器,本质上也还是“外部agent”去读心跳、做判断——agent自己也是单点,挂了照样没人管。
另一个痛点是一致性。传统异步复制下,主库提交成功不代表从库一定拿到了日志,半同步也只是尽量保证“日志到了”而不是“日志一定被应用了”。一旦故障转移发生,新主如果缺了最后一段日志,丢数据几乎是必然的。这也是很多团队不敢轻易做自动切换的原因:不是不会切,是怕切完了对不上账。
MGR走的是另一条路线。它把多个MySQL实例组织成一个“组”,组内成员通过专门的组通信系统分布式地交换消息,事务提交的排序、成员的进出、故障的感知,全部由这个通信层用共识算法推进。它不是“主从”关系,更像是“一个组里大家都有数据,写的时候按照身份来做权限区分”。这样无论是故障检测还是选主,都不再依赖外部脚本,组内自己就能闭环。
1.2 MGR的架构基石:Paxos与组通信
MGR内部最核心的两个关键词:组通信引擎和Paxos。组通信引擎负责组成员之间的消息可靠投递,每一个节点都保存全局的事务顺序号(global transaction identifier),而Paxos保证整个组对事务顺序、成员状态变更的认定是一致的。你可以把Paxos理解成“表决器”:每个成员都有发言权,但只有大多数成员认可的结论才能生效。
具体到事务层面,单主模式下客户端只能往primary写,primary产生的事务被复制到组里其他成员;多主模式下每个成员都能写,但每个事务在提交前都要经过冲突认证,由组通信引擎分配一个全局有序的序列号,保证所有节点最终以相同的顺序应用事务。如果两个节点同时修改了同一行数据,认证阶段就会把后到达的节点判“输”,直接回滚该事务,从而避免数据分叉。
这个设计解决了一个老问题:数据合法性不再依赖“某台机器的自觉”,而是由组内多数成员共同背书。某个成员就算出现瞬时脑裂或者日志应用滞后,也影响不了整个组的安全判断。
1.3 单主与多主:两种模式怎么选
MGR有两种运行模式:single-primary和multi-primary。单主模式就是组内只有一个primary节点,其余都是secondary,平时只能读,不能写;只有在primary故障或主动退出时,组内才会自动选举出新的primary。多主模式则允许每个成员都接受写请求,但正如前面说的,多主会带来事务冲突回滚的风险,对业务侧的要求更高。
我平时给团队的建议是:能上单主就尽量单主。多主模式虽然听起来更“高级”,但冲突检测、死锁处理、HAProxy/应用层读写路由这些麻烦事并不少。而且从故障转移角度看,单主模式的逻辑更清晰——旧主退位,新主补位,其他成员自动改变自己的角色和事务所有权状态。MGR的角色变化是组内自动完成的,应用层只需要能够重新建立到新主的连接即可。
2. 故障检测机制:如何在不靠谱的网络里判断节点挂了
2.1 分布式故障检测原理:心跳、怀疑与确认
MGR的故障检测不是靠“拉一把”那种中心化探活,而是组内每个成员都会定期向其他成员发送心跳消息,同时监听其他成员的消息。这个机制来源于分布式系统里的“应付故障检测器”(failure detector)。每个节点都会维护一套本地对“其他成员活没活着”的估计,并持续更新。
当某个成员在预期时间内没有收到另一个成员的任何消息时,它会把这个成员标记为“怀疑故障”。注意,是“怀疑”,不是“立刻驱逐”。组通信层会经过多轮确认,避免因为一次网络抖动就误杀节点。等到检测器最终确定“这个节点已经失联”,发起驱逐动作时,组内才会发布一个新的“视图”(view),把故障节点从成员列表中排除掉。这个视图变更信息同样要经过Paxos协议,等大多数成员确认后,新视图才生效。
这套机制最大的好处是避免了误判,但代价是对网络比较敏感。比如交换机短暂丢包、GC停顿时间长、负载过高导致心跳处理延迟,都有可能在默认参数下触发驱逐。生产经验里,MGR所在的网络必须稳定,最好独立VLAN,不要和业务流量抢带宽。把故障检测超时调大一点,也是常规操作。
2.2 驱逐与自动重连的关键参数
这里必须提一个非常实用的参数:group_replication_member_expel_timeout。默认值是0,即检测确认后立刻驱逐。如果网络环境有瞬断、重启服务的场景,我建议把它设置为5到10秒,这样当节点疑似故障时,会给它一个“宽限期”,等它重新恢复通信。值得强调的是,这个参数必须在节点启动组复制之前配置,运行中直接set global不会立即生效,需要重启组复制通道。
另一个8.0.16+才有的参数是group_replication_autorejoin_tries。它控制节点在意外退出后,是否自动重新加入组、重试几次。默认是3次,但如果你把节点配置成“被驱逐后自动重试”,一定要确认应用层有重连机制,防止它还没加回组、旧连接就已经被客户端反复超时打爆。我把这两个参数搭配使用,生产上很少再遇到“半夜网络抖动,三个节点全部离组变ERROR”那种经典事故。
2.3 网络分区与脑裂:MGR凭什么不会双主
很多人问我:如果机房网络断开,两个分区各剩一半节点,MGR不会脑裂吗?答案是不会,原因就是多数派原则。
假设一个三节点组。网络分区后,一边有一个节点,另一边有两个节点。少的那个分区只有1票,不足半数,它没有资格选举新主,也无法对外提供写服务;多的那个分区有2票,满足多数派,可以继续工作并选出新的主。网络恢复后,少的那边的节点会被视作“落后成员”,需要重新加入组并通过分布式恢复把数据补上。整个过程不会出现两边同时写的情况。
但如果你的组只有两个节点,一个挂了,剩下的那个节点恰好没有多数派支持,组就无法正常提供服务。很多人以为两节点MGR“至少还有一个活着”,实际上在MGR的多数派逻辑里,2节点组残缺后存活节点也会被驱逐或进入不可用状态。所以我的经验是:生产环境最少3节点,最好跨机架甚至跨机房部署。2节点组不是节省成本的聪明选择,而是给自己埋雷。
3. 选主机制:谁说了算,按什么规矩选
3.1 选主不是“抢”,是“比”
MGR选主的过程不是各节点互相抢占,而是通过组内协商后,按照一套既定规则选出新任primary。这套规则的优先级是:
- 如果某个节点被配置为组复制“PRIMARY”角色(
group_replication_single_primary_mode下),优先选择它; - 再比较成员权重(
group_replication_weight),权重高的优先; - 权重相同时,选择server_uuid最小(字典序)的节点。
听起来很抽象,我就用一个生活化的例子解释。这就像公司里要选一个临时负责人:先看谁在组织架构里就被指定为副手,在没有指定副手的情况下重新选举,就看谁资历高、贡献多(权重),如果资历贡献都一样,就看谁的工号靠前(UUID序号)。这样一来,选主的结果是确定性的,不会出现两个节点同时认为自己是主的情况。
3.2 group_replication_election_mode参数演进
group_replication_election_mode是理解MGR选主行为的关键参数。它的三个取值是:
weighted:完全依赖权重,权重最高的获得primary角色,不加额外逻辑;single_primary:平时只允许一个节点作为primary,故障时自动选出一个新的primary;multi_primary:不选举primary,所有节点都是主,故障节点被排除后,其他节点继续工作。
在MySQL 8.0.13之前,默认是weighted模式;从8.0.13开始,默认值调整为single_primary。这个变化透露出一个趋势:官方希望组复制在选主时更自动化、更少依赖人工配置权重,直接把“单主模式”作为默认路径。如果你手上有8.0.13之前的版本,升级后注意确认一下这个参数的取值,避免升级导致行为变化。
3.3 影响选主结果的关键配置清单
讲了这么多,实际操作时,为了让选主符合预期,我会按下面这套清单来做:
- 给不同容量的节点设置不同的
group_replication_weight,比如主力机器权重80,备机权重40; - 确保每个节点有唯一的server_uuid,不要克隆了机器忘了改;
- 如果希望指定某台节点永远当主,可以通过配置primary角色来固定,减少选主不确定性;
- 在单主模式下,把应用层的写连接指向“当前primary”而不是固定IP,或者直接用MySQL Router做读写路由。
还需强调,选主时只会选择“在线”且“已通过分布式恢复”的成员,正在RECOVERING状态的成员不会被选为primary。如果主节点挂了,剩下的secondaries里有数据严重落后的节点,MGR会优先选择数据更完整的节点来当新主,宁可牺牲性能也要保障数据最多不丢。
4. 故障转移的完整流程:从检测到恢复,一步步走
4.1 单主模式故障转移的完整路径
我拆一个最常见的场景:三个节点A、B、C,A是primary,B和C是secondary。A突然因为断电整个宕机。此时B和C通过组通信层的心跳消息发现A失联,经过几轮确认后,把A驱逐出组。B和C组成新的组视图,两个节点都拿到了足够多的票数,进入选举阶段。
选举结果可能是B,也可能C,取决于我们前面说的权重和UUID规则。假设B胜出,B的角色会从SECONDARY切换为PRIMARY,并且对所有成员开放写权限。C会继续保持SECONDARY,但它的复制源会自动切换为B,继续接收新事务。整个过程中,业务不需要人工干预,只需要让应用重连到B即可。如果前面接了MySQL Router,它也会自动感知到primary变化并把流量导到B。
这里要注意一个细节:MGR的故障检测到选主完成通常只需要几秒钟,但应用层重连、安全认证建立、连接池刷新都是需要时间的。极端情况下,一些长连接会在这几秒内抛出连接异常,应用框架没有配置自动重试就会报错。所以MGR只负责“数据库侧的事故处理”,应用层“怎么优雅重连”这件事仍然要提前设计好。
4.2 多主模式下的故障处理差异
多主模式在故障转移上更“简单直接”:因为群组内本来就没有“专属primary”,A挂了以后,B和C早就在接受写请求,根本不需要重新选举。但代价就是认证冲突。比如客户端同时把请求分发到了A和B,两边都更新了同一行,最终只会有一个事务被提交,另一个会回滚并返回“认证失败”或“死锁重试”的错误。
所以在多主模式下,应用必须能够处理随机回滚的可能。我在实际项目中见过不少团队把多主模式当成“自动负载均衡”来用,结果上线第一天就有两个服务对同一用户余额做扣减,出现大量事务回滚,不得不紧急改回单主模式。多主不是不能用,而是适合“数据天然按业务分片、写冲突概率极低”的场景。如果业务是全局写同一个资源,单主模式才是安全选择。
4.3 应用层如何配合故障转移
故障转移做得再漂亮,如果应用层的连接不认账,等于白搭。建议从以下几个方面配合:
- 数据库账号尽量在所有节点保持一致,且权限一致,避免新主接棒时连接认证失败;
- 应用配置写入“组内节点列表”而非单个IP,或者通过MySQL Router做VIP式的漂移;
- 连接池的
testOnBorrow或socketTimeout要合理配置,避免在MGR切换期间拿到失效连接后一直卡着不释放; - 对写操作要有幂等设计,因为切换瞬间可能有重复提交、部分重试等异常情况。
我还习惯在切换完成后主动做一次“全链路探活”:用脚本连接新primary执行一条写操作加读操作,验证数据库确实是可写不报错,再让业务流量放量接入。MGR的自动选举是技术保证,但上线前的人为验证是最后的兜底。
5. 实战经验:常见故障、排查思路与避坑技巧
5.1 常用巡检SQL和排查入口
排障第一步,先看成员状态。以下SQL基本是每次必查:
SELECT * FROM performance_schema.replication_group_members; SELECT * FROM performance_schema.replication_group_member_stats\G; SELECT * FROM performance_schema.replication_connection_status\G;第一张表可以告诉我们每个成员当前是ONLINE、RECOVERING、OFFLINE还是ERROR,以及它在组里的角色是PRIMARY还是SECONDARY。第二张表能看到每个节点的事务应用情况,比如已接收的事务数、已应用的事务数,用于判断节点之间是否存在数据差距。第三张表排查复制通道的连接状态和错误信息。
这里有个小技巧:不要只看MEMBER_STATE这一个字段,还要结合MEMBER_VERSION、MEMBER_ROLE和MEMBER_HOST一起看。比如8.0和5.7混跑的实验环境的MEMBER_VERSION不一致,某些行为就会和预期不符,直接在生产环境会造成兼容性问题。
5.2 典型问题一:节点被驱逐后又恢复,但一直卡在RECOVERING
这种状况通常发生在节点网络闪断期间,组里已经发布了新视图,把该节点剔除。等网络恢复后,节点重新加入组,进入RECOVERING状态,需要通过binlog追历史数据。如果它落后的数据量很大,而binlog已经被purge掉了,恢复就会一直卡住。
解决方案有两种:
- 如果距离被驱逐时间不长,先确保binlog保留时长足够,增大
binlog_expire_logs_seconds; - 如果数据相差太远,直接用远程克隆(CLONE插件)给这个节点重建一份快照数据,再继续以增量方式追日志。
生产经验告诉我:无论哪种方式,都要等到节点变为ONLINE后,再让业务流量指向它。RECOVERING状态下的节点虽然能查询,但数据不是最新,写操作也绝对不允许。
5.3 典型问题二:主节点没挂,但没有新主被选出来
有一种隐藏场景容易被忽略:疑似主节点宕机,但剩下的备节点都满足多数派,选主却没有发生。这往往是因为组内出现了“分区视图”,即主节点并没有真正宕机,只是被其中一个分区隔离了,另一分区里的节点其实都无法接收到主节点的心跳,因此它被驱逐,但它们之间的网络质量极差,组通信迟迟不能达成一致。
排查方法是看各节点的replication_group_members视图,如果发现某个节点在自己的视角里是ONLINE,但在另一个节点视角里是UNREACHABLE,基本就是网络分区或防火墙策略出现不一致。这时候不要急着重启组复制,先把网络链路、iptables、安全组规则全部检查一遍。
5.4 典型问题三:MGR集群偶发全员ERROR的自救指南
我见过很多刚上手MGR的人被“全员ERROR”吓得连夜重启服务器,其实这套故障往往和参数配置息息相关。最常见的诱因有两个:一是group_replication_communication_stack或Paxos相关超时设置得太短,导致网络抖动误判;二是启动组复制的顺序不规范,多个节点同时尝试引导组,引发视图冲突。
自救的操作顺序是:
- 停止所有节点的组复制通道:
STOP GROUP_REPLICATION; - 确认哪一个是当前最完整的数据节点,先在这个节点执行
START GROUP_REPLICATION,让它尝试引导组; - 依次启动其他节点,等待每个节点都变为ONLINE后再启动下一个。
重点提醒:不要多个节点同时“孤注一掷”地执行START GROUP_REPLICATION,那样极易形成多个都不完整的小组视图,互相冲突。配合前面说的group_replication_bootstrap_group参数时尤其要小心,bootstrap只在第一个节点、且只在集群初始化时使用,日常恢复绝对不要开。
6. 参数调优与部署建议:让MGR少点幺蛾子
6.1 推荐的MGR核心配置基线
这里放一套我平时部署MGR时会用的基础参数模板,仅供参考,实际要根据你的硬件和业务负载微调:
[mysqld] server_id=100 gtid_mode=ON enforce_gtid_consistency=ON binlog_format=ROW transaction_write_set_extraction=XXHASH64 master_info_repository=TABLE relay_log_info_repository=TABLE log_replica_updates=ON relay_log_recovery=ON binlog_checksum=NONE loose-group_replication_group_name="aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" loose-group_replication_start_on_boot=OFF loose-group_replication_local_address="10.0.0.1:33061" loose-group_replication_group_seeds="10.0.0.1:33061,10.0.0.2:33061,10.0.0.3:33061" loose-group_replication_bootstrap_group=OFF loose-group_replication_single_primary_mode=ON loose-group_replication_enforce_update_everywhere_checks=OFF loose-group_replication_member_expel_timeout=5 loose-group_replication_autorejoin_tries=5几个关键点说明一下:
binlog_checksum=NONE是MGR常见的兼容性要求,如果设为CRC32,部分复制通道可能报错;group_replication_group_name必须填写一个合法的UUID,不能随便编;group_replication_start_on_boot在初次部署时建议OFF,等所有节点配置一致后再决定是否开机自启;group_replication_enforce_update_everywhere_checks在单主模式下OFF,多主模式下ON,二者不要搞混。
6.2 存储、网络与监控的建议
MGR对I/O和网络的敏感度都要明显高于传统主从。SSD几乎是必需品,因为分布式恢复和认证机制会频繁读取binlog、执行事务应用,磁盘太慢会拖慢整个组的进度。网络方面,组员之间的Paxos通信虽然不大,但延迟过高会直接影响故障检测和事务提交排队。生产上最好保证节点间的RTT在1ms以内(同机房),跨机房部署要考虑专门的专线并适当放宽超时参数。
监控方面,除了常规的MySQL监控项,建议重点盯住replication_group_members的成员状态变化、replication_group_member_stats中每个节点的事务队列积压量、以及replication_connection_status中的IO状态。如果队列积压持续上涨,说明某个节点应用事务的速度已经跟不上,随时可能成为故障转移中的拖后腿角色。
6.3 什么时候不要用MGR
MGR不是银弹。如果你的业务特性是:大量串行化全局写、必须使用MyISAM表、对事务隔离级别要求特殊、或者属于老旧的5.6版本,MGR根本跟不上你的节奏。MGR只适用于InnoDB表和基于行复制的事务型工作负载,而且对SQL语法也有约束,临时表、LOCK TABLES等操作都会受到限制。做方案取舍时,先用官方“Group Replication Limitations”文档过一遍,别等上线后才发现某个业务需求不支持。
我个人见过最荒唐的案例,是有人把MGR部署在虚拟机里,还开了磁盘快照功能,结果每次打快照都会触发全成员延迟飙升,随后接连被驱逐。虚拟化层的I/O尖刺对MGR太不友好。要明白,故障检测的“心跳”不依赖网络包数量,而是依赖进程能不能及时响应——CPU被抢占、磁盘卡I/O、Java GC式的大停顿,都会让MySQL无法按时发送心跳。MGR再懂自动检测,也救不了底层环境不稳的节点。
最后的实际操作体会
用了这三年MGR,我对它最大的感受是:它的故障转移确实比传统主从要省心,但省心不等于“不用管”。真正决定MGR生产体验的,往往是部署前的网络规划、数据目录的独立磁盘、参数模板的合理性,以及你对“多数派”机制的敬畏。踩过几次全员ERROR、受够了半夜被电话叫醒之后,我现在只要听到谁打算仅用两台机器组MGR来省钱,都会劝他再加一台。多数派不是说说的,少一台机器,你连遇到故障时“说一句公道话”的资格都没有。
如果你正在从传统主从切换到MGR,我的最后一条建议是:先在测试环境模拟故障,拔网线、kill进程、重启节点,一轮一轮地演练,直到每个开发同学都习惯“数据库连接突然断了会自动恢复”这件事。MGR看着高大上,归根到底还是一套分布式系统,分布式系统的定律就是这么冷酷:任何自动转移机制,都需要稳定环境、合理参数和可靠应用层来配合。把这三样补齐了,MGR才能从“演示很炫”变成“生产很香”。