Files
2026-05-24 11:42:38 +08:00

346 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
tags: [MySQL, MHA, Orchestrator, 高可用, 故障切换, MySQL HA]
create time: 2026-05-16 14:30
---
# MHA / Orchestrator 故障切换
## 概述
当主库宕机时,如何**快速且正确**地选出一个新的主库并完成流量切换?这就是高可用(HA)系统的核心问题——既要尽量减少停机时间,又要保证数据不丢失、不发生冲突。
本节将对比两种主流的 MySQL 自动故障切换方案:
- **MHA**:由日本 DeNA 开发的经典 Perl 方案,以数据一致性著称
- **Orchestrator**:由 Spotify 开发的现代 Go 工具,侧重拓扑管理和运维便利
> [!QUESTION] 核心思考
> 在讨论具体工具之前,先问一个问题:**如果只有一个从库,它应该立刻提升为主库吗?**
>
> 答案并不简单。如果主库只是短暂的网络抖动,切换就是"过度响应";但如果主库真的挂了还不切,服务就不可用了。如何在「反应太快」和「反应太慢」之间找到平衡,是 HA 设计的永恒命题。
## 故障场景分类
```mermaid
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 事件。
```mermaid
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 执行远程操作
#### 安装与配置
```bash
# 安装 RPM 包(CentOS/RHEL 7+)
yum install mha4mysql-manager mha4mysql-node -y
```
```bash
# 配置文件 /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 模式下的故障切换:
```ini
# 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 的问题
> 1. **Manager 单点**:虽然可以配 Phantom,但需要一个额外的管理入口
> 2. **Perl 技术栈**:生态维护活跃度下降
> 3. **不支持 GTID 之前**:GTID 出现前依赖 binlog position
> 4. **脑裂风险**:如果 Master 只是网络闪断而非真宕机,可能出现双主
>
> **替代方案**:Orchestrator + Go-HAProxy 或 Patroni(PostgreSQL 方向)
## Orchestrator
由 Spotify 开源的纯 Go 编写的 MySQL 拓扑发现和运维工具。核心定位是**「MySQL 拓扑的管理员」**——它首先帮你搞清楚「现在整个集群是什么样的」,然后在此基础上提供故障切换、手动切换、修复落后从库等操作。
```mermaid
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 以拓扑管理见长,但它同样支持全自动故障切换:
```ini
# 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 选择了更松耦合的设计。原因是:
> 1. 不同环境使用不同的代理层(HAProxy / ProxySQL / MaxScale / 云厂商 LB)
> 2. 让外部系统决定「怎么切流量」更加灵活
> 3. Orchestrator 专注于做一件事:**准确知道拓扑结构并给出操作建议**
>
> 生产环境中常见的做法是写一个 wrapper 脚本,当 Orchestrator 检测到 Master 挂掉时自动调用它来切换 ProxySQL 或 HAProxy 的后端配置。
### 常用 API 示例
```bash
# 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` 接口支持模拟切换流程而不真正执行。在生产环境中强烈建议先用这个命令**演练一次**,确认推荐的切换方案符合预期后再正式执行。
## 脑裂的处理
```mermaid
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] 预防脑裂的策略
> 1. **STONITH(Shoot The Other Node In The Head)**:通过 IPMI / 远程管理卡强制关闭疑似脑裂节点——这是最彻底的方案
> 2. **Quorum(多数派机制)**:3 节点集群中,至少 2 节点存活才允许选举,避免少数派擅自决策
> 3. **带外心跳**:除了 TCP ping,再用 SSH 或其他独立通道探测,降低单通道误判概率
> 4. **延迟备份**:保留一台物理隔离的冷备,脑裂时可从中恢复,相当于最后的保险丝
> 5. **超时阈值调优**:不要为了减少误切把超时设得太长(如超过 30s),故障期间每一秒都很宝贵
## 选型建议
```mermaid
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 方案的对比