第一篇解决了数据有副本,这一篇解决主库倒了怎么办。
本文行文逻辑:什么是故障转移 → 如何完成故障转移/主从切换 → 单主架构需要主从切换,那么多主架构呢 → 多主架构的注意事项
故障转移
复制只保证了”从库有数据”,但主库宕机时:
- 应用连接的是主库地址,主库不可用 = 服务不可写;
- 没有人去把某个从库提升为新主,服务就无法自愈。
故障转移(Failover) 就是把这两件事自动化:发现故障 → 选出新主 → 切换流量 → 重建拓扑。
故障检测
核心原则:避免误判。
故障检测的输入通常有四个来源:
| 探测方式 | 做法 | 误判风险 |
|---|---|---|
| 实例探活 | TCP 连通、SELECT 1、mysqladmin ping | 网络抖动 / 慢查询把连接池占满会误判 |
| 复制链路 | Replica_IO_Running、Last_IO_Error、Seconds_Behind_Master | 单看延迟会误判(见第一篇) |
| 代理健康检查 | ProxySQL / MySQL Router / HAProxy 的后端健康探测 | 探测间隔与超时设置不当会引发抖动 |
| 仲裁裁决 | 多数派投票、仲裁节点、独立 witness | 无仲裁的 1 主 1 从结构天然只能”人工确认” |
工程上的判定原则:
- 连续多次失败才算故障:例如探测间隔 1s、连续 3 次失败、总超时 10s 才判定,避免瞬时抖动触发切换。
- 区分故障类型:进程崩溃(可自动切换)/服务器宕机(需要确认不会”假死复活”)/网络分区(可能脑裂,必须靠仲裁)。
- 切换前先”隔离”旧主:通过
STONITH(关机/断网/关闭 MySQL)确保旧主不会再接受写入,否则双写必然冲突。 - 误判的代价不对称:漏切只是服务不可用(可人工介入),误切会丢数据 + 双写 + 拓扑错乱。因此宁可保守。
主从切换
切换流程
计划内切换(如升级、机房搬迁)和计划外切换(故障)步骤一致,区别只在于能否”优雅地”执行:
- 确认故障与旧主状态:旧主是否彻底不可写;若还活着,先
SET GLOBAL super_read_only=ON或直接停实例。 - 选出新主:在候选从库中挑一个——优先选延迟最小 / GTID 最完整 / 规格最好的节点。候选集必须排除延迟过大的从库(否则提升即丢数据)。
- 补齐数据(可选):把其他从库中比候选新主多出来的事务,在新主上补齐(用 binlog 手工重放或
gtid_purged对齐)。 - 提升新主:
STOP REPLICA; RESET REPLICA ALL; SET GLOBAL read_only=OFF; SET GLOBAL super_read_only=OFF;,并确保绑定 VIP / 写入 DNS 或代理后端。 - 重定向其他从库:GTID 模式下
CHANGE REPLICATION SOURCE TO ... SOURCE_AUTO_POSITION=1即可,不需要位点。 - 切换流量与收尾:应用通过 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抽样比对核心表;- 应用侧关键指标(下单量、写入成功率)是否恢复;
- 复制链路延迟是否归零。
切换脚本与演练
自动切换脚本必须满足:
- 幂等:重复执行不产生额外破坏(例如”已经只读”就跳过)。
- 可中止:任何一步校验失败就停下并告警,而不是硬着头皮往下走。
- 有审计:每一步记录到日志/事件系统,事后可复盘。
- 定期演练:不演练的切换脚本等于不存在。演练至少每季度一次,覆盖”主库进程被杀""主库整机断电""网络分区”三类场景。
多主架构
集群主从复制方案。一主多从的写入口只有一个,提升需要外部裁决。
多主架构试图让多个节点都能写:写节点不再唯一,切换就退化成”把流量挪到另一个健康节点”。
| 形态 | 一致性模型 | 写冲突处理 | 典型实现 |
|---|---|---|---|
| 双主 + 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 组成完整链路。
核心机制:
- 组通信(Group Communication System, GCS):基于 Paxos 变体(XCom)实现消息全序广播,保证所有成员看到相同顺序的事务流。
- 认证(Certification):每个成员对收到的写集做冲突检测(行级、基于主键/唯一键的哈希比对),有冲突的事务在提交前被拒绝并回滚。
- 原子广播 + 两阶段提交:事务先在所有成员上认证通过,再统一提交;多数派(
(N/2)+1)可达才提交,因此不会出现网络分区两侧同时写。 - 分布式恢复:新成员加入时用
group_replication_recovery通道自动从现有成员补齐差异(增量不够则触发全量克隆,8.0.17+ 支持clone插件)。
基础配置(所有节点一致):
[mysqld]server_id = 1gtid_mode = ONenforce_gtid_consistency = ONbinlog_checksum = NONE # MGR 要求log_replica_updates = ONbinlog_format = ROWbinlog_transaction_dependency_tracking = WRITESETtransaction_write_set_extraction = XXHASH64group_replication_group_name = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' # 合法 UUID,且全组统一group_replication_start_on_boot = OFFgroup_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;
-- 观察成员状态,所有成员应为 ONLINESELECT member_id, member_host, member_state, member_roleFROM performance_schema.replication_group_members;也可以用 MySQL Shell 的 AdminAPI 省掉大部分细节:
// mysqlsh --uri root@10.0.0.11:3306dba.configureInstance('root@10.0.0.11:3306');dba.createCluster('mycluster');mycluster.addInstance('root@10.0.0.12:3306');mycluster.status();关键参数与状态:
| 参数 / 状态 | 作用 |
|---|---|
group_replication_single_primary_mode | ON 单主(推荐)、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_STATE | ONLINE / 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 复制插件实现。
核心机制:
- 认证(Certification-Based Replication):事务在本地执行,把”写集”广播给所有节点;每个节点基于写集做冲突检测,通过则提交。没有主从之分,无需 binlog 传输。
- 全同步:已提交事务在所有可用节点上都已应用(代价是提交延迟 = 最慢节点 + 网络 RTT)。
- 流控(Flow Control):
wsrep_flow_control_*参数让快节点等慢节点,避免落后节点被拖垮。 - 状态转移(SST / IST):新节点加入时通过 SST 全量(
xtrabackup-v2、mariabackup、rsync)或 IST 增量(基于gcache)同步。
[mysqld]binlog_format = ROWdefault_storage_engine = InnoDB # Galera 只支持 InnoDBinnodb_autoinc_lock_mode = 2 # 交错模式,多主必备wsrep_on = ONwsrep_provider = /usr/lib64/galera-4/libgalera_smm.sowsrep_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-v2wsrep_sst_auth = 'sstuser:SstPassw0rd!'-- 集群状态:关键指标SHOW STATUS LIKE 'wsrep_cluster_size'; -- 集群成员数SHOW STATUS LIKE 'wsrep_cluster_status'; -- Primary / non-PrimarySHOW STATUS LIKE 'wsrep_local_state_comment'; -- Synced / Donor / Joiner / JoinedSHOW 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拒绝写入。
注意事项
多主写入的代价
多主”看起来”能提升写能力,实际上:
- 写冲突无法避免:两个节点同时改同一行(或同一唯一键),只有一个能提交,另一个报
Deadlock/Lock wait timeout之类的认证失败。业务代码必须能安全重试。 - 重试需要幂等:
INSERT冲突重试要处理 1062;UPDATE ... SET x = x + 1这类语句天然幂等,但”读-改-写”的业务逻辑必须靠乐观锁版本号或唯一键约束。 - 多主不提升写吞吐:每个写都要广播给所有节点并在所有节点认证,集群写能力实际受最慢节点约束,热点行写入甚至比单主更差。
- 自增/序列不连续:交错自增导致 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 都把冲突交给认证层,用多数派换来了”不会脑裂”;代价是网络延迟敏感、节点数受限、冲突事务需要应用重试。
- 多主写入解决的是容错,不是性能;能单主就单主。
- 集群解决了”节点故障”,但没有解决”数据被误删/被写坏”。这需要第三篇的备份与容灾演练。
分享文章
生成精美分享图或复制链接,与更多人分享本文。
继续阅读
换条路线
从其他文章中稳定抽取
最后更新于 ,距今已过 51 天
部分内容可能已过时