Skip to content
Go back

MySQL MHA 故障切换与脑裂防护:从 STONITH 到 VIP 切换

一句话结论(30s)

MHA 是”主从 + 外部管理器”的异步/半同步高可用方案,本质是用 Manager 脚本驱动五阶段故障切换,因为 MGR 的 Paxos 多数派要求零丢失且复杂度高,MHA 则简单、支持老版本。关键设计是三层脑裂防护:secondary_check_script 多路径仲裁防误切、STONITH 物理断电旧主、VIP 漂移路由流量。权衡在于:MHA 可能丢数据(异步窗口),RPO=0 的金融场景必须换 MGR。

核心原理(2min)

MHA 切换时间线约 8-15 秒:Manager 连续 3 次探测 Master 无响应后,先跑 secondary_check_script 从另一网络路径再确认 Master 是否真死(网络分区则不切换、避免脑裂),再选同步延迟最小的 Slave 为新主并补全差异 binlog,随后所有从库 CHANGE MASTER 指向新主,最后 VIP 漂移 + ARP 广播刷新 MAC 表。防脑裂靠 STONITH:切换后旧 Master 可能复活形成双主,需通过 IPMI 断电、云 API 关机或 iptables 封 3306 把旧主 MySQL 进程彻底杀死,因为 VIP 飘走不代表旧主停止接受直连写入。半同步复制在 Master 宕机后会超时降级为异步,降级窗口内的写入可能丢失。

底层深入(5-10min)

一、MHA vs MGR:架构哲学的根本差异

MHA(Master High Availability)和 MGR(MySQL Group Replication)代表了 MySQL 高可用的两条路线:

维度MHAMGR
架构主从 + 外部管理器多主/单主 + 内置 Paxos
同步机制异步/半同步复制组复制(Paxos 共识)
数据一致性可能丢数据(异步)RPO=0(Paxos 多数派)
故障切换MHA Manager 脚本驱动组内自动选举
脑裂防护依赖外部机制(STONITH/VIP)多数派自动处理
MySQL 版本5.5-8.0 全部支持5.7+ 支持,8.0 成熟
复杂度简单、外部依赖少较复杂、网络延迟敏感
适用规模小到中型(3-10 节点)中小型(3-9 节点)

选型原则:如果业务能接受秒级数据丢失(RPO=0 不是硬要求),且团队对 Paxos 不熟悉,MHA 更简单可靠。如果零数据丢失是硬约束(金融、交易),MGR 是必然选择。

💭 想一想:为什么 MHA 简单却”可能丢数据”?——因为它的复制是异步/半同步的:主库写 binlog 后未必等从库确认就返回了成功,切换时新主可能还缺主库最后那一小段日志。这份”秒级窗口”就是 RPO 不为零的代价;MGR 用 Paxos 多数派强制”多数节点确认才提交”,所以 RPO=0 但复杂度高。

二、MHA 故障切换的完整时间线

T+0s    Master 宕机(进程崩溃 / 网络不可达 / 硬件故障)
T+1s    MHA Manager 连续 3 次探测 Master 无响应
T+2s    MHA Manager 进入故障切换流程
T+3s    【阶段1:确认 Master 确实死亡】
         ├── 执行 secondary_check_script
         │   └── 从另一台机器再次 ping Master → 确认不可达
         └── 判断:是网络分区还是 Master 真的crash?
             ├── 网络分区 → 不做切换(避免脑裂)
             └── Master 真死 → 继续
T+4s    【阶段2:选择新 Master】
         ├── 选出同步延迟最小的 Slave(最接近 Master binlog 位置)
         ├── 检查半同步状态 → 可能有未应用到 relay log 的事件
         └── 对比所有 Slave 的 Relay_Master_Log_File + Exec_Master_Log_Pos
T+5s    【阶段3:补全差异日志】
         ├── 从存活的 Slave 上拉取最新的 binlog → 应用到新 Master
         └── 补全其他 Slave 缺失的日志(通过 relay log 或 binlog)
T+6s    【阶段4:主从切换】
         ├── 新 Master 执行 CHANGE MASTER → 重置为主库
         ├── 所有 Slave 执行 CHANGE MASTER TO 新 Master
         └── 启动复制
T+7s    【阶段5:VIP 漂移】
         ├── 旧 Master 的 VIP 释放(如果可达)
         ├── 新 Master 绑定 VIP
         └── 发送 ARP 广播刷新交换机 MAC 表
T+8-10s 应用恢复连接(VIP 流量路由到新 Master)

总体耗时:8-15 秒(取决于配置和数据量)。关键在于阶段 3(补差异日志)——如果旧 Master 的 binlog 还没来得及同步到 Slave,这部分数据将丢失。

💭 想一想:为什么”丢数据”偏偏卡在阶段 3?——因为阶段 3 是从存活的 Slave 上往回捞旧主最后的 binlog;如果旧主崩溃前那几条日志压根没复制出去,任何存活节点上都没有,就”无源可补”。这正对应半同步/异步复制的那段窗口,也是 MHA 与 MGR 在 RPO 上分道扬镳的地方。

三、secondary_check_script:网络分区的仲裁者

MHA Manager 自己可能也处于网络分区中,因此不能仅凭 Manager 一侧的判断决定是否切换。secondary_check_script 提供双重验证:

# secondary_check_script 典型实现
#!/bin/bash
MASTER_HOST=$1
# 从至少两个不同网络路径探测 Master
ping -c 3 -W 1 $MASTER_HOST || exit 1           # ICMP
mysqladmin -h $MASTER_HOST ping || exit 1         # MySQL 协议
ssh $MASTER_HOST "echo alive" || exit 1           # SSH(如有)
exit 0

执行逻辑

💭 想一想:为什么 Manager 不能只靠自己一侧的判断就切?——因为 Manager 自己也可能跟 Master 一起被网络分区隔开:Manager 连不上 Master,不代表 Master 真的死了,可能只是 Manager 自己掉线。若此时强行切换,就会出现两个 Master 同时对外服务(脑裂)。所以必须从另一条网络路径二次确认,多路径仲裁才能区分”Master 真死”和”Manager 自己失联”。

多 Manager 部署

生产环境建议至少 2 个 MHA Manager 实例,部署在不同机房或不同机架。它们共享配置但只有 一个活跃 Manager(通过文件锁或 etcd 选举)。当活跃 Manager 宕机时,备用 Manager 接管。

四、STONITH:防止脑裂的物理机制

STONITH = Shoot The Other Node In The Head,是防止脑裂的终极手段。

场景:MHA 切换后旧 Master 复活

时刻 T0:Master A 宕机,MHA 将 Slave B 提升为新 Master
时刻 T1:Master A 恢复(网络恢复、进程重启)
结论:如果不做处理,存在两个 Master → 脑裂 → 数据冲突

STONITH 的三种实现

方式机制实现难度可靠性
IPMI/电源控制物理断电旧 Master高(需 BMC/IPMI)极高
Fence Agent通过 VMWare/云 API 强制关机 VM
iptables 封锁旧 Master 启动时自动 reject 3306中(用户自行启动仍可绕过)

MHA 内置的 Fence 机制master_ip_failover_script 可以在切换后 SSH 到旧 Master,执行以下操作:

ssh old_master "
    systemctl stop mysql;        # 停 MySQL
    iptables -A INPUT -p tcp --dport 3306 -j REJECT;  # 封端口
    ip addr del $VIP/24 dev eth0;  # 释放 VIP
"

为什么不能只靠 VIP 避免脑裂?

VIP 飘走不代表旧 Master 不再接受写入。如果旧 Master 网卡恢复但 VIP 已漂移到新 Master,旧 Master 仍然可以通过真实 IP 接受应用直连的写入(如果应用配置了双 IP)。STONITH 确保旧 Master 的 MySQL 进程被 杀死,从根本上杜绝写入。

💭 想一想:VIP 已经飘走了,为什么还挡不住旧主写入?——因为 VIP 只是”应用该连谁”的路由约定,约束不了旧主本身;旧主网卡一恢复,它照样能用真实 IP 接收那些绕过 VIP 的直连写入。脑裂的本质是”两个节点都在写”,光改路由不够,必须物理/进程层面让旧主彻底闭嘴,这就是 STONITH 存在的理由。

五、VIP 切换的三种方案

方案 A:Keepalived + VIP

               ┌─────────────┐
Client ──────► │  VIP: 10.0.0.100  │
               └──────┬──────┘

          ┌───────────┼───────────┐
          ▼           ▼           ▼
     ┌────────┐ ┌────────┐ ┌────────┐
     │Master A│ │Slave B │ │Slave C │
     └────────┘ └────────┘ └────────┘

MHA 通过 master_ip_failover_script 触发 VIP 迁移,同时 Keepalived 作为备用。优点:对应用透明、切换快。缺点:ARP 广播延迟导致部分客户端缓存旧 MAC 地址数秒。

方案 B:DNS + 短 TTL

VIP 替换为域名,DNS TTL 设 5-10 秒。MHA 切换后更新 DNS 解析。优点:无 ARP 问题。缺点:客户端 DNS 缓存可能超过 TTL,切换延迟 > TTL。

方案 C:ProxySQL / HAProxy 中间层

应用连接 ProxySQL → ProxySQL 感知 MHA 切换 → 自动切换读写端口。优点:应用零改动。缺点:引入中间层性能损耗和单点问题(需部署 ProxySQL Cluster)。

六、半同步复制降级

半同步复制在 Master 宕机后有一个重要行为:

aftersync 模式:Master 等待至少 1 个 Slave 确认收到 binlog
              → 如果 Slave 不可达 → 等待 rpl_semi_sync_master_timeout(10s)
              → 超时后降级为异步复制 → 可继续写入

降级期间,如果 Master 宕机,这期间的写入可能丢失。MHA 无法恢复这部分数据。解决方案:

-- 设置超时时间较大,减少降级窗口
SET GLOBAL rpl_semi_sync_master_timeout = 60000;  -- 60s
-- 但要避免长期阻塞写入主库

结合 rpl_semi_sync_master_wait_for_slave_count = 2(等待 2 个 Slave 确认)可进一步降低丢失风险,代价是写入延迟增加。

七、脑裂的完整场景与防御矩阵

场景描述MHA 防御残余风险
Manager 网络分区Manager 与 Master 在同一网络分区secondary_check_script多 Manager 部署可缓解
Master-Slave 网络分区Master 可达但 Slave 不可达不触发切换写入全丢在旧 Master
切换中旧 Master 复活STONITH 延迟/失败master_ip_failover_script应用双写旧 Master + 新 Master
旧 Master 复活后应用直连旧 IP应用配置了双 IP 地址STONITH 杀死 MySQL 进程需确保 STONITH 执行成功

最佳实践:STONITH(物理/API 级别) + VIP 漂移 + ProxySQL 中间层 + 半同步复制 ≥ 2 副本。四层防御可压降脑裂概率到 99.99% 以下。

章末提问

追问 1:MHA 切换为什么可能丢数据?丢在哪个环节?

回答思路:结论先行——丢在「旧主最后一段日志还没复制到从库」的窗口,集中在补差异日志阶段。因为 MHA 是异步/半同步复制,主库写 binlog 后不保证立刻同步给从库;主库一宕,这段日志若没到任何存活节点就再也捞不回来,于是 RPO 不为零。要 RPO=0 只能换 MGR 这类多数派方案。

追问 2:为什么不能只靠 VIP 漂移防脑裂,还必须有 STONITH?

回答思路:结论先行——因为 VIP 只改路由、不杀进程,堵不住旧主的直连写入。因为脑裂的本质是两个 Master 同时在写,VIP 飘到新主后,旧主网卡一旦恢复仍能用真实 IP 接应用直连的写;STONITH 通过 IPMI 断电、云 API 关机或封 3306 把旧主 MySQL 进程彻底杀死,从根源上消除第二个写入口。

追问 3:secondary_check_script 解决的是什么问题?没有它会怎样?

回答思路:结论先行——解决「Manager 自己身处网络分区时误判 Master 已死」的问题,防误切。因为 Manager 连不上 Master 可能是网络分区而非 Master 真死,单侧判断会导致脑裂;secondary_check_script 从至少两条网络路径再探测,只有多路径都不可达才确认 Master 真死、才切。没有它,Manager 一失联就切,极易造成双主。


Share this post on:

Previous Post
MySQL MVCC——ReadView的四个字段如何决定可见性
Next Post
MySQL GTID——为什么比binlog position更可靠