1. 项目背景业务场景某金融支付平台的核心交易数据库经历了三次严重故障——第一次主库硬盘损坏手动切换到从库花了 45 分钟其中 30 分钟在核对数据一致性第二次主库网络闪断后恢复——但因为没配自动故障检测——出现了双主写入了 3 分钟——产生了 50 多笔重复交易第三次计划内的主库硬件升级——想在凌晨 2 点做切换——但操作失误导致整整 2 小时的停机。CTO 发话“我们需要一个能自动检测故障、自动切换、零数据丢失的高可用方案——不能再靠人手工操作了。”痛点传统主从复制 手工切换的高可用方案有以下致命缺陷切换靠人判断人是慢的、易出错的——宕机后先得确认真的挂了还是网络闪断→ 再执行切换 → 再验证数据——整个过程可能 30-60 分钟。切换后数据可能丢异步复制下主库宕机——binlog 还没传到从库的那部分事务永久丢失。脑裂风险网络分区后两边各选了一个主——都接受写入——等网络恢复后数据冲突需要人工逐一修复。无自动成员管理节点加入或退出集群都需要手动操作——运维负担随节点数线性增长。本章带你掌握 MySQL Group ReplicationMGR——原生高可用方案实现自动故障检测、自动选主、数据零丢失并配合 MySQL Shell 和 Router 完成应用层透明的故障切换。2. 项目设计【场景第三次故障的复盘会上小胖一脸无奈】小胖“大师为什么不能像 Redis Sentinel 一样——主库挂了自动切到从库为什么我们还要手工操作”大师“因为 MySQL 的数据一致性要求比 Redis 高得多。Redis Sentinel 是主从异步复制——切换时丢数据是’可接受的’。但你的数据库里是用户的支付流水——丢一笔就是几十万的法律风险——所以切换时必须保证新主库上的数据是全的——不能有丢掉的事务。”小白“那 Group Replication 是怎么做到’切换不丢数据’的”大师“Group Replication 的核心思路是——不只有一个主库、一个从库——而是一个 Group 中有 N 个节点各个节点之间组成一个分布式一致性集群。写请求必须先在超过半数多数派的节点上通过认证——只有多数派同意之后——事务才真正提交。所以即使原主库挂了——多数派中至少有一个节点有完整的事务记录——选出来的新主库不会丢数据。”技术映射MGR 基于 Paxos 变体Mencius 协议的分布式一致性集群。写事务需要多数派节点认证才能提交——保证零数据丢失。小胖“那单主模式和多主模式有什么区别我们该选哪种”大师“单主模式——Group 中只有一台节点接受写入Primary其他都是只读的 Secondary。Primary 挂了之后——Group 自动从 Secondary 中选出新的 Primary——对应用来说只有一个写入入口——逻辑简单——不会出现写入冲突。这是 99% 的应用场景。多主模式——Group 中所有节点都可以写入——需要应用自己处理写入冲突如自增 ID 冲突、唯一键冲突。只有当你真的需要多地同时写入的极端场景才用——比如跨洲部署的全球分布式系统。”技术映射单主 MGR 只有一个可写节点 自动选主切换推荐。多主 MGR 所有节点可写 冲突检测需业务配合。小白“那 MySQL Router 和 MySQL Shell 是干嘛的跟 MGR 配合”大师“MySQL Router 是一个轻量级的中间件——它知道 Group 中谁是当前的 Primary——应用连接 Router 时——Router 自动把请求转发到正确的节点。当 Primary 切换后——Router 自动感知并更新路由——应用不需要改代码或重启。MySQL Shell 是一站式管理工具——可以一键部署 MGR 集群dba.createCluster()、查看集群状态、执行切换操作。”3. 项目实战3.1 环境准备# Docker Compose 三节点 MGR 环境mkdir-p~/mysql-mgr/{node1,node2,node3}/{data,conf,logs}# 三台节点的配置仅 server-id、report-host 和端口不同foriin123;docat~/mysql-mgr/node${i}/conf/my.cnfEOF [mysqld] server-id${i}port3306 datadir/var/lib/mysql socket/var/run/mysqld/mysqld.sock # 基础复制配置 log-bin/var/log/mysql/binlog binlog_formatROW gtid_modeON enforce_gtid_consistencyON log_replica_updatesON master_info_repositoryTABLE relay_log_info_repositoryTABLE binlog_checksumNONE # Group Replication 配置 plugin_load_addgroup_replication.so loose-group_replication_group_nameaaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee loose-group_replication_start_on_bootOFF loose-group_replication_local_addressnode${i}:33061 loose-group_replication_group_seedsnode1:33061,node2:33061,node3:33061 loose-group_replication_bootstrap_groupOFF loose-group_replication_single_primary_modeON loose-group_replication_enforce_update_everywhere_checksOFF # 其他 report_hostnode${i}disabled_storage_enginesMyISAM,BLACKHOLE,FEDERATED,ARCHIVE EOFdone# docker-compose.ymlcat~/mysql-mgr/docker-compose.ymlEOF version: 3.8 services: node1: image: mysql:9.6.0 container_name: mgr-node1 hostname: node1 environment: MYSQL_ROOT_PASSWORD: Root123 volumes: - ./node1/data:/var/lib/mysql - ./node1/conf:/etc/mysql/conf.d - ./node1/logs:/var/log/mysql networks: mgr_net: ipv4_address: 172.25.0.11 node2: image: mysql:9.6.0 container_name: mgr-node2 hostname: node2 environment: MYSQL_ROOT_PASSWORD: Root123 volumes: - ./node2/data:/var/lib/mysql - ./node2/conf:/etc/mysql/conf.d - ./node2/logs:/var/log/mysql networks: mgr_net: ipv4_address: 172.25.0.12 node3: image: mysql:9.6.0 container_name: mgr-node3 hostname: node3 environment: MYSQL_ROOT_PASSWORD: Root123 volumes: - ./node3/data:/var/lib/mysql - ./node3/conf:/etc/mysql/conf.d - ./node3/logs:/var/log/mysql networks: mgr_net: ipv4_address: 172.25.0.13 networks: mgr_net: driver: bridge ipam: config: - subnet: 172.25.0.0/24 EOFcd~/mysql-mgrdockercompose up-dsleep303.2 分步实现步骤一初始化 Group Replication 集群-- 在所有节点上创建复制用户三台都执行-- docker exec -it mgr-node1 mysql -uroot -pRoot123SETSQL_LOG_BIN0;-- 创建用户不写 binlog——避免冲突CREATEUSERrepl%IDENTIFIEDBYRepl123;GRANTREPLICATIONSLAVEON*.*TOrepl%;GRANTCLONE_ADMIN,CONNECTION_ADMIN,BACKUP_ADMIN,GROUP_REPLICATION_STREAMON*.*TOrepl%;FLUSHPRIVILEGES;SETSQL_LOG_BIN1;-- 安装 Group Replication 插件如果还没自动加载INSTALL PLUGIN group_replicationSONAMEgroup_replication.so;-- 配置分布式恢复凭据CHANGEREPLICATIONSOURCETOSOURCE_USERrepl,SOURCE_PASSWORDRepl123FORCHANNELgroup_replication_recovery;-- 只在 node1引导节点执行 SETGLOBALgroup_replication_bootstrap_groupON;STARTGROUP_REPLICATION;SETGLOBALgroup_replication_bootstrap_groupOFF;-- 查看集群状态SELECT*FROMperformance_schema.replication_group_members;-- 预期-- node1:3306 | ONLINE | PRIMARY-- node2 和 node3 加入集群 -- docker exec -it mgr-node2 mysql -uroot -pRoot123-- 执行同样的复制用户创建 插件安装 CHANGE REPLICATION SOURCESTARTGROUP_REPLICATION;-- docker exec -it mgr-node3 mysql -uroot -pRoot123STARTGROUP_REPLICATION;-- 再次查集群状态SELECT*FROMperformance_schema.replication_group_members;-- 预期-- node1:3306 | ONLINE | PRIMARY-- node2:3306 | ONLINE | SECONDARY-- node3:3306 | ONLINE | SECONDARY步骤二模拟 Primary 故障——观察自动切换-- 步骤目标通过强制停止 Primary 节点观察自动选主过程-- 在 node1Primary上记录当前 GTIDSELECTglobal.gtid_executed;-- 模拟 Primary 宕机-- docker stop mgr-node1-- 在 node2 或 node3 上观察SELECT*FROMperformance_schema.replication_group_members;-- node1 的状态会变为 UNREACHABLE → 被移除-- node2 或 node3 被选举为新 PrimaryMEMBER_ROLE 从 SECONDARY 变为 PRIMARY-- 这个过程通常在 5-30 秒内完成取决于 group_replication_member_expel_timeout-- 在新 Primary 上验证——写入不受影响-- docker exec -it mgr-node2 mysql -uroot -pRoot123USEecommerce;CREATETABLEmgr_test(idINTPRIMARYKEY,valINT);INSERTINTOmgr_testVALUES(1,100);-- 在新的 Secondary 上验证——数据已同步-- docker exec -it mgr-node3 mysql -uroot -pRoot123SELECT*FROMecommerce.mgr_test;-- 应能看到 (1, 100)-- 恢复 node1——它会自动重新加入集群作为 Secondary-- docker start mgr-node1-- 等待 10 秒后查看集群状态SELECT*FROMperformance_schema.replication_group_members;-- node1 应重新出现状态为 ONLINE | SECONDARY步骤三用 MySQL Shell 一键管理集群# 步骤目标使用 MySQL Shell 的 AdminAPI 简化集群运维# 连接到任意节点dockerexec-itmgr-node1 mysqlsh-uroot-pRoot123# 在 MySQL Shell 中# 查看集群状态# \sql SELECT * FROM performance_schema.replication_group_members;# JS 模式下的集群 API# \js# var cluster dba.getCluster();# cluster.status();# 输出类似# {# clusterName: myCluster,# defaultReplicaSet: {# primary: node2:3306,# status: OK,# topology: {# node1:3306: { mode: R/O, status: ONLINE },# node2:3306: { mode: R/W, status: ONLINE },# node3:3306: { mode: R/O, status: ONLINE }# }# }# }# 手动切换 Primary (switchover)# cluster.setPrimaryInstance(node1:3306);# 预期输出node1 被选举为新 Primary原 Primary 降为 Secondary# 查看集群状态变化# cluster.status();步骤四MySQL Router 部署与透明路由# 步骤目标部署 MySQL Router 实现应用透明的读写分离和故障切换# 使用 Router 的 bootstrap 功能——自动获取集群拓扑dockerrun-d--namemysql-router\--networkmysql-mgr_mgr_net\mysql/mysql-router:9.6.0\--bootstraproot:Root123mgr-node1:3306\--namemy_router# Router 自动配置两个端口# - 6446读写端口自动路由到 Primary# - 6447只读端口轮询路由到 Secondary# 应用连接 Router 的读写端口# mysql -h127.0.0.1 -P6446 -uroot -pRoot123# 所有的读写操作自动转发到当前的 Primary# 应用连接 Router 的只读端口# mysql -h127.0.0.1 -P6447 -uroot -pRoot123# 所有的只读操作轮询到 Secondary 节点# 验证当 Primary 宕机后Router 自动把 6446 端口重定向到新 Primary步骤五监控集群健康——告警规则# mgr_monitor.py# 步骤目标监控 MGR 集群健康异常时推送告警importmysql.connectorimporttime NODES[{host:127.0.0.1,port:3311,user:root,password:Root123},{host:127.0.0.1,port:3312,user:root,password:Root123},{host:127.0.0.1,port:3313,user:root,password:Root123},]defcheck_cluster(node_config):检查单个节点的集群状态try:connmysql.connector.connect(**node_config,connect_timeout3)curconn.cursor(dictionaryTrue)cur.execute(SELECT MEMBER_HOST, MEMBER_PORT, MEMBER_STATE, MEMBER_ROLE FROM performance_schema.replication_group_members)memberscur.fetchall()cur.close()conn.close()returnmembersexceptExceptionase:returnNonedefmonitor():online_count0has_primaryFalsefornodeinNODES:memberscheck_cluster(node)ifmembers:online_count1forminmembers:ifm[MEMBER_ROLE]PRIMARYandm[MEMBER_STATE]ONLINE:has_primaryTruealerts[]ifonline_count2:alerts.append(fOnly{online_count}/3 nodes reachable)ifnothas_primary:alerts.append(No PRIMARY node found!)ifonline_count3andhas_primary:print(f[OK] Cluster healthy:{online_count}nodes online, PRIMARY exists)returnalertsif__name____main__:whileTrue:alertsmonitor()forainalerts:print(f[ALERT]{a})time.sleep(10)3.3 测试验证# 验证清单# 1. 确认所有节点在线fornodeinmgr-node1 mgr-node2 mgr-node3;doecho$nodedockerexec$nodemysql-uroot-pRoot123-e SELECT MEMBER_HOST, MEMBER_PORT, MEMBER_STATE, MEMBER_ROLE FROM performance_schema.replication_group_members; done# 2. 验证数据同步延迟# 在 Primary 插入数据dockerexecmgr-node1 mysql-uroot-pRoot123-e CREATE DATABASE IF NOT EXISTS ecommerce; CREATE TABLE IF NOT EXISTS ecommerce.mgr_sync_test (id INT, ts DATETIME(3)); INSERT INTO ecommerce.mgr_sync_test VALUES (1, NOW(3)); # 在其他节点检查sleep1fornodeinmgr-node2 mgr-node3;doecho$nodedockerexec$nodemysql-uroot-pRoot123-eSELECT * FROM ecommerce.mgr_sync_test;done# 3. 测试故障切换dockerstop mgr-node1sleep15dockerexecmgr-node2 mysql-uroot-pRoot123-e SELECT MEMBER_HOST, MEMBER_ROLE FROM performance_schema.replication_group_members; # 预期node1 OFFLINE/UNREACHABLE, node2 或 node3 为 PRIMARY# 恢复 node1dockerstart mgr-node1sleep20dockerexecmgr-node2 mysql-uroot-pRoot123-e SELECT MEMBER_HOST, MEMBER_ROLE FROM performance_schema.replication_group_members; # 预期node1 重新加入作为 SECONDARY# 4. 运行监控脚本python mgr_monitor.py4. 项目总结优点 缺点维度优点缺点/局限自动故障检测与切换5-30 秒完成选主无需人工介入网络分区时需等group_replication_member_expel_timeout默认 5 秒零数据丢失基于 Paxos 的多数派认证——RPO0写入延迟比异步复制高需要至少一个其他节点 ACKMySQL Router应用透明的路由自动感知 Primary 切换需要额外部署组件Router 自身需要高可用两个 Router Keepalived单主模式逻辑简单兼容现有应用写入只能走 Primary——写扩展受限于单节点性能多主模式所有节点可写写入弹性好冲突检测 回滚代价高自增字段需特殊配置适用场景金融/支付核心库不能丢数据 自动切换——MGR 单主模式 Router。电商订单库写少读多——MGR 单主 多个 Secondary 分摊读取。多地容灾3 节点跨 3 个机房——任意一个机房故障不影响服务。数据库即服务DBaaS云平台自动为用户创建 MGR 集群——开箱即用的高可用。读扩展为主Secondary 节点线性扩展读能力——读写分离 Router 自动负载均衡。不适用场景写远大于读单主模式写入只能走 Primary吞吐受限于单节点 → 考虑分库分表或 NewSQL如 TiDB。极低延迟要求Paxos 通信增加 1-2ms 的提交延迟——敏感业务可考虑异步复制 人工切换。跨洲广域网部署多数派协议对延迟敏感——跨大洲的 RTT 100ms 会导致写入 TPS 急剧下降。注意事项节点数必须是奇数3、5、7多数派 floor(N/2) 1偶数节点容易脑裂。group_replication_group_name必须是有效 UUID集群内所有节点必须一致。MySQL Router 的 bootstrap 端口 6446/6447 可能冲突部署前确认这些端口未被占用。常见踩坑经验故障案例一两个节点网络断开后都自称 Primary——双主写入。根因只有 2 个节点存活——多数派无法形成——双方都无法知道对方状态。修复最少 3 个节点——确保任意网络分区下最多只有一个分区有多数派。故障案例二节点日志大小不一致导致无法加入集群。根因节点掉线太久——集群的 binlog 被 purge 了——掉线节点的 GTID 已经落后到无法通过 binlog 补齐。修复使用 Clone Plugingroup_replication_recovery_use_cloneON做全量快照恢复。故障案例三写入 TPS 比单机低 30%。根因MGR 的 Paxos 通信增加了提交延迟——group_replication_flow_control_modeQUOTA可能限速。修复调整group_replication_flow_control_modeDISABLED允许短暂的不一致或调大group_replication_flow_control_certifier_threshold。思考题MGR 的单主模式下如果 Primary 产生了一个大事务100 万行 UPDATE——提交之前宕机了——这个事务对其他 Secondary 有影响吗为什么在跨 3 个机房的 MGR 集群中——如果机房间的延迟分别是 A→B 2ms、A→C 50ms、B→C 55ms——Primary 在 A 机房——写入的平均提交延迟是多少答案提示第 1 题——无影响事务在 Primary 宕机前未提交——binlog 未广播到其他成员——其他节点不受影响第 2 题——约 2ms只需多数派 ACK即 A 本地 BA→B2msC 的 ACK 不参与提交路径。延伸阅读与资源Java 工程师进阶从 JVM 生产排障到OpenJDK原理NumPy 从入门到生产落地全链路实战指南科学计算/向量化Redis 8 实战精讲从 CRUD 到源码构建高可用缓存系统Redis 实战修炼与原理进阶Python 3实战精进从脚本到高并发订单引擎python入门Rquests从菜鸟脚本到企业级SDK的网络实战圣经Milvus向量数据库实战修炼从 0 到 1精通向量检索与生产落地MongoDB 实战进阶与内核修炼后端工程师的 AI 转型第一课Ollama 与私有化大模型实战10倍开发者的 Dify 魔法书从零构建全栈 AI 应用后端工程师转型AI第一课-Ollama 与私有化大模型实战大型语言模型(LLM) vLLM 高性能推理落地实战Agent开发之LlamaIndex 实战修炼与源码进阶大语言模型Transformers 实战修炼与源码剖析