Files
cs-note/hhs/MySQL/07-高可用与分布式/33-InnoDB Cluster.md
T
2026-05-24 11:42:38 +08:00

331 lines
11 KiB
Markdown
Raw 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, InnoDB Cluster, MySQL Shell, MySQL Router, Group Replication, 高可用, 官方集群, HA]
create time: 2026-05-16 00:00
status: draft
---
# InnoDB Cluster
## 概述
InnoDB Cluster 是 MySQL 官方的**高可用(HA)集群解决方案**,它将 Group Replication(GR)底层协议封装成一个开箱即用的平台。
> [!TIP] 为什么需要 InnoDB Cluster?
> Group Replication 虽然可靠,但手动配置极其繁琐:需要处理 SSL、参数调优、选举策略等。InnoDB Cluster 把 GR 的复杂性隐藏起来,提供一套标准化的 API(MySQL Shell)来完成部署、监控和运维,让开发者聚焦应用层而非基础设施。
它由三个组件构成:
| 组件 | 角色 | 运行时必需? |
|------|------|:-:|
| **MySQL Shell** | 管理工具,用于创建和维护集群 | ❌ |
| **MySQL Router** | 客户端代理,智能路由读写请求 | ✅ |
| **Cluster Monitor** | 内置监控器,嵌入在每个 MySQL 实例中 | ✅ |
核心能力包括:**自动故障转移**、**多主/单写拓扑**、**数据一致性保证**以及**无缝连接**。
## 架构总览
```mermaid
flowchart TB
subgraph "应用层"
App1["App 1"]
App2["App 2"]
end
Router["MySQL Router<br/>端口 6446(RW) / 6447(RO)"]
subgraph "InnoDB Cluster"
ICM["InnoDB Cluster Monitor<br/>MySQL Shell 守护进程"]
subgraph "Group Replication Set"
Node1["Instance 1<br/>Primary (RW)"]
Node2["Instance 2<br/>Secondary (RO)"]
Node3["Instance 3<br/>Secondary (RO)"]
end
end
subgraph "Metadata Store"
Metadb["metadata_db<br/>元数据存储实例"]
end
App1 --> Router
App2 --> Router
Router -->|"6446 RW"| Node1
Router -->|"6447 RO"| Node2
Router -->|"6447 RO"| Node3
Node1 -.Heartbeat.-> ICM
Node2 -.Heartbeat.-> ICM
Node3 -.Heartbeat.-> ICM
ICM --> Metadb
```
> [!QUESTION] 思考:为什么 Router 需要两个端口?
> 写入必须到达唯一的 Primary 以保证数据一致性,读取则可以分发到任意 Secondary 节点提升并发能力。Router 通过两个端口天然实现了读写分离,应用层无需感知集群拓扑变化。
## 快速搭建
### 前置条件
```bash
# 准备 3 台服务器(或容器),每台安装 MySQL 8.0+
# 确保 mysql-shell 和 mysql-router 也已安装
# 最小配置(单机测试可共用一台机器不同端口)
for port in 33061 33062 33063; do
mysqld --defaults-file=/etc/mysql/my-${port}.cnf &
done
```
### 使用 MySQL Shell 一键部署
```javascript
// 连接到第一个实例
dba.connect('root@localhost:33061')
// 检查实例是否符合集群要求
dba.checkInstanceConfiguration('root@localhost:33061')
// 配置实例(如果需要)
dba.configureInstance('root@localhost:33061')
// 创建集群
cluster = dba.createCluster('myCluster')
// 添加第二、第三个实例
cluster.addInstance('root@localhost:33062')
cluster.addInstance('root@localhost:33063')
// 查看集群状态
cluster.status()
```
输出示例:
```json
{
"clusterName": "myCluster",
"defaultReplicaSet": {
"name": "default",
"primary": "localhost:33061",
"status": "OK",
"statusText": "Cluster is ONLINE and can tolerate up to ONE failure.",
"topology": {
"localhost:33061": {
"role": "PRIMARY",
"mode": "R/W",
"status": "ONLINE"
},
"localhost:33062": {
"role": "SECONDARY",
"mode": "R/O",
"status": "ONLINE"
},
"localhost:33063": {
"role": "SECONDARY",
"mode": "R/O",
"status": "ONLINE"
}
}
}
}
```
### 配置 MySQL Router
```bash
# 自动生成路由配置
mysqlrouter --bootstrap root@localhost:33061 --directory ./router --force
# 启动 Router
cd ./router && ./start.sh
# Router 自动暴露:
# - localhost:6446 → 写操作 → 转发到 Primary
# - localhost:6447 → 读操作 → 轮询到所有 Secondary
```
## 高可用与故障转移
InnoDB Cluster 的自动故障转移基于 **Group Replication 的 Paxos 式选举机制**。当当前 Primary 不可达时:
```mermaid
sequenceDiagram
participant App as 应用
participant Router as MySQL Router
participant P as Primary (A)
participant S1 as Secondary (B)
participant S2 as Secondary (C)
App->>Router: SELECT / INSERT
Router->>P: 转发写请求
P-->>S1: binlog / GTID 复制
P-->>S2: binlog / GTID 复制
Note over P: Primary 宕机
P--x Router: 连接断开
Router--x App: 返回 Error
S1->>S2: 心跳超时检测
S2->>S1: 无法联系 Primary
S1->>S1: 触发选举(GCS view change)
S1->>S2: 投票选举新 Primary
S2->>S1: 确认(B 票数最高)
B->>B: 成为新的 Primary
Note over Router: 监控器检测到拓扑变化
Router->>S1: 更新路由表
App->>Router: 重试写入
Router->>S1: 转发到新 Primary
```
> [!NOTE] 故障转移关键参数
> - `group_repification_member_weight`(默认 50):影响选举权重,范围 0~100,值越高越优先成为 Primary
> - `group_repification_single_primary_mode`:开启单写模式后,同一时刻只有一个可写节点
> - **RPO = 0**:在同步模式下,已提交的事务不会丢失;异步模式下可能存在少量数据损失
### 容忍节点数
| 节点总数 | 可容忍故障数 | 需在线最低数 |
|----------|:-:|------------|
| 3 节点 | 1 | 2 |
| 5 节点 | 2 | 3 |
| 7 节点 | 3 | 4 |
> [!WARNING] 脑裂风险
> 当网络分区导致集群分裂成两组时,每组都认为自己是合法的。InnoDB Cluster 通过 **"多数派原则"(Quorum)** 解决——只有拥有多数节点的分区才能继续提供服务,其余进入只读模式。这就是为什么推荐部署奇数个节点:避免平分局面。
## 日常运维操作
以下操作均在 MySQL Shell 中执行(JavaScript 模式)。
### 查看集群状态
```javascript
// 连接到集群中的任意实例即可
dba.connect('root@localhost:33061')
var cluster = dba.getCluster()
cluster.status() // 输出完整的拓扑和每个节点的健康信息
```
### 添加 / 移除节点
```javascript
const cluster = dba.getCluster()
// 添加新节点
cluster.addInstance('root@localhost:33064')
// 安全移除节点(会先同步数据)
cluster.removeInstance('root@localhost:33062', {'safeClone': true})
// 注意:如果节点已宕机且无法恢复,使用 force 模式:
// cluster.removeInstance('root@localhost:33062', {'force': true})
```
### 切换主节点
```javascript
// 手动将某个 Secondary 提升为 Primary
cluster.switchToMultiPrimaryMode() // 切换到多主模式
cluster.switchToSinglePrimaryMode() // 切回单主模式
// 在单主模式下指定哪个节点作为 Primary
cluster.setPrimaryInstance('root@localhost:33062')
```
### 升级集群
```javascript
// 滚动升级(逐个节点升级,不停服)
cluster.checkInstanceUpgrade() // 检查各节点是否满足升级条件
cluster.checkStackUpgrade() // 整体栈升级评估
```
## 生产注意事项
### 必备前置步骤
```bash
# 每个 MySQL 实例必须配置的关键参数(my.cnf)
[mysqld]
# Group Replication 基础配置
server-id=1 # 全局唯一
gtid_mode=ON # 必须开启 GTID
enforce_gtid_consistency=ON # 必须启用
binlog_checksum=CRC32 # 增强数据完整性
transaction_write_set_extraction=XXHASH64 # Router 发现读写集用
loose-group_replication_bootstrap_group=OFF
```
> [!TIP] 单机多实例测试技巧
> 开发阶段可以用一台机器跑三个实例,只需:
> 1. 每个实例独立的数据目录、端口、socket 文件
> 2. 独立的 `.cnf` 配置文件
> 3. `group_replication_ip_whitelist="127.0.0.1"` 允许本地通信
### 常见陷阱
| 问题 | 原因 | 解决方案 |
|------|------|---------|
| 节点加入后变为 `ERROR` | server_uuid 冲突或 UUID 未生成 | 删除 `data/auto.cnf` 重启实例 |
| Router 报 `No connection` | Cluster Monitor 未运行 | 确保实例正常运行,Monitor 是嵌入式的 |
| 写入变慢 | 单写模式下游记事务等待组内确认 | 增加 `transaction_alloc_block_size` 或使用多主模式 |
| 脑裂后无法恢复 | 少数派分区无法 elect 新 primary | 手动引导:`dba.startCluster()` 在多数派分区执行 |
## 应用接入
### Go 接入
> [!TIP] 连接池配置建议
> Go 的 `sql.DB` 需要合理设置连接池参数,避免在故障切换瞬间全部连接失效。
```go
dsn := "app_user:password@tcp(localhost:6446)/mydb?charset=utf8mb4&parseTime=True"
db, err := sql.Open("mysql", dsn)
if err != nil { log.Fatal(err) }
// 故障切换时的关键配置
db.SetMaxIdleConns(10) // 保持足够空闲连接快速恢复
db.SetConnMaxLifetime(5 * time.Minute) // 定期丢弃旧连接,避免持有死连接
db.SetMaxOpenConns(50)
```
**要点解释:**
- 应用只需连接 Router 的端口,无需知道后端有多少节点
- `SetConnMaxLifetime` 确保经过几次切换后,旧连接自然被回收,新连接会自动指向新 Primary
- 建议在应用层配合重试逻辑(如指数退避),因为故障切换期间 Router 可能有几秒的延迟
### Java 接入
```java
String url = "jdbc:mysql://localhost:6446/mydb";
Properties props = new Properties();
props.setProperty("user", "app_user");
props.setProperty("password", "password");
props.setProperty("autoReconnect", "true"); // 启用自动重连
props.setProperty("maxReconnects", "5"); // 故障后最多重试 5 次
try (Connection conn = DriverManager.getConnection(url, props)) {
// 写操作走 6446,读操作走 6447
}
```
## InnoDB Cluster vs 自建 GR
| 特性 | InnoDB Cluster | 自建 GR |
|------|---------------|---------|
| **部署复杂度** | 低(Shell 自动化)| 高(手动配置每个节点)|
| **Router 集成** | 内置 MySQL Router | 需自行搭配 ProxySQL 等 |
| **监控界面** | `shell cluster.status()` | 需查 performance_schema |
| **升级维护** | `dba.patchInstance()` | 手动逐个升级 |
| **灵活性** | 较低(Oracle 锁定)| 高(自定义参数)|
| **适用场景** | 快速落地、中小型团队 | 深度定制、大规模生产 |
## 关联笔记
- [[hhs/MySQL/07-高可用与分布式/32-Group Replication]] — InnoDB Cluster 底层就是 GR
- [[hhs/MySQL/07-高可用与分布式/31-MHA与Orchestrator]] — 不同 HA 方案的对比
- [[hhs/GORM/13-多数据库支持]] — GORM 在集群环境中的使用注意事项