vault backup: 2026-05-17 00:06:11
This commit is contained in:
@@ -0,0 +1,330 @@
|
||||
---
|
||||
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/32-Group Replication]] — InnoDB Cluster 底层就是 GR
|
||||
- [[hhs/MySQL/31-MHA与Orchestrator]] — 不同 HA 方案的对比
|
||||
- [[hhs/GORM/13-多数据库支持]] — GORM 在集群环境中的使用注意事项
|
||||
Reference in New Issue
Block a user