4184 字
21 分钟
MySQL 高可用详解(2)故障转移

第一篇解决了数据有副本,这一篇解决主库倒了怎么办。

本文行文逻辑:什么是故障转移 → 如何完成故障转移/主从切换 → 单主架构需要主从切换,那么多主架构呢 → 多主架构的注意事项

故障转移#

复制只保证了”从库有数据”,但主库宕机时:

  • 应用连接的是主库地址,主库不可用 = 服务不可写;
  • 没有人去把某个从库提升为新主,服务就无法自愈。

故障转移(Failover) 就是把这两件事自动化:发现故障 → 选出新主 → 切换流量 → 重建拓扑。

故障检测#

核心原则:避免误判。

故障检测的输入通常有四个来源:

探测方式做法误判风险
实例探活TCP 连通、SELECT 1、mysqladmin ping网络抖动 / 慢查询把连接池占满会误判
复制链路Replica_IO_Running、Last_IO_Error、Seconds_Behind_Master单看延迟会误判(见第一篇)
代理健康检查ProxySQL / MySQL Router / HAProxy 的后端健康探测探测间隔与超时设置不当会引发抖动
仲裁裁决多数派投票、仲裁节点、独立 witness无仲裁的 1 主 1 从结构天然只能”人工确认”

工程上的判定原则:

  1. 连续多次失败才算故障:例如探测间隔 1s、连续 3 次失败、总超时 10s 才判定,避免瞬时抖动触发切换。
  2. 区分故障类型:进程崩溃(可自动切换)/服务器宕机(需要确认不会”假死复活”)/网络分区(可能脑裂,必须靠仲裁)。
  3. 切换前先”隔离”旧主:通过 STONITH(关机/断网/关闭 MySQL)确保旧主不会再接受写入,否则双写必然冲突。
  4. 误判的代价不对称:漏切只是服务不可用(可人工介入),误切会丢数据 + 双写 + 拓扑错乱。因此宁可保守。

主从切换#

切换流程#

计划内切换(如升级、机房搬迁)和计划外切换(故障)步骤一致,区别只在于能否”优雅地”执行:

  1. 确认故障与旧主状态:旧主是否彻底不可写;若还活着,先 SET GLOBAL super_read_only=ON 或直接停实例。
  2. 选出新主:在候选从库中挑一个——优先选延迟最小 / GTID 最完整 / 规格最好的节点。候选集必须排除延迟过大的从库(否则提升即丢数据)。
  3. 补齐数据(可选):把其他从库中比候选新主多出来的事务,在新主上补齐(用 binlog 手工重放或 gtid_purged 对齐)。
  4. 提升新主:STOP REPLICA; RESET REPLICA ALL; SET GLOBAL read_only=OFF; SET GLOBAL super_read_only=OFF;,并确保绑定 VIP / 写入 DNS 或代理后端。
  5. 重定向其他从库:GTID 模式下 CHANGE REPLICATION SOURCE TO ... SOURCE_AUTO_POSITION=1 即可,不需要位点。
  6. 切换流量与收尾:应用通过 VIP/Proxy 重连(连接池要能自动摘除死连接);清理旧主的复制元数据;记录故障时间线用于复盘。

谁来执行切换#

方案检测切换适用
人工 + 脚本由监控告警触发人执行既定脚本规模小、可接受 RTO 分钟级
MHA监控进程轮询自动 promote + 重建从库传统一主多从,需 Manager 节点
Orchestrator拓扑发现 + 故障检测自动选主、支持多级拓扑与钩子被许多云厂商与 Vitess 采用
MySQL Router + MGR组内成员状态Router 自动识别新主并改路由与 MGR 配套的官方方案
ProxySQL 等代理后端健康检查把写组切到新主需要外部触发提升主库
云托管 RDS云厂商内部一键/自动切换,暴露一个写域名不想自建时最省事

选型的核心是:谁拥有”选主”的裁决权。代理层只负责把流量导到”当前主”,提升动作必须有唯一权威来执行,否则多个组件同时切换就是灾难。

切换时最容易出的事故:延迟中的提升#

假设异步复制下,主库在执行 T1~T100 时宕机,从库只重放到了 T80:

  • 提升该从库为新主,T81~T100 永久丢失;
  • 如果应用已经基于 T100 的结果做了后续写入,就会产生”凭空出现”的业务异常。

工程对策:

对策说明
用 GTID 量化差距对比各从库 Executed_Gtid_Set,找”最全”的节点提升
半同步作为前置rpl_semi_sync_source_wait_for_replica_count=1,把丢失窗口压到接近 0
用 MGR 替代异步多数派确认写,从机制上不允许”已提交但只在一个节点”
切换前校验提升前比对 gtid_executed,与预期集合不符则中止并告警
应用侧幂等关键业务用幂等键,降低”丢事务 + 重放”带来的破坏

切换后的数据校验#

切换完成后应立刻做一轮校验,而不是等业务报错:

  • 新旧主之间的 gtid_executed 差异集合是否为空;
  • pt-table-checksum 抽样比对核心表;
  • 应用侧关键指标(下单量、写入成功率)是否恢复;
  • 复制链路延迟是否归零。

切换脚本与演练#

自动切换脚本必须满足:

  1. 幂等:重复执行不产生额外破坏(例如”已经只读”就跳过)。
  2. 可中止:任何一步校验失败就停下并告警,而不是硬着头皮往下走。
  3. 有审计:每一步记录到日志/事件系统,事后可复盘。
  4. 定期演练:不演练的切换脚本等于不存在。演练至少每季度一次,覆盖”主库进程被杀""主库整机断电""网络分区”三类场景。

多主架构#

集群主从复制方案。一主多从的写入口只有一个,提升需要外部裁决。

多主架构试图让多个节点都能写:写节点不再唯一,切换就退化成”把流量挪到另一个健康节点”。

形态一致性模型写冲突处理典型实现
双主 + VIP异步(仍有丢数据窗口)靠”同一时刻只让一个节点对外写”规避keepalived + MySQL 双主
同步多主(认证型)乐观并发,行级认证后提交的事务被回滚并报错MGR(多主模式)、Galera Cluster、Percona XtraDB Cluster
分片多主分片内单主分片间不冲突(分片键隔离)Vitess、ShardingSphere + 多主
分布式数据库Raft/Paxos 共识由共识层串行化TiDB、OceanBase

可以看到,真正”任意节点随时可写且不冲突”的方案,本质都是把冲突交给共识层或认证层裁决,而不是靠复制兜底。

MGR 组复制#

MGR(MySQL Group Replication)是 MySQL 5.7.17 起的官方高可用方案,8.0 已成熟,常与 MySQL Shell、MySQL Router 组成完整链路。

核心机制:

  1. 组通信(Group Communication System, GCS):基于 Paxos 变体(XCom)实现消息全序广播,保证所有成员看到相同顺序的事务流。
  2. 认证(Certification):每个成员对收到的写集做冲突检测(行级、基于主键/唯一键的哈希比对),有冲突的事务在提交前被拒绝并回滚。
  3. 原子广播 + 两阶段提交:事务先在所有成员上认证通过,再统一提交;多数派((N/2)+1)可达才提交,因此不会出现网络分区两侧同时写。
  4. 分布式恢复:新成员加入时用 group_replication_recovery 通道自动从现有成员补齐差异(增量不够则触发全量克隆,8.0.17+ 支持 clone 插件)。

基础配置(所有节点一致):

/etc/my.cnf(MGR 节点)
[mysqld]
server_id = 1
gtid_mode = ON
enforce_gtid_consistency = ON
binlog_checksum = NONE # MGR 要求
log_replica_updates = ON
binlog_format = ROW
binlog_transaction_dependency_tracking = WRITESET
transaction_write_set_extraction = XXHASH64
group_replication_group_name = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' # 合法 UUID,且全组统一
group_replication_start_on_boot = OFF
group_replication_local_address = '10.0.0.11:33061' # 组通信端口,非业务端口
group_replication_group_seeds = '10.0.0.11:33061,10.0.0.12:33061,10.0.0.13:33061'
group_replication_single_primary_mode = ON # 默认单主;OFF 为多主

启动与运维:

-- 复制账号(组内成员互相恢复时使用)
SET SQL_LOG_BIN = 0;
CREATE USER 'mgr'@'10.0.0.%' IDENTIFIED BY 'MgrPassw0rd!';
GRANT REPLICATION SLAVE ON *.* TO 'mgr'@'10.0.0.%';
SET SQL_LOG_BIN = 1;
CHANGE REPLICATION SOURCE TO
SOURCE_USER = 'mgr', SOURCE_PASSWORD = 'MgrPassw0rd!'
FOR CHANNEL 'group_replication_recovery';
-- 首个节点引导(只在一个节点执行一次!)
SET GLOBAL group_replication_bootstrap_group = ON;
START GROUP_REPLICATION;
SET GLOBAL group_replication_bootstrap_group = OFF;
-- 其余节点直接加入
START GROUP_REPLICATION;
-- 观察成员状态,所有成员应为 ONLINE
SELECT member_id, member_host, member_state, member_role
FROM performance_schema.replication_group_members;

也可以用 MySQL Shell 的 AdminAPI 省掉大部分细节:

// mysqlsh --uri root@10.0.0.11:3306
dba.configureInstance('root@10.0.0.11:3306');
dba.createCluster('mycluster');
mycluster.addInstance('root@10.0.0.12:3306');
mycluster.status();

关键参数与状态:

参数 / 状态作用
group_replication_single_primary_modeON 单主(推荐)、OFF 多主
group_replication_enforce_update_everywhere_checks多主模式下强制检查(串行化隔离级别、禁用级联外键等)
group_replication_consistency一致性等级:EVENTUAL(默认,不等待)、BEFORE_ONLINE(写等待本地成员前滚完)、AFTER(写等多数派应用)、BEFORE(读等积压事务应用完)、BEFORE_AND_AFTER(最强,延迟最高)
group_replication_member_expel_timeout成员被怀疑到被驱逐的时间,跨机房部署需调大
group_replication_exit_state_action被驱逐后行为:READ_ONLY(默认,留作只读)/ ABORT_SERVER(直接退出,避免误连写)
MEMBER_STATEONLINE / RECOVERING / ERROR / UNREACHABLE / OFFLINE
group_replication_primary_member当前主(单主模式)

MGR 的现实限制:

  • 写必须经过多数派网络往返,跨地域部署(RTT 数十毫秒)会直接把写入延迟放大一个数量级,一般只在同城三机房内使用。
  • 组越大,认证与广播开销越高,实践中 3~5 节点 最常见,需要扩展读能力时应在组外再挂普通异步从库。
  • 认证是乐观的:多主模式下冲突事务直到提交才报错,应用必须能处理”提交失败并重试”。
  • DDL 需要人工协调(8.0 支持部分原子 DDL,但跨表结构变更仍需按节点滚动执行)。

Galera Cluster#

Galera Cluster(含 Percona XtraDB Cluster、MariaDB Galera Cluster)是最早流行的同步多主方案,通过 Galera 复制插件实现。

核心机制:

  1. 认证(Certification-Based Replication):事务在本地执行,把”写集”广播给所有节点;每个节点基于写集做冲突检测,通过则提交。没有主从之分,无需 binlog 传输。
  2. 全同步:已提交事务在所有可用节点上都已应用(代价是提交延迟 = 最慢节点 + 网络 RTT)。
  3. 流控(Flow Control):wsrep_flow_control_* 参数让快节点等慢节点,避免落后节点被拖垮。
  4. 状态转移(SST / IST):新节点加入时通过 SST 全量(xtrabackup-v2、mariabackup、rsync)或 IST 增量(基于 gcache)同步。
/etc/my.cnf(Galera 节点)
[mysqld]
binlog_format = ROW
default_storage_engine = InnoDB # Galera 只支持 InnoDB
innodb_autoinc_lock_mode = 2 # 交错模式,多主必备
wsrep_on = ON
wsrep_provider = /usr/lib64/galera-4/libgalera_smm.so
wsrep_cluster_name = 'galera-prod'
wsrep_cluster_address = 'gcomm://10.0.0.11,10.0.0.12,10.0.0.13'
wsrep_node_address = '10.0.0.11'
wsrep_node_name = 'node1'
wsrep_sst_method = xtrabackup-v2
wsrep_sst_auth = 'sstuser:SstPassw0rd!'
-- 集群状态:关键指标
SHOW STATUS LIKE 'wsrep_cluster_size'; -- 集群成员数
SHOW STATUS LIKE 'wsrep_cluster_status'; -- Primary / non-Primary
SHOW STATUS LIKE 'wsrep_local_state_comment'; -- Synced / Donor / Joiner / Joined
SHOW STATUS LIKE 'wsrep_ready'; -- ON 才能接受写入
SHOW STATUS LIKE 'wsrep_flow_control_paused'; -- 流控暂停比例,越高越说明有节点跟不上
SHOW STATUS LIKE 'wsrep_certification_failures';-- 认证冲突次数

Galera 的实际约束(比 MGR 更严格):

  • 只支持 InnoDB(MyISAM 等非事务引擎写入不受保护),每张表必须有主键。
  • 不支持 XA 事务,也不支持表级锁与显式锁(LOCK TABLES、GET_LOCK());多主模式下”锁定再写”的语义与其他节点不一致。
  • 主键自增需 innodb_autoinc_lock_mode=2,但多主下自增 ID 仍会有跳号/交错。
  • 写入延迟随集群规模上升(同步复制),节点数不宜多。
  • 需要奇数节点(3、5、7)来保证多数派;剩 2 个节点的 3 节点集群若再挂一个就失去 quorum,集群变为 non-Primary 拒绝写入。

注意事项#

多主写入的代价#

多主”看起来”能提升写能力,实际上:

  1. 写冲突无法避免:两个节点同时改同一行(或同一唯一键),只有一个能提交,另一个报 Deadlock / Lock wait timeout 之类的认证失败。业务代码必须能安全重试。
  2. 重试需要幂等:INSERT 冲突重试要处理 1062;UPDATE ... SET x = x + 1 这类语句天然幂等,但”读-改-写”的业务逻辑必须靠乐观锁版本号或唯一键约束。
  3. 多主不提升写吞吐:每个写都要广播给所有节点并在所有节点认证,集群写能力实际受最慢节点约束,热点行写入甚至比单主更差。
  4. 自增/序列不连续:交错自增导致 ID 跳号,某些业务对连续 ID 有假设时要提前评估。

结论:绝大多数场景应使用单主(MGR 单主模式或多主但只让一个节点对外写),多主的价值在于”任一节点都能接写”的容错性,而非性能。

脑裂(Split-Brain)#

脑裂指网络分区后,两侧都认为自己是合法主库并各自接受写入,造成相互冲突的两份数据。

方案脑裂是否可能机制说明
异步/半同步主从 + 脚本切换可能脚本与旧主失联但旧主其实活着,两人同时当主
MGR基本不可能提交需多数派;失去多数的分区自动变为只读,无多数派则不可写
Galera基本不可能需要 quorum;少数派分区 wsrep_cluster_status 变为 non-Primary,拒绝写
双主 + keepalived可能VIP 漂移期间两边都可能持有 VIP,必须配合 STONITH/仲裁

防御手段:

  • 不设偶数节点:3/5/7 节点的多数派明确,避免”两边都不够多数”或”两边都以为自己是多数”。
  • 引入仲裁/见证节点:同城第二机房或轻量 witness,参与投票但不存数据。
  • STONITH:切换前先确保旧主”物理上不能再写”(关机、断网、super_read_only)。
  • 代理与 VIP 唯一:同一时刻只能有一个组件有资格把写流量指向某个节点。
  • 监控仲裁丢失:MGR 的 MEMBER_STATE=UNREACHABLE、Galera 的 non-Primary 必须是最优先级的告警。

其他容易踩的坑#

坑说明
复制过滤规则replicate-ignore-db 之类的过滤会让节点数据不一致,集群方案中禁止使用
非事务引擎表集群方案下写入不受保护,即使存在一张 MyISAM 表也要改成 InnoDB
无主键表行级认证与并行复制都依赖主键,无主键表会退化为表级冲突甚至不可用
大事务大事务会阻塞认证/广播并引发流控,与并行复制治理同源:拆小
忘记 log_replica_updates级联从库链路断掉却不报警
集群成员全部重启Galera 需用 wsrep_cluster_address=gcomm:// 首节点引导,MGR 需重新 bootstrap,误操作会导致集群无法自动恢复
只测”能切换”,不测”能恢复”切换后旧主重新加入、数据补齐这一环最容易出问题,必须演练

小结#

  • 故障转移由三步构成:可靠的检测(宁慢勿误判)→ 权威的选主(用 GTID 挑最全的从库)→ 干净的切换(隔离旧主、重定向流量与所有引用主库地址的地方)。
  • 主从切换最大的风险是”延迟中的提升导致丢事务”,半同步、GTID 校验和 MGR 是三条不同的解法。
  • MGR 与 Galera 都把冲突交给认证层,用多数派换来了”不会脑裂”;代价是网络延迟敏感、节点数受限、冲突事务需要应用重试。
  • 多主写入解决的是容错,不是性能;能单主就单主。
  • 集群解决了”节点故障”,但没有解决”数据被误删/被写坏”。这需要第三篇的备份与容灾演练。
Master High AvailabilityMySQL Group ReplicationMySQL Group ReplicationMySQL Group ReplicationMySQL Group ReplicationMySQL Group ReplicationMySQL Group ReplicationMySQL Group ReplicationMySQL Group ReplicationMySQL Group ReplicationMySQL Group ReplicationMySQL Group ReplicationMySQL Group ReplicationMySQL Group ReplicationMySQL Group ReplicationMySQL Group ReplicationMySQL Group Replication
MySQL 高可用详解(2)故障转移
https://blog-l7wd3.pages.dev/posts/ha/mysql-dr1/
作者
L7WD3-Xiao
发布于
2026-09-05
许可协议
CC BY-NC-SA 4.0

分享文章

生成精美分享图或复制链接,与更多人分享本文。

继续阅读

沿着主题读

基于共同的标签与分类

换条路线

从其他文章中稳定抽取