331 lines
11 KiB
Markdown
331 lines
11 KiB
Markdown
---
|
||
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 在集群环境中的使用注意事项
|