MGR选主策略全解析:从视图切换、多数派约束到权重调优 MGR 选主这个话题我经常看到很多人在网上讨论但大多数讨论都停留在“主挂了权重高的顶上”这种简化结论。作为用过 MySQL Group ReplicationMGR在线上线下跑过几年生产环境的人我必须说结论虽然没错但它严重低估了选主策略背后的复杂链路。真正决定谁成为下一任主库的不是一行参数而是一整套嵌在组通信、共识协议和成员状态机里的流程。你只了解表面遇到故障时一定会被现实教育。这篇文章我就把 MGR 单主模式下的选主策略彻底拆开讲清楚状态判断、多数派约束、权重参数、故障时间线和排障方法适合正在搭建或维护 MGR 的 DBA、架构师阅读。1. MGR 选主背后的基本运行框架1.1 从组通信协议说起选主本质是一次“视图切换”MGR 和传统主从复制有一个决定性的区别传统复制里谁做主库通常由外部脚本或者第三方高可用组件说了算而 MGR 把故障探测、成员管理、主节点选举全部收进了数据库节点自己内部。每个节点启动后会通过组通信系统加入同一个复制组所有成员基于一套共识协议交换消息。这个通信层在 MySQL 的实作里叫 GCS底层用的是 XCom 通信库。它要保证的事情非常简单所有存活节点对“组里有哪些成员、哪个事务先执行、当前视图是什么”持有完全一致的认知。没有这个基础后面任何选主逻辑都会失去意义。选主之所以和视图绑定在一起是因为 MGR 的所有关键操作都以“视图变更”作为事件起点。所谓视图你可以理解成“当前存活且被共识认可的成员名单”。每当有成员加入、离开、被判定不可达组内就会触发一次新视图的生成。单主模式下新视图生成的同时MGR 会判断当前是否还存在 primary、候选节点有哪些然后决定是否启动一次新的主节点选举。所以你可以把选主理解成为一个视图切换的子动作而不是数据库在某个时刻突然发起的一场投票。这个视角能解释很多奇怪现象比如切换延迟忽高忽低。视图变更需要存活成员对新视图内容达成一致如果某个节点因为内存抖动、网络延迟迟迟不确认新视图其他节点只能等共识超时。我在生产环境排查过几次切换慢的问题最后都发现瓶颈不在选举算法本身而在视图确认阶段被某一个网络不稳定的节点拖住了。日志里那些反复出现的 view change 相关告警才真正值得盯。1.2 单主与多主模式下选主规则的巨大差异MGR 的选主策略并不是在所有运行模式下都存在。把 group_replication_single_primary_mode 设置为 ON组内同时只会有一个 primary这个角色可以自动选举更换设置为 OFF组处于多主模式所有节点都能接受写请求组内不存在统一的主节点概念自然也谈不上“选主”。多主模式下节点之间靠事务冲突检测和认证来收敛写入和单主模式的选主逻辑完全是两套体系。但很多人容易把这两个模式混在一起聊甚至以为把 primary_member_weight 调高在多主模式下也能让某个节点“优先成为主节点”。实际情况是多主模式下根本不存在全局 primary 角色这个参数完全没有效果。设计高可用方案时我建议在架构文档里写清楚集群用的是单主还是多主选主策略基于什么规则。否则后期维护者面对一套 MySQL 配置会下意识套用单主模式的经验去调参最后发现完全不对。下面整个文章的讨论都默认是单主模式。2. 候选人池的两道门槛状态门禁与多数派硬约束2.1 成员状态直接决定有没有资格参选视图变化确认后MGR 要决定的是“哪些节点可以做下一任 primary”。候选池构建的第一道门槛就是成员状态。在 performance_schema.replication_group_members 表里MEMBER_STATE 字段会展示每个节点的运行状态通常有 ONLINE、RECOVERING、UNREACHABLE、OFFLINE 四类。它们和候选资格的关系可以简单整理成下表MEMBER_STATE含义能成为候选吗ONLINE在线组复制正常运行可以RECOVERING正在追数据、重建复制不可以UNREACHABLE组通信不可达不可以OFFLINE未加入组或已被驱逐不可以其中 RECOVERING 状态最容易让人误解。一个节点刚加入复制组可能已经连上了主库正通过分布式恢复通道复制积压事务但它的数据还没有和主库完全对齐。如果把它选成 primary业务写进去的数据立刻会产生不一致组内其他成员根本没法跟随。所以 MGR 对待候选池非常严格不会为了“好歹留个主可用”而把一个 RECOVERING 节点抬上来。这一点对我们的高可用设计有直接影响如果你想用三节点集群扛住两次节点故障但恢复流程没设计好即使组里多了一个 RECOVERING 的节点它也无法在故障中补位。还要提醒一句ONLINE 只是通信层面的“活着”不代表复制线程健康。一个节点的 SQL 线程因为本地错误中断时它的 MEMBER_STATE 可能仍然是 ONLINE但复制实际已经断了。这种隐藏故障在选主演练时最容易现形节点被选为新主却立刻回放报错或者中断写入口反而被一个不健康的节点接管了。所以我做监控时不只盯状态字段还会额外看复制通道报错、relay log 可用性以及 replication_group_member_stats 里的复制积压指标只有这些组合起来才能确认一个 ONLINE 节点真的具备接任条件。2.2 多数派不是全员投票而是“只剩一半就停摆”候选池确定之后还需要一道硬约束来保证合法性这就是多数派原则。MGR 沿用分布式系统里非常经典的多数派机制N 个节点的组任何关键决策包括视图变更和选主至少需要 floor(N/2)1 个节点达成一致。三个节点至少两个同意五个节点至少三个同意七个节点至少四个同意。这个机制的价值主要在网络分区场景中体现。如果没有多数派约束一个节点被孤立后可以继续认为自己还是合法主节点继续接收写入等网络恢复时再和集群其他节点对账就会产出真正的脑裂。多数派原则用简单粗暴的方式把这个风险堵死了少数派分区里的所有事务提交都会阻塞哪怕这个分区里恰好有原来的主节点。多数派的代价也值得提前想清楚三节点集群挂两个就停摆五节点集群挂三个也停摆。这不是 bug而是保证一致性的必要成本。如果你对可用性的预期是“只要还剩一台机器活着就能写”那 MGR 的多数派原则显然不合适。之前我在协助一个团队评审时他们一直把 MGR 和高可用画等号直到演练时发现三节点同时断电两台剩余一台唯一能做的就是拒绝所有写入才开始真正理解多数派的含义。精确地说“剩一半就停摆”这个描述不够准确更准确点是“剩不到半数就停摆”这个点我在分场景推演时会再展开。3. 权重参数决定胜负group_replication_primary_member_weight 的真实用法3.1 权重的排序逻辑与 UUID 兜底候选池合格之后还需要一个明确的顺序来决定谁接任否则每个节点对“谁当新主”的判断可能各有出入。MGR 给出的排序依据就是 group_replication_primary_member_weight 这个参数。它的默认值是 50取值范围是 0 到 100越大优先级越高。你可以把它写在配置文件里也可以在实例运行时动态调整。当一次选主开始时MGR 会从 ONLINE 节点组成的候选池里优先挑选权重最大的节点接任 primary。整个过程是确定性的所有存活节点会得出同一个结果。一个常见问题是如果所有节点权重都一样默认都是 50那到底选谁实际上权重只负责第一层排序权重相同时 MGR 会退回到一个稳定可复现的规则。这里的细节和成员在共识视图里的排序、成员自身的标识信息有关比如基于 server_uuid 的字典序或者组视图的成员顺序最终保证每个节点都能得出完全一致的结论。生产环境里你可以通过查看各节点的 server_uuid 大致推断默认情况下的接任顺序但通常没必要依赖它因为一旦权重策略生效优先级的决定性就归权重所有了。还需要明确一个参数的应用时机它不影响一个已经稳定运行的 primary 是否继续服务。也就是说集群没发生视图变化、primary 没故障时你把某个 secondary 的权重从 50 改成 100它不会立刻把主库抢过来。只有当 primary 失效、被误判驱离、或者组内成员列表发生变动并需要生成新的主角色时权重才参与排序。我习惯把它叫“排序器”不叫“触发器”。理解这一点能在排障时省下大量时间。3.2 我在生产环境配置权重时踩过的三个坑第一个坑是在多主模式下调参。有次我在测试环境把组从单主切换成多主忘了把权重配置理顺后来看监控数据才发现这个参数在多主模式下完全没有影响事务走向。多主模式没有唯一主权重自然闲置。如果团队里有切换运行模式的操作配置管理里一定要说清楚参数生效的模式边界避免代码配置跟随模式切换自动迁移后产生幻觉。第二个坑是“低权重节点不会接管”的错觉。有人为了防止备用机房里的节点在故障时被动接管把它的 primary_member_weight 压得非常低比如 1。但如果主节点和另一个高权重节点同时失效组里只剩这个权重为 1 的节点它照样会被选成 primary。多数派和候选池只看节点是否 ONLINE不存在最低权重门槛。换句话说你想让某个节点“永远不当主”靠 weight 是无法实现的只要它还在候选池里总有概率成为唯一可用者。第三个坑是权重参数的持久化问题。运行时用 SET GLOBAL 修改的 group_replication_primary_member_weight 只对当前实例内存生效实例重启后就会退回配置文件里的默认值。很多时候我们线下测试时用 SET GLOBAL 改了权重当时确实生效故障恢复后节点重启参数却悄悄变回 50后续选主顺序就和预期完全不一致了。所以生产环境里我建议用 SET PERSIST 写入持久化配置。几种设置方式对比如下设置方式生效范围重启后是否保留配置文件 my.cnf实例级全局是SET GLOBAL当前运行时否SET PERSIST当前运行时 持久化是我在生产上基本只用配置文件或 SET PERSISTSET GLOBAL 只留在线调试时使用。4. 三节点实例推演主库宕机后的完整选主时间线4.1 第一阶段失效检测与成员驱逐为了把选主讲得具体这里用一个最典型的三节点组推演一遍。假设 A 是 primaryB 和 C 是 secondary三节点同机房网络稳定。某天 A 所在物理机突然断电。B 和 C 会在随后的心跳中立刻发现 A 没有回应这时会把 A 标记为 UNREACHABLE。但注意标记不可达不代表马上踢出组MGR 会进入一个故障确认阶段由 group_replication_member_expel_timeout 参数控制等待时间。这个参数默认值是 0含义是故障检测确认后立刻执行驱逐。生产环境我一般会调成 5 到 10 秒因为一次交换机闪断、虚拟机热迁移引起的小抖动持续时间可能只有几百毫秒到几秒。如果默认立即驱逐网络稍微抖一下就把一个节点误杀出组触发无意义的选主这对应用是纯伤害。调得太大也不行驱逐确认越晚切换越慢RTO 越高。到底设多少要在业务容忍度和网络稳定性之间做平衡。我的经验是同机房网络稳定可以先设 5 秒长链路跨机房场景按往返时延的若干倍再放宽。一旦超时到达A 被正式驱逐组视图从三节点退化为 B、C 两节点。此时两节点满足多数派条件2 大于 3 的一半新视图生成选主流程正式启动。4.2 第二阶段新主产生、旧主回归与回切策略B、C 作为候选先比较权重。如果 B 的 primary_member_weight 是 90C 是 50那 B 毫无悬念成为新 primary。如果两个都是默认 50则落到权重相同的兜底排序上。B 切换角色后会立刻向客户端宣告自己成为组内唯一写入口C 自动把复制源指向 B。这部分在日志里往往体现为一条 primary 变更消息而不是大段选举细节。从时间线上看整个过程分三段故障确认阶段受超时参数控制、视图变更阶段通常毫秒级到秒级、角色切换阶段主要是本地角色通知和状态更新。如果参数配置合适三节点下从 A 失联到 B 可写通常能控制在 5 秒上下。想要更快就必须降低超时参数、减少网络抖动但也会同步增加误判风险。A 网络恢复后重新连接组MGR 不会直接把 primary 还给 A。A 会先以新成员身份进入 RECOVERING追赶缺失的事务直到 ONLINE 之后才具备候选资格。之后如果 A 的权重比 B 高在下一次选主时会优先候选但 B 只要不退出、不被判定失效A 不会自动触发回切。想要业务切回 A常规做法是等 A 追平事务后对 B 执行 STOP GROUP_REPLICATION让 B 主动退组触发一次新选举再由权重更高的 A 接任。操作窗口内要保证客户端连接已经切换到 A或者由负载均衡自动完成。我在实践中特别强调一点回切前一定要确认 A 的积压事务完全追平否则它即便当了主追数据的高延迟也会让写入短暂卡顿业务投诉比故障期间还多。5. 网络分区、脑裂防线与实际集群的最终处理5.1 分区发生时的选主困境从验证到抉择单节点宕机时的选主过程相对干净。真正的难点在网络分区。同样的三节点 A、B、C如果上层网络故障把集群切成两片比如 A、B 在一个区块C 在另一个区块。C 这侧立即陷入困境它看不到 A、B但它仍然存活于是自己也尝试形成新视图并继续当主。A、B 这侧也看不到 C但至少彼此还能通信。多数派机制在这里发挥作用。A、B 一侧有 2 个节点占 3 个节点中的多数可以合法生成新视图并完成选主C 只有 1 个节点不是多数只能停在孤岛上所有写请求都会阻塞。如果 primary 恰好落在 C 那侧A、B 会在驱逐 C 后重新选出新主写入口整体迁移到 A、B 侧。C 网络恢复后只能以 secondary 身份追赶事务之前提交到 C 的未同步事务要根据具体上下文回滚或重新执行。如果把分区做得更极端三个节点分别落在三个互不可达的区块那就没有一个分区拥有多数整个集群会全部拒绝写入无论哪个节点都凑不出合法视图。这种情况在跨机房多活设计中尤其值得警惕。看起来每个机房各配了一台数据库似乎弹性很高实际上一条专线抖动就可能让三个机房彼此隔离集群瞬间停摆。我在给团队做架构评审时一定会问一个问题你设计的分区容错到底是在容忍“节点故障”还是在容忍“网络故障”大多数人前者想清楚了后者没有。5.2 旧主恢复后为什么不能直接改权重让它接管旧主在分区恢复后重新加入组如果组里已经产生新主它不会自动夺回角色。很多人在这一步犯的错是试图把旧主权重调高来“抢主”。实际上权重不会强制挤出正在运行的 primary一个已经稳定的组并不会因为某个成员权重变大而主动发起重新选举。权重只在一次新鲜选主事件发生时生效比如现有主节点主动退出、被驱逐或者视图因为成员变动重新生成。想让旧主回归需要手动编排。安全步骤是先把旧主的积压事务追平确认它从 RECOVERING 变成 ONLINE然后让当前主节点主动下线或执行 STOP GROUP_REPLICATION触发一次新选举旧主作为高权重节点才会在新一轮排序中胜出。整个过程相当于做了一次计划内的主角色迁移而不是参数热修复。如果旧主当初在隔离期落后得太多甚至不建议立刻回切。我记得有个线上案例网络恢复半小时后团队急着把流量切回旧主结果旧主还在回放积压事务读延迟飙到几秒业务投诉持续了大半天。耐心等它追平再回切才是稳妥做法。所谓“追平”就是要看到 replication_group_member_stats 里的事务应用队列稳定归零。6. 选主执行后的观察与排障指北附监控查询6.1 用 performance_schema 观察当前主节点选主结束后第一时间确认谁是当前主。最直接的办法是查 performance_schema.replication_group_membersSELECT MEMBER_ID, MEMBER_HOST, MEMBER_PORT, MEMBER_STATE, MEMBER_ROLE FROM performance_schema.replication_group_members;输出里的 MEMBER_ROLE 如果为 PRIMARY那就是当前主节点。如果还是 SECONDARY说明只是普通跟随者。顺便看一眼 MEMBER_STATE确认通信状态是否 ONLINE。如果节点 ONLINE 且角色是 SECONDARY说明它至少健康只是本次选举没轮上不需要紧张。要判断新主是否有能力继续对外服务则需要看 performance_schema.replication_group_member_statsSELECT * FROM performance_schema.replication_group_member_stats\G重点看两个字段COUNT_TRANSACTIONS_IN_QUEUE代表等待认证的事务队列COUNT_TRANSACTIONS_REMOTE_IN_APPLIED_QUEUE代表远程事务等待应用队列。如果新主在切换后这两个队列持续不为零说明它还在追赶事务对外写入可能变慢甚至阻塞如果稳定为零说明已经追平可以把业务流量逐步放开。监控告警脚本建议基于这两个字段做阈值比如队列长度超过 100 且持续 10 秒以上就高等级告警。如果这些查询没有返回数据需要先确认当前账号是否具备访问性能模式表的权限通常 PROCESS 权限或者只读的监控账号就够用了。权限不到位时脚本会拿不到任何状态容易误判集群故障。6.2 客户端的感知与接管期间的常见误判MGR 选主只解决数据库内部谁做主的问题并不负责客户端如何感知新主。传统架构里JDBC 驱动、中间件、负载均衡、VIP 漂移哪一环没有跟上业务就会继续把请求发给已经失效的旧主。常见误判就是“MGR 已经选出新主”等同于“业务已经恢复写入”。实际上两者之间还隔着一条很长的链路。我在设计高可用架构时会把客户端连接逻辑和 MGR 状态查询绑定起来。最简单的成熟做法是给 primary 关联一个 VIP 或者虚拟读写地址由脚本周期读取 replication_group_members发现 MEMBER_ROLEPRIMARY 的节点后把 VIP 绑到该节点一旦主节点切换脚本立刻释放原 VIP 并绑定到新主。另一种做法是用具备读写分离和故障转移能力的数据库中间件它们能感知 MySQL 复制拓扑。不过无论用哪种方案探活逻辑都要避免一个陷阱当 MGR 内部已经因为失去多数派而停摆时脚本不能把一个在线节点的状态误判为可写然后把 VIP 绑过去。VIP 可以漂但 MGR 多数派不满足时漂过去也没有意义。所以探活脚本最好把 MEMBER_STATE、MEMBER_ROLE、以及所在节点是否满足多数派三个条件同时纳入判断。6.3 基于选举机制的运维建议我把前面讲的机制收敛成几条可以直接落地的建议。第一三节点集群在选主和多数派容错上看起来够用但生产环境我一般建议直接上五节点。五节点允许同时挂两个节点容量更大不过五节点也存在分区限制没有任何拓扑是百分百安全的。第二不要为了切换快就把 group_replication_member_expel_timeout 调得极低。故障确认速度和切换速度是跷跷板确认越快误杀概率越高。建议结合监控里的网络抖动数据把超时设置成一个“比最长抖动稍大、比业务容忍的 RTO 稍小”的值。第三选主参数要在没有故障时统一规划。把每台机器的 primary_member_weight 写进配置管理和机房优先级、硬件规格对齐。同时用 SET PERSIST 落到持久化配置避免节点重启后权重悄悄回归默认值。第四最好每个季度做一次拔线演练。拔掉 primary 所在节点的网线记录选主耗时、日志输出、客户端重连表现再拔掉一个 secondary 的网线观察多数派是否正常。演练中发现的问题远比读文档来得深刻。写到这里MGR 选主涉及到的机制、参数和场景基本都讲透了。最后再说一点个人体会。我在演练和真实故障里踩过几次坑之后最大的收获是别把选主当成一条命令的结果它更像“成员关系变更后必然触发的连锁反应”权重只是反应链条里很小的一环。很多问题比如恢复顺序不对、回切后延迟飙高、权重调了没生效本质上都不是选主算法本身的问题而是对成员状态和多数派约束的理解不够。如果你也在维护 MGR 集群强烈建议找个测试环境把网络拔了、把主库杀了逐个观察日志和监控指标完整走一遍选主时间线。走完一遍你会对这套机制建立起真正的肌肉记忆。