Group Replication 与 InnoDB Cluster
提出问题
传统 MySQL 主从复制采用异步模式,主库提交事务后不等从库确认就返回客户端,一旦主库宕机,尚未同步到从库的数据就会丢失。半同步复制虽然解决了部分丢数据问题,但方案层面仍有几个硬伤:没有自动故障转移,需要挂靠外部工具(MHA、Orchestrator);主从切换后的数据一致性无原子保证——启动 MySQL 前要手动 SET GLOBAL gtid_purged、CHANGE MASTER TO,一旦 GTID 设置错位就是数据不一致;多个从库之间缺乏统一的复制状态管理,主库挂了只能靠运维一个个检查 Seconds_Behind_Master。
Group Replication(MGR)是 MySQL 5.7.17 起官方推出的原生高可用方案,核心用 Paxos 变种做组通信,把"提交确认"从单节点决策变成了群体决策。InnoDB Cluster 是 MGR 上层的完整解决方案,三件套:MGR + MySQL Router + MySQL Shell,让开发者用几条命令就能部署一个自动故障转移的 MySQL 集群,不需要外围配套工具。
分析问题
MGR 的组通信协议:XCom 与 Paxos 变种
MGR 的组通信层叫 XCom,它基于 Paxos 的变种——确切说是 Paxos 的 Multi-Paxos 优化版,外加 ZAB 风格的 Leader Lease。整个流程分三个阶段:
- Propose:Primary 节点(Leader)将事务的 binlog 事件打包成一个消息,广播给组内所有节点。
- Majority Ack:每个节点收到后,将消息写入 relay log 并回复 ACK。当 Primary 收到多数派(N/2 + 1)的 ACK 后,该消息在组内达成"共识"。
- Commit:Primary 提交本地事务,同时广播一个 Commit 消息,其他节点收到后也提交。
关键优化点:
- Paxos 的 Prepare 阶段被省略:在单主模式下,Primary 持有 Leader Lease(租约),不需要每次事务都走完整的 Prepare → Accept 两阶段。Leader Lease 有 TTL,Primary 必须在 TTL 过期前续约,续约失败则触发重新选举。
- 消息打包 + 批处理:XCom 会把多个事务打包到一个 Paxos 实例中,减少网络往返。
- 乐观并发控制:多主模式下,事务先在本地执行,提交时再做冲突检测,避免分布式锁的开销。
时序图:单主模式事务提交流程
App → Primary: BEGIN / INSERT / UPDATE
Primary → Group: Propose(事务的 binlog 事件)
Group → Node2: 收到 Propose
Node2 → Primary: ACK
Group → Node3: 收到 Propose
Node3 → Primary: ACK
Note over Primary: 收到多数派 ACK → 事务达成共识
Primary → Group: Commit 广播
Primary → App: OK / 返回客户端
Node2 → Node2: 提交本地事务
Node3 → Node3: 提交本地事务这个流程意味着:Primary 拿到客户端响应的时候,事务已经在多数派节点上持久化了。即使 Primary 下一秒宕机,新 Primary 也能拿到这个事务的 binlog 继续执行,不会丢数据。
单主模式与多主模式
单主模式(Single-Primary):只有一个节点可写,其余节点只读。这是默认模式,也是生产推荐模式。组内自动选举一个 Primary 节点(通常是最早加入的,或通过 group_replication_member_weight 权重选举),其他节点作为 Secondary。多主模式下的写冲突风险和分布式死锁在这里完全不存在。
多主模式(Multi-Primary):所有节点都可写,适合地理分布较近、读写分散的场景。但多主模式引出了分布式事务冲突检测问题——MySQL 在事务提交时做乐观锁检测:如果两个事务修改了同一行(通过行级锁的 wait-for graph 判断),后提交的事务会被回滚,返回 DEADLOCK 或 ER_LOCK_DEADLOCK 错误。这要求应用程序做好重试逻辑。
-- 查看 MGR 模式
SELECT * FROM performance_schema.replication_group_members;
-- 查看当前 Primary
SELECT MEMBER_HOST, MEMBER_PORT, MEMBER_ROLE
FROM performance_schema.replication_group_members
WHERE MEMBER_ID = (
SELECT VARIABLE_VALUE
FROM performance_schema.global_status
WHERE VARIABLE_NAME = 'group_replication_primary_member'
);性能对比数据(基于 sysbench oltp_read_write,3 节点同机房,2 核 4G):
| 模式 | TPS(QPS) | P99 延迟 | 说明 |
|---|---|---|---|
| 异步复制 | 8500 | 3ms | 主库写完即回,不保证不丢 |
| 半同步复制 | 6200 | 5ms | 至少一个从库确认,延迟略高 |
| MGR 单主模式 | 4800 | 8ms | 多数派确认,写入吞吐下降约 40% |
| MGR 多主模式(无冲突) | 8200 | 6ms | 写分散到多节点,但冲突检测有开销 |
| MGR 多主模式(高冲突) | 1200 | 45ms | 大量回滚重试,性能急剧下降 |
踩坑:某支付系统上线 MGR 多主模式,双机房部署,业务上两个机房同时写同一张订单表,冲突率 30%,TPS 从 5000 掉到 800,一周后老老实实切回单主。
与传统主从复制的核心差异
| 维度 | 异步/半同步复制 | Group Replication |
|---|---|---|
| 一致性 | 最终一致(异步可能丢数据) | 强一致(多数派确认) |
| 自动故障转移 | 需要外部工具(MHA/Orchestrator) | 原生支持,自动选举新 Primary |
| 节点角色 | 主库写,从库读 | 单主下同,切换自动化 |
| 延迟 | 主从可能延迟,尤其异步 | 多数派确认后才提交,延迟可控 |
| 扩容 | 在线添加从库,对主库影响小 | 在线添加节点,需同步已有数据(Clone Plugin 或增量 binlog) |
| 网络要求 | 容忍较大延迟(跨机房可用) | 要求低延迟高带宽(10ms 内最佳,超 30ms 提交延迟暴增) |
| 切换 RTO | 秒级到分钟级(依赖 MHA 探测) | 秒级(自动选举,Router 感知一般在 5-15s) |
传统主从复制的最大问题是主库提交即成功,从库追不追得上不管。MGR 把"提交"这个动作从单节点决策变成了群体决策,崩溃恢复后没有数据分歧。代价是写入吞吐下降约 30%-50%,不适合纯写入密集型场景。
InnoDB Cluster 完整方案
InnoDB Cluster = MGR(组复制)+ MySQL Router(流量路由)+ MySQL Shell(管理工具)
MySQL Shell 提供 AdminAPI,通过命令即可创建和扩展集群:
# 使用 MySQL Shell 创建集群
$ mysqlsh
mysql-js> cluster = dba.createCluster('myCluster');
mysql-js> cluster.addInstance('user@mysql2:3306');
mysql-js> cluster.addInstance('user@mysql3:3306');
mysql-js> cluster.status();
# 配置 Router
$ mysqlrouter --bootstrap root@localhost:3306 --user=mysqlrouter新节点加入的分布式恢复流程:
- 新节点通过
dba.addInstance()加入组 - 如果新节点数据落后太多(GTID 差距超过
group_replication_clone_threshold,默认 50MB),自动触发 Clone Plugin 全量拷贝 - Clone Plugin 从捐赠节点(donor)拷贝 InnoDB 数据文件,期间不阻塞捐赠节点正常服务
- 全量拷贝完成后,新节点通过增量 binlog 追赶 Clone 期间产生的新事务
- 新节点追上后,进入 Online 状态,开始接收组通信消息
MySQL Router 的工作原理:
Router 是一个轻量级代理,部署在应用层和 MySQL 集群之间。它启动时通过 --bootstrap 从集群元数据中获取拓扑信息,然后定期(默认 5 秒)轮询集群状态。当 Primary 切换时:
时序图:Primary 切换 + Router 感知
Node1 (Primary) → Node1: 宕机
Node2 → Group: 发现 Leader 失联
Node3 → Group: 发现 Leader 失联
Node2 → Group: 发起选举请求
Group → Node2: 获得多数派投票
Node2 → Node2: 成为新 Primary
Node2 → Group: 广播新 Primary 身份
Router → Group: 轮询(5 秒间隔)感知到新 Primary
Router → Router: 将写流量切换到 Node2
App → Router: 写入请求 → Router 转发到 Node2应用完全感知不到后端切换,除了那 5-15 秒内的写请求可能因 Router 未感知而短暂失败,需要在应用层做重试(一般用连接池的自动重连 + 指数退避)。
脑裂与多数派
MGR 依赖组通信的多数派机制来避免脑裂。如果集群是 3 个节点,当网络分区发生时,只有能凑齐 2 个节点的分区可以继续服务,孤立的节点或单节点分区会被自动踢出组并拒绝写入。
网络分区场景分析(3 节点):
场景 A:网络抖动,某个节点短暂失联
- 多数派(2 节点)继续服务
- 失联节点超时(group_replication_member_expel_timeout,默认 5 秒)后被踢出组
- 节点恢复后自动重新加入,通过增量 binlog 追赶
场景 B:两个节点之间网络中断,但都与第三个节点联通
- 形成两个分区,每个分区各 1 个节点,都凑不齐多数派
- 两个分区都停止写入,只提供只读
- 网络恢复后自动合并
场景 C:机房级故障,整个机房失联
- 幸存机房有 2 个节点,凑齐多数派,继续服务
- 失联节点被踢出,恢复后重新加入生产中最常见的配置是 3 个节点,刚好容忍 1 个节点宕机。如果部署 5 个节点,可以容忍 2 个节点宕机。但节点越多,提交延迟越高——因为需要等待更多节点的 ACK。
避坑指南(面试常问)
1. 大事务会导致组复制挂起
MGR 的事务大小受 group_replication_transaction_size_limit 限制,默认 150MB。超过这个阈值的事务会被直接拒绝。但更关键的是:大事务会让组通信卡住,因为 XCom 需要等整个事务的 binlog 消息传输完成才能做 Paxos 确认。如果业务上有个 DELETE 删了 1000 万行,MGR 集群可能会卡住几十秒。
-- 设置事务大小限制(字节)
SET GLOBAL group_replication_transaction_size_limit = 52428800; -- 50MB2. 异步通道的断路器
如果 MGR 组内节点之间的网络延迟 > 10ms,不要用 MGR。我见过一个案例:某团队把 MGR 部署在跨城专线上(延迟约 15ms),写性能从 3000 TPS 掉到 800 TPS,大量事务因为 ACK 超时被回滚,最后不得不切回半同步 + MHA。
3. 不要与异步复制混用
MGR 组内节点不能用传统异步复制再挂从库。MGR 自己是一个完整的复制拓扑,在其外部再挂异步从库会导致 GTID 空间混乱。如果确实需要只读扩展,用 InnoDB Cluster 的 Read Replicas 功能(MySQL 8.0.22+)或通过 Router 在集团外挂只读实例。
4. gtid_mode 必须 ON
MGR 要求 gtid_mode=ON、enforce_gtid_consistency=ON、binlog_format=ROW。这三项必须在配置文件里写死,MySQL Shell 创建集群时会自动校验,不符合的话直接报错。
总结
- MGR 基于 XCom+Multi-Paxos 变种实现组通信,事务提交需要多数派确认,保证了强一致性和自动故障转移
- 单主模式是生产推荐;多主模式在冲突率 > 5% 的场景下性能急剧下降,慎重使用
- InnoDB Cluster 三件套(MGR + Router + Shell)一键部署,RTO 5-15s,适合中小规模高可用场景
- 3 节点集群是成本与可用性的最佳平衡点,容忍单节点故障
- MGR 对网络延迟敏感,跨机房部署延迟 > 10ms 时提交延迟会显著增加
- 写入吞吐通常比异步复制下降 30%-50%,适合读写比 7:3 以上的场景;不适合纯写入密集型
- 大事务、异步通道超时、MGR 外加异步从库是三个最常见的踩坑点
参考
参考:MySQL 官方文档 - Group Replication;MySQL Shell AdminAPI 文档;MySQL Router 用户指南;XCom 源码注释(mysql-group-replication plugin)