本文是对 Improving MySQL® Cluster Uptime: Making MGR Viable at Scale 的整理与翻译。
内容结构概览
本文主要分为六部分:
- Uber 为什么要把 MySQL 高可用架构推进到 MGR 共识模式。
- 如何通过控制平面自动完成集群接入、退出和再平衡。
- 当参与共识组的节点被删除或替换时,系统如何保持稳定。
- 主库故障后,MGR 如何检测失败并完成新的 Primary 选举。
- 主库切换期间,如何在可用性和一致性之间做取舍。
- Uber 在大规模部署 MGR 过程中总结出的关键经验。
一、背景:从"外部救火"到"数据库内部自恢复"
在传统 MySQL 主从架构里,通常是一个 Primary 承担写入,多个 Replica 承担读请求。Primary 一旦故障,外部系统需要先检测异常,再选择合适的 Replica 提升为新的 Primary,最后更新路由,让业务流量切到新主库。
这个方案能工作,但问题也很明显:故障检测、主库提升、路由更新都依赖外部系统。一旦外部系统本身变慢、误判、不可用,数据库故障恢复就会被拖慢。Uber 在第一篇文章里提到,他们过去的 MySQL 集群依赖外部 Health Controller 和自动化提升工具来做故障检测与主库提升,而旧系统的故障缓解 SLA 大约是 120 秒。(Uber)
MGR 的核心变化在于:数据库节点之间自己组成共识组,通过共识协议维护一致状态,并在 Primary 失败时自动选举新的 Primary。Uber 最终采用的是三节点共识组,并运行在 single-primary 模式下:一个 Primary 负责写入,另外两个 Secondary 参与共识,但不直接承接写流量。共识组之外,仍然可以挂载更多只读副本,用来支撑读扩展。(Uber)

从图里可以看到,Primary Region 内有一个 Consensus Group,其中包含 Primary、Secondary 1、Secondary 2 三个 MGR 节点。普通 Read Replica 不直接复制 Primary,而是挂在共识组 Secondary 后面。这样做的好处是:Primary 失败时,新的 Primary 可以在共识组内部选举出来;而普通读副本不直接从旧 Primary 读取,也能减少异常 GTID 扩散到集群外围的风险。
二、自动化运维:接入、退出和再平衡
真正难的地方不只是"搭一个三节点 MGR",而是让这个架构能在大规模环境里稳定运行。Uber 为此构建了一个控制平面,用来编排集群接入、退出、再平衡、节点删除和节点替换等关键流程。原文强调,这些操作都被自动化了,从而降低人工干预和人为错误。(Uber)
1. 将一个 MySQL 集群接入共识模式
把一个已有 MySQL 集群迁移到新的高可用架构,并不是简单执行一条命令。Uber 将这个过程封装成一键工作流,大体分为三步。
第一步,控制平面先选择一个健康节点作为 bootstrap node,也就是新共识组的启动节点。这个节点会成为构建 MGR 组的起点。
第二步,另外两个节点被自动加入这个 group。新加入的节点会和 bootstrap node 同步数据,确保三者状态一致。
第三步,当三个节点都进入共识组,并且状态为 ONLINE 后,原来的复制流程会被关闭,新的 MGR 架构成为集群的事实数据源。
Uber 的 MGR group 固定为三个节点,并运行在 single-primary 模式下。同时,他们设置了一些关键参数:
text
group_replication_paxos_single_leader = ON
group_replication_member_expel_timeout = 0
group_replication_unreachable_majority_timeout = 1 second
其中,group_replication_paxos_single_leader 用来确保 MGR 运行在单主模式;group_replication_member_expel_timeout 控制可疑成员被踢出 group 的时机;group_replication_unreachable_majority_timeout 则用于处理网络分区场景,当某些节点处在少数派分区时,会在很短时间后自动离开 group,避免形成 split-brain,也就是脑裂。相关参数属于 MySQL Group Replication 的系统变量。(Uber)
这里的重点是:Uber 没有把 MGR 接入流程交给人工执行,而是把节点选择、加入 group、状态检查、旧复制链路关闭这些动作都纳入控制平面。这样可以保证每个集群都按同一套流程迁移,减少人为操作带来的不确定性。
2. 将一个集群从共识模式退出
退出共识模式的流程基本是接入流程的反向操作。
控制平面会逐个将节点从 consensus group 中移除,通常先从 Secondary 节点开始。节点退出后,会被重新配置为传统异步复制模式,接近旧架构里的普通副本。
这种结构化的退出流程有两个价值:第一,节点不会突然脱离共识组,降低数据不一致风险;第二,退出后集群仍然保持可运行状态,不会因为架构切换而让业务侧感知到明显中断。
3. 对共识集群做再平衡
MGR 集群在运行过程中可能会因为节点替换、节点故障、扩容缩容等原因偏离理想状态。Uber 为此实现了一个 Rebalance HA Cluster 工作流,用来持续检查并修正 HA 共识组状态。(Uber)
这个工作流首先会分析当前有多少节点正在运行 HA consensus,并且处于 ONLINE 状态。根据 ONLINE 节点数量,控制平面会判断集群处于哪种状态。
如果正好有三个 ONLINE 节点,说明集群处于稳态,不需要动作。
如果超过三个 ONLINE 节点,说明共识组里节点过多。控制平面会选择一个 Secondary 节点下线,避免不必要的资源开销和性能影响。
如果少于三个 ONLINE 节点,说明冗余不足。控制平面会选择合适的候选节点,将它接入共识组,以恢复三节点结构。
除了共识组本身,工作流还会修正复制拓扑。Uber 要求只有共识组中的 Secondary 节点可以直接从 Primary 复制,其他节点不应该直接挂在 Primary 下面。同时,普通 Read Replica 应该均匀分布在两个 Secondary 后面,避免某一个 Secondary 成为读复制链路上的瓶颈。

这张图展示了一个典型的再平衡过程:初始状态下,Primary 和两个 Secondary 组成三节点共识组;当 Primary 不可达后,系统会让某个 Read Replica 加入 group;随后新的 Primary 被选出,旧 Primary 被移出 group,集群重新回到稳定状态。
这里可以看到 Uber 的设计思路:高可用不是一次性搭好就结束,而是要有一个持续运行的控制循环。这个控制循环不断检查目标状态和实际状态的差异,并自动把系统拉回目标状态。
三、节点删除:不能直接删,先从共识组优雅退出
在普通 MySQL 副本架构里,删除一个 Replica 通常相对简单。但如果这个节点是 HA consensus group 的成员,直接删除就可能影响共识组稳定性,也可能影响挂在它后面的下游副本。
Uber 的做法是在真正删除节点之前,增加一个额外步骤:先调用"从 HA consensus 中 offboard 该节点"的工作流。这个流程会做两件关键事情。(Uber)
第一,处理下游副本。如果要删除的节点下面挂着普通 Read Replica,这些副本会被重新指向共识组的 Primary,保证删除节点后下游副本仍然可以继续复制数据。
第二,终止该节点上的 consensus 角色。节点会从共识参与者退化为普通 replica,不再参与 MGR 共识流程。
完成这一步后,再按照普通副本删除流程处理该节点。这样一来,删除流程既不会破坏共识组,也不会让下游复制链路突然断掉。
四、节点替换:优雅替换和非优雅替换都要能兜住
节点替换比节点删除更复杂,因为它涉及旧节点退出和新节点加入。Uber 的 stateful container platform 在不同场景下会有不同顺序。
1. 优雅替换:先加新节点,再删旧节点
在 graceful replacement 场景下,平台会先添加一个新节点,并让它具备和旧节点相同的目标状态,然后再删除旧节点。
Uber 在 post-add node hook 中做了修改:新节点加入时,先以普通 read replica 的身份启动。这一步非常重要,因为新节点刚加入时不应该立刻成为共识成员,而应该先以普通副本身份同步数据,保证连续性和数据一致性。
随后旧节点被删除时,HA consensus group 可能会临时只剩两个 ONLINE 节点。这会触发前面提到的 Rebalance HA Cluster 工作流。控制平面会找到一个合适节点,将其接入共识组,让 group 重新恢复为三个 ONLINE 节点。
2. 非优雅替换:先删旧节点,再加新节点
在 ungraceful replacement 场景下,顺序正好相反:平台先删除旧节点,然后再添加一个新节点。
旧节点被立即删除后,共识组会临时减少到两个 ONLINE 节点。此时 Rebalance HA Cluster 工作流同样会被触发,用来快速补齐共识组成员。新节点加入后,也会通过更新后的 post-add node hook 以普通 read replica 的方式启动。
这里的关键是:无论节点替换顺序是"先加后删",还是"先删后加",Uber 都不依赖人工判断,而是通过控制平面和再平衡逻辑自动把共识组恢复到理想状态。
五、Primary 故障:检测、选举与恢复写入
MGR 架构的一个核心优势是:当现有 Primary 离开 group,无论是主动退出还是意外失败,新的 Primary 都可以自动选举出来。这个机制由 MySQL Group Replication 插件屏蔽了大量细节。(Uber)
1. 如何检测 Primary 失败
共识组里的所有节点都会持续监控当前 Primary 的健康状态。节点之间会定期交换 heartbeat,也就是心跳消息。如果某个节点在指定超时时间内没有收到 Primary 的心跳,就会认为 Primary 可能已经失败。
一旦节点检测到 Primary 失败,就会触发 leader election,也就是领导者选举流程。检测到故障的节点会向 group 内其他节点发送选举消息,启动新的 Primary 选择过程。
2. 选举过程的三个阶段
原文把选举过程概括为三个阶段。
第一是 Prepare 阶段。检测到失败的节点可能会声明自己成为候选者。候选节点会向多数节点发送 prepare request。收到请求的节点如果还没有响应过其他候选者,就会确认该请求,并承诺在某个任期内不再接受其他候选者的 proposal。
第二是 Voting 阶段。每个节点为自己收到 prepare request 的候选者投票。同一个 election term 内,一个节点只能投一次票。候选者如果拿到多数派投票,就成为新的 leader,也就是新的 Primary。
第三是 Leader announcement 阶段。新的 leader 会向整个 group 宣告自己的领导者身份。其他节点确认后更新内部状态。新的 Primary 开始处理写事务,系统恢复正常。
这套流程的意义在于:故障转移不再依赖外部脚本去"挑一个副本提升为主库",而是由 MGR group 内部基于多数派自动完成。对于高可用系统来说,这种内生恢复能力比外部救火式恢复更可靠。
六、Primary failover 期间的一致性选择
Primary 故障后,新 Primary 被选出来并不意味着马上就应该对业务开放。这里有一个关键问题:新 Primary 是否已经应用完旧 Primary 遗留下来的 backlog?
原文讨论了两种方案。(Uber)
第一种方案是尽快恢复可用性。系统在 Primary 失败后快速确定稳定 group membership,选出新的 Primary,然后立即允许应用访问。这样写不可用时间最短,但代价是可能牺牲从 Primary 读取时的严格外部一致性。因为新 Primary 可能还在应用旧 Primary 的 backlog。
第二种方案是优先保证一致性。系统同样会先选出新的 Primary,但在 backlog 全部应用完成之前,不允许业务访问新 Primary。这样会带来很小的可用性损失,但可以保证在 Primary 上执行的只读事务具备严格外部一致性。
MySQL 通过 group_replication_consistency 参数控制这一行为。默认值是 EVENTUAL,更偏向第一种方案,也就是尽快可用。Uber 在部署中覆盖了默认值,改为 BEFORE_ON_PRIMARY_FAILOVER,要求新 Primary 在应用完 backlog 之前阻塞新的操作,从而保障 Primary 上读请求的严格外部一致性。(Uber)
这也是这篇文章里非常值得注意的一点:高可用不是只追求"越快恢复越好",还要明确业务到底需要什么一致性语义。Uber 在这里选择牺牲一点点切换期间的可用性,换取更强的一致性保障。
七、关键经验:MGR 落地不是只改架构,还要改运维习惯
Uber 在大规模实践 MGR 过程中,总结了两个非常具体的经验。
1. MGR 会增加内存使用
运行 MGR plugin 后,Uber 观察到内存使用有所增加。原因是 MGR 插件需要维护自身状态,并在事务应用前将事务缓存在内存里。这个额外开销可能不算很大,但在规划数据库服务器容量时必须纳入考虑。(Uber)
Uber 使用 Performance Schema 中和 memory/group_replication 相关的 instrumentation 来监控 MGR 插件的内存占用。通过主动监控,他们可以确认节点是否有足够内存承接高峰负载,避免因为内存不足导致性能下降。MySQL 官方文档也提供了关于 Group Replication 内存监控的说明。(MySQL)
对于很多团队来说,这是一个很现实的提醒:MGR 不是"免费"的高可用能力。它通过共识协议、事务缓冲、状态维护换来更强的可用性和一致性,因此容量规划、监控指标和告警规则也要跟着更新。
2. 谨慎使用 group_replication_bootstrap_group
SET GLOBAL group_replication_bootstrap_group=ON; 是启动新共识组时非常关键的命令,但它也很危险。错误使用这个命令可能导致 split-brain,也就是同一个集群中出现两个节点都认为自己是 Primary,最终造成数据不一致。(Uber)
Uber 的做法是对 bootstrap 采取非常严格的控制。自动化工作流在执行 bootstrap 前,会做两步确认。
第一,确认当前节点确实是新 group 的第一个节点,而不是已经属于某个现存 group。
第二,要求路由层也确认该节点就是预期中的 Primary。也就是说,不只是数据库节点自己判断,流量路由系统也要认可这个角色变化。
只有这两个条件都满足,bootstrap 才会被执行。这个设计可以避免误操作导致多个 Primary 并存,从而保护数据完整性。
八、这套方案的本质:让数据库集群自己维持目标状态
这篇文章表面上是在讲 MySQL Group Replication,但真正有价值的地方不只是 MGR 本身,而是 Uber 如何把 MGR 变成一个可运维、可自动恢复、可大规模推广的生产系统。
他们并没有满足于"手工搭一个三节点共识组",而是围绕 MGR 建了一整套控制平面:
接入集群时,自动选择 bootstrap 节点、加入成员、检查 ONLINE 状态、切换复制模式。
退出集群时,按顺序移除节点,并恢复到传统异步复制拓扑。
运行过程中,持续检查共识组成员数量和复制拓扑,把集群拉回三节点稳态。
删除节点前,先把它从共识组中 offboard,并重定向下游副本。
替换节点时,无论是 graceful 还是 ungraceful,都通过 hook 和 rebalance 流程保证共识组恢复稳定。
Primary 失败时,由 MGR 内部完成检测和选举,减少对外部故障处理链路的依赖。
Primary 切换期间,通过 BEFORE_ON_PRIMARY_FAILOVER 在一致性和可用性之间做明确取舍。
这些设计共同体现了一种生产系统思路:高可用架构不能只依赖"某个组件很强",还必须有自动化控制平面持续维护系统目标状态。否则,架构越复杂,人工操作风险越高;节点越多,拓扑漂移越频繁;规模越大,偶发问题越容易变成常态问题。
九、总结
Uber 这次 MySQL 高可用改造,本质上是从"外部系统检测故障,然后人工或自动化脚本救火",转向"数据库集群内部通过共识协议自我恢复"。第一篇文章讲的是为什么要引入 MGR,以及 MGR 在故障切换和一致性方面带来的架构收益;第二篇文章则补上了真正落地所需的运维能力:自动接入、自动退出、自动再平衡、节点删除处理、节点替换处理、Primary 选举、一致性参数选择以及 MGR 内存监控。
原文最后的结论是:在 Uber 的规模下推广 MySQL Group Replication,不只是一次技术替换,更是可靠性和运维方式的变化。借助自动化工作流和共识组机制,系统可以在故障发生时更快恢复,并减少工程师每次都介入处理的需求。(Uber)
从工程实践角度看,这篇文章最值得借鉴的地方有三点。
第一,高可用能力要内生化。外部检测和外部提升链路越长,故障恢复路径就越脆弱。让数据库集群内部通过共识完成 Primary 选举,可以显著减少对外部系统的依赖。
第二,架构变更必须配套控制平面。MGR 这样的技术只有和自动化接入、退出、再平衡、节点替换流程结合起来,才能真正进入大规模生产。
第三,可用性和一致性要显式选择。Primary failover 期间是立即开放新主库,还是等 backlog 应用完再开放,本质上是业务语义选择。Uber 选择 BEFORE_ON_PRIMARY_FAILOVER,说明他们更重视 Primary 读请求的严格外部一致性,即使这会带来一点点切换期间的可用性损失。
这也是很多基础设施系统设计中反复出现的主题:组件能力只是起点,真正决定系统可靠性的,是它在故障、变更、扩容、缩容和异常路径下,是否仍然能自动回到一个正确、稳定、可解释的状态。