14 KiB
tags, create time
| tags | create time | ||||||
|---|---|---|---|---|---|---|---|
|
2026-05-16 14:30 |
MHA / Orchestrator 故障切换
概述
当主库宕机时,如何快速且正确地选出一个新的主库并完成流量切换?这就是高可用(HA)系统的核心问题——既要尽量减少停机时间,又要保证数据不丢失、不发生冲突。
本节将对比两种主流的 MySQL 自动故障切换方案:
- MHA:由日本 DeNA 开发的经典 Perl 方案,以数据一致性著称
- Orchestrator:由 Spotify 开发的现代 Go 工具,侧重拓扑管理和运维便利
[!QUESTION] 核心思考 在讨论具体工具之前,先问一个问题:如果只有一个从库,它应该立刻提升为主库吗?
答案并不简单。如果主库只是短暂的网络抖动,切换就是"过度响应";但如果主库真的挂了还不切,服务就不可用了。如何在「反应太快」和「反应太慢」之间找到平衡,是 HA 设计的永恒命题。
故障场景分类
flowchart TD
A["主库异常"] --> Type{"异常类型"}
Type -->|"进程挂死"<| H1["检测快<br/>切换快<br/>可自动处理"]
Type -->|"磁盘损坏"<| H2["数据可能损坏<br/>需人工介入"]
Type -->|"脑裂 Split-Brain"<| H3["最危险!<br/>双主写入导致数据冲突"]
Type -->|"网络分区"<| H4["隔离误判<br/>假阳性切换"]
style H3 fill:#EE5A24,color:#fff
[!NOTE] 关键认知 HA 工具能自动处理的大多是进程级故障。一旦发生磁盘损坏或网络脑裂,就需要人工判断——这也是为什么理解底层原理比单纯部署工具更重要。
MHA(Master High Availability)
由日本 DeNA 开发(2009年)的经典 MySQL HA 方案,以 Perl 编写。其核心设计思想是**「绝不丢失已提交的数据」**——在故障切换前,会尽力补齐最从库缺失的 binlog 事件。
sequenceDiagram
participant M as MHA Manager
participant DM as Dead Master
participant MS as Most Up-to-Date Slave
participant SS as Other Slaves
M->>DM: ping... no response
Note over M: 连续 3 次 ping 失败
M->>MS: SHOW MASTER STATUS
MS-->>M: relay log 最新的位置
M->>SS: SHOW RELAYLOG STATUS
SS-->>M: relay log 较旧的位置
Note over M: Step 1: 补齐最从库的 missing events
M->>MS: 应用其他 slave 的 relay log 中的缺失事件
Note over M: Step 2: 提升新主
M->>MS: CHANGE MASTER TO... (无 master)
Note over MS: 脱离子复制链,成为独立主库
Note over M: Step 3: 重新配置其他从库指向新主
M->>SS: CHANGE MASTER TO master=new-master-ip
Note over SS: 全部切换到新主
MHA 的核心优势
| 特性 | 说明 |
|---|---|
| 数据一致性保证 | 选择 relay log 最新的 slave 作为新主 |
| 自动补齐 | 从其他 slave 获取 dead master 尚未传的 binlog |
| VIP 漂移 | 通过 Keepalived + VIP 实现无缝切换 |
| Phantom Manager | Manager 节点本身也可以做数据管理 |
[!TIP] 为什么选「最从」而非「任意从」?
假设 Master → SlaveA → SlaveB 的级联复制结构:
- Master 挂了,SlaveA 可能比 SlaveB 更「新」
- 如果直接提升 SlaveB,需要等待它追完所有relay log,延时长且风险大
- MHA 的做法是:找到 relay log 最全的那个从库,确保数据丢失最小
MHA 组成
MHA 采用 Manager + Node 的分布式架构:
Manager Node ← 运行 mha_manager,独立部署(不放在 MySQL 节点上)
Master Node ← 当前主库(可挂,不影响 Node 组件)
Slaves Nodes ← 多个从库(自动检测哪个最新)
App Servers ← 应用端通过 VIP 访问
[!NOTE] 部署建议
- Manager 应部署在独立的第三方机器上,不要和任何 MySQL 实例同机
- 所有节点之间必须配置 SSH 互信,Manager 需要通过 SSH 执行远程操作
安装与配置
# 安装 RPM 包(CentOS/RHEL 7+)
yum install mha4mysql-manager mha4mysql-node -y
# 配置文件 /etc/masterha/app1.cnf
[server default]
manager_workdir=/data/mha/app1
manager_log=/data/mha/app1/manager.log
user=mha_user # Manager 连接 MySQL 的管理账号
password=mha_pass
ssh_user=root # SSH 连接账号
repl_user=repl_user # 主从复制账号
repl_password=repl_pass
[server1]
hostname=10.0.1.10
candidate_master=1 # 允许被提升为主库
[server2]
hostname=10.0.1.11
candidate_master=1
[server3]
hostname=10.0.1.12
no_master=1 # 永远不做主库(例如数据量特别大的从库)
[!TIP] candidate_master 的作用
默认情况下,MHA 优先选择 relay log 最新的从库,而不看它离 Master 的距离。但如果一个从库是「级联」链路末端(Master → S1 → S2),即使 S2 的 relay log 最全,也不应该提升它——因为它一旦变主,其他从库要重新追很长的 binlog。
candidate_master=1可以告诉 MHA:这个节点虽然可能不是 relay log 最完整的,但仍然有资格被提升。配合report_script还可以加入更多判断逻辑。
GTID 支持
MHA 0.56+ 版本开始支持 GTID 模式下的故障切换:
# app1.cnf 中开启 GTID 模式
[server default]
master_binlog_dir=/var/lib/mysql
remote_workdir=/tmp/mha
use_gtid_current_pos=1 # 使用 GTID 定位复制位置
[!NOTE] GTID vs Position 的选择
方式 优点 缺点 binlog position 兼容所有 MySQL 版本 需要精确计算每个 slave 丢失的事件 GTID current_pos 简化了定位逻辑,切换更可靠 要求 MySQL 5.6+,且需提前开启 GTID
如果生产环境已启用 GTID,强烈建议使用 GTID 模式;否则保持默认的 binlog position 即可。
MHA 的局限
[!WARNING] MHA 的问题
- Manager 单点:虽然可以配 Phantom,但需要一个额外的管理入口
- Perl 技术栈:生态维护活跃度下降
- 不支持 GTID 之前:GTID 出现前依赖 binlog position
- 脑裂风险:如果 Master 只是网络闪断而非真宕机,可能出现双主
替代方案:Orchestrator + Go-HAProxy 或 Patroni(PostgreSQL 方向)
Orchestrator
由 Spotify 开源的纯 Go 编写的 MySQL 拓扑发现和运维工具。核心定位是**「MySQL 拓扑的管理员」**——它首先帮你搞清楚「现在整个集群是什么样的」,然后在此基础上提供故障切换、手动切换、修复落后从库等操作。
flowchart TB
subgraph "Orchestrator 架构"
OAPI["Orchestrator HTTP API<br/>port 3000"]
ODB["SQLite/MySQL<br/>存储拓扑信息"]
OSCAN["Discovery Scan<br/>定期 SHOW SLAVE HOSTS"]
frontend["Web UI<br/>拓扑可视化"] --> OAPI
dashboard["Dashboard"] --> OAPI
OSCAN --> ODB
OAPI --> ODB
HAProxy["Go-HAProxy / <br/>MaxScale / ProxySQL"] -->|"重定向流量"| NEWMASTER["新主库"]
end
OAPI --> HAProxy
[!NOTE] 设计理念差异
维度 MHA Orchestrator 首要目标 自动故障切换(Failover) 拓扑管理 + 运维辅助 侵入性 需要在每个节点安装 node 组件 零侵入,只读查询 执行方式 Manager 直接 SSH 操作各节点 通过 API 调用,外部脚本接管 技术栈 Perl Go(单二进制文件部署)
Orchestrator 如何工作
Orchestrator 的核心是一个后台扫描循环:
每 5~30 秒 ──┬──► SHOW SLAVE HOSTS / SHOW REPLICA STATUS
├──► 读取各实例的 Binlog Position
├──► 构建完整拓扑树(含级联复制关系)
└──► 写入内部数据库 → Web UI 刷新显示
这意味着 Orchestrator 不主动修改任何 MySQL 配置,它只是一个观察者 + 操作台。所有的故障切换需要外部工具(如 Go-HAProxy)配合完成。
自动故障切换(Auto-Failover)
虽然 Orchestrator 以拓扑管理见长,但它同样支持全自动故障切换:
# orchestrator.conf.json 关键配置
{
"DiscoverByShowSlaveHosts": true,
"DetectMasterFailoverWithSuperReadOnly": false,
"DetectorScript": "/usr/local/bin/orchestrator-detector.sh",
// 自动 Failover 的条件控制
"AutoFaildown": true, // Master 恢复后是否自动降级并重新纳入复制链
"AutoFailop": true, // Master 宕机时是否自动切换
"ExpireStatsSec": 600 // 统计信息过期时间
// 选择新主的策略
"PreferredCandidateDistances": [1, 2], // 优先选距离近的从库
"CheckSlaveLagThreadsCached": true // 自动检测 Slave 延迟
}
[!QUESTION] Orchestrator 为什么不直接改 VIP?
MHA 内置了 VIP 漂移功能,但 Orchestrator 选择了更松耦合的设计。原因是:
- 不同环境使用不同的代理层(HAProxy / ProxySQL / MaxScale / 云厂商 LB)
- 让外部系统决定「怎么切流量」更加灵活
- Orchestrator 专注于做一件事:准确知道拓扑结构并给出操作建议
生产环境中常见的做法是写一个 wrapper 脚本,当 Orchestrator 检测到 Master 挂掉时自动调用它来切换 ProxySQL 或 HAProxy 的后端配置。
常用 API 示例
# 1. 查看某个应用下的所有实例拓扑
curl http://localhost:3000/api/topology/app/mydb
# 2. 找到最适合晋升为主库的候选者
curl http://localhost:3000/api/candidate-instances/app/mydb
# 3. 强制提升某实例为主库(手动干预)
curl -X POST http://localhost:3000/api/become-master/host/db/instance
# 4. 自动修复脱离的从库(重新对接到新主)
curl -X POST http://localhost:3000/api/recover/full/host/db/instance
# 5. 获取故障切换分析(不实际执行,只看推荐方案)
curl http://localhost:3000/api/fail-coordination/force/master-simulate/host/db/instance
[!TIP] Simulate 模式
fail-coordination接口支持模拟切换流程而不真正执行。在生产环境中强烈建议先用这个命令演练一次,确认推荐的切换方案符合预期后再正式执行。
脑裂的处理
flowchart TD
A["网络分区导致<br/>Master 与 Slave 失联"] --> B{"HA 工具判断<br/>Master 存活?"}
B -->|否| C["提升 Slave 为新主"]
C --> D{"原 Master 恢复后<br/>是否还活着?"}
D -->|是| E["⚠️ 双主!两个库都在接受写请求"]
E --> F["用 binlog 比对<br/>找出冲突数据"]
F --> G["回滚冲突数据<br/>或将原 Master 降级为从库"]
D -->|否| H["原 Master 重启后<br/>作为从库接入"]
B -->|是| I["不做切换,继续等待"]
style E fill:#EE5A24,color:#fff
style H fill:#00D866,color:#fff
脑裂后的数据恢复步骤
当确认发生脑裂时,标准处理流程如下:
Step 1 ──► 停止新主的写入(防止更多冲突)
Step 2 ──► 导出两个库的 GTID 集合或 binlog position 范围
Step 3 ──► 使用 mysqlbinlog + diff 对比差异事件
Step 4 ──► 保留「正确」分支的数据
Step 5 ──► 将另一方的冲突数据标记为 rollback 或直接丢弃
Step 6 ──► 重建复制链路
[!WARNING] 预防脑裂的策略
- STONITH(Shoot The Other Node In The Head):通过 IPMI / 远程管理卡强制关闭疑似脑裂节点——这是最彻底的方案
- Quorum(多数派机制):3 节点集群中,至少 2 节点存活才允许选举,避免少数派擅自决策
- 带外心跳:除了 TCP ping,再用 SSH 或其他独立通道探测,降低单通道误判概率
- 延迟备份:保留一台物理隔离的冷备,脑裂时可从中恢复,相当于最后的保险丝
- 超时阈值调优:不要为了减少误切把超时设得太长(如超过 30s),故障期间每一秒都很宝贵
选型建议
flowchart LR
A["需要 MySQL HA<br/>自动故障切换"] --> B{"技术栈偏好?"}
B -->|"传统运维体系<br/>已有 Perl 团队"| MHA["MHA<br/>经典稳定"]
B -->|"Go / 云原生<br/>需要 Web UI"| ORC["Orchestrator<br/>灵活现代"]
B -->|"想要官方支持"<| INN["InnoDB Cluster<br/>MySQL Shell"]
B -->|"不想自己维护 HA"| CLOUD["云厂商 RDS<br/>托管方案"]
style MHA fill:#4A90D9,color:#fff
style ORC fill:#00D866,color:#fff
style INN fill:#F5A623,color:#fff
style CLOUD fill:#EE5A24,color:#fff
关键对比维度
| 维度 | MHA | Orchestrator | InnoDB Cluster |
|---|---|---|---|
| 数据丢失 | 极小(自动补齐 binlog) | 较小(依赖 GTID) | 零(基于 Group Replication) |
| 部署复杂度 | 中(需 SSH 互信 + RPM) | 低(单二进制文件) | 高(需 PXC/Galera) |
| 故障恢复时间 | 通常 < 10s | 通常 < 15s | 秒级但检测较慢 |
| 在线扩展 | 不支持 | 仅拓扑管理,不处理 DDL | 支持分布式 DDL |
| 社区活跃度 | 较低(DeNA 维护) | 高(VK / 社区活跃) | 官方维护 |
| 适用 MySQL 版本 | 5.6~5.7 | 5.6+(推荐 8.0) | 8.0+ |
[!TIP] 实际生产中的常见选择
- 中小团队(< 10 台 MySQL 实例):Orchestrator 是性价比最高的选择——上手简单、维护成本低、Web UI 对 DBA 友好
- 大流量业务对数据一致性要求极高:如果预算允许,上云厂商 RDS 的付费高可用版是最省心的
- 自建全量多主写入:考虑 InnoDB Group Replication 或 ProxySQL + Orchestrator 组合
关联笔记
- hhs/MySQL/07-高可用与分布式/30-主从复制详解 — 主从复制的配置和监控
- hhs/MySQL/07-高可用与分布式/32-Group Replication — 内置多主复制,不需要外部 HA 工具
- hhs/Redis/06-主从与哨兵 — Redis Sentinel 与 MySQL HA 方案的对比