--- 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
端口 6446(RW) / 6447(RO)"] subgraph "InnoDB Cluster" ICM["InnoDB Cluster Monitor
MySQL Shell 守护进程"] subgraph "Group Replication Set" Node1["Instance 1
Primary (RW)"] Node2["Instance 2
Secondary (RO)"] Node3["Instance 3
Secondary (RO)"] end end subgraph "Metadata Store" Metadb["metadata_db
元数据存储实例"] 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 在集群环境中的使用注意事项