docker 搭建 MySQL MGR (MySQL Group Replication) 集群
基于最新版 MySQL (目前最新的 LTS 长期支持版为 MySQL 8.4) 构建高可用集群,目前官方力推的终极形态是 MySQL InnoDB Cluster。它直接使用了 MGR (MySQL Group Replication) 作为底层复制协议。本文详细介绍 MGR 及集群架构,并提供可以直接运行的
docker-compose部署代码。
核心概念与架构解析
MySQL InnoDB Cluster 主要由三大核心组件构成:
MGR (MySQL Group Replication) —— 数据复制层
MGR 是 MySQL 官方基于 Paxos 协议实现的高可用复制插件,替代了传统的异步/半同步主从复制。
- 特性:提供“无中心节点”的强一致性数据同步。支持多主模式(所有节点皆可写)和单主模式(一主多从,默认且推荐)。
- 容错性:节点故障时能自动选举新的主节点并剔除故障节点,防止“脑裂”。节点重新加入时,支持利用 Clone Plugin 原生进行物理级别的数据快照恢复,非常极速。
MySQL Shell —— 自动化运维层
以往手动配置 MGR 需要在每个节点写大量的 my.cnf 和通过 SQL 执行复杂的配置。
如今官方提供了 MySQL Shell (内建的 AdminAPI)。它能自动检查环境、初始化 MGR、拉起集群、分配节点角色,将复杂的部署变成只要调几个 JavaScript/Python 函数即可完成。
MySQL Router —— 智能代理/路由层
- 它是对业务透明的数据库代理,自动感知识别集群状态和主从角色。
- 提供两个默认端口:
6446(读写分离的主节点读写端口) 和6447(只读端口,自动轮询打到各个从节点)。 - 优势:当主节点宕机发生切换时,Router 会自动将
6446端口的流量切给新的主节点,业务端代码零修改,完全无感。
docker-compose部署
将搭建 3 个 MySQL 节点构成的单主 MGR 集群,1 个 MySQL Shell 容器(用于执行初始化操作),以及 1 个 MySQL Router。
在新建空目录中,创建以下两个文件:
docker-compose.yml
version: '3.8'
services:
mysql1:
image: mysql:8.4
container_name: mysql1
hostname: mysql1
environment:
MYSQL_ROOT_PASSWORD: rootpassword
MYSQL_ROOT_HOST: '%'
# MGR 8.4 节点必须开启 GTID 和 binlog,同时设置唯一的 server-id 和汇报的主机名
command:
- --server-id=1
- --report-host=mysql1
- --enforce-gtid-consistency=ON
- --gtid-mode=ON
ports:
- '33061:3306'
mysql2:
image: mysql:8.4
container_name: mysql2
hostname: mysql2
environment:
MYSQL_ROOT_PASSWORD: rootpassword
MYSQL_ROOT_HOST: '%'
command:
- --server-id=2
- --report-host=mysql2
- --enforce-gtid-consistency=ON
- --gtid-mode=ON
mysql3:
image: mysql:8.4
container_name: mysql3
hostname: mysql3
environment:
MYSQL_ROOT_PASSWORD: rootpassword
MYSQL_ROOT_HOST: '%'
command:
- --server-id=3
- --report-host=mysql3
- --enforce-gtid-consistency=ON
- --gtid-mode=ON
mysql-shell:
image: mysql/mysql-shell:latest
container_name: mysql-shell
volumes:
- ./init-cluster.js:/scripts/init-cluster.js
command: tail -f /dev/null
depends_on:
- mysql1
- mysql2
- mysql3
mysql-router:
image: mysql/mysql-router:latest
container_name: mysql-router
environment:
MYSQL_HOST: mysql1
MYSQL_PORT: 3306
MYSQL_USER: root
MYSQL_PASSWORD: rootpassword
ports:
- '6446:6446' # 业务主库入口(读写)
- '6447:6447' # 业务从库入口(只读)
depends_on:
- mysql1
profiles:
- router # 标记为 router profile,以使其不会随着默认启动而启动init-cluster.js
这是交给 MySQL Shell 自动执行的 JS 脚本,全自动部署集群。
var pwd = 'rootpassword';
print('\n[1/5] 连接到主节点 mysql1...\n');
shell.connect('root:' + pwd + '@mysql1:3306');
print('\n[2/5] 开始预配置各个实例 (自动设置 MGR 依赖的环境变量)...\n');
dba.configureInstance('root:' + pwd + '@mysql1:3306', { interactive: false });
dba.configureInstance('root:' + pwd + '@mysql2:3306', { interactive: false });
dba.configureInstance('root:' + pwd + '@mysql3:3306', { interactive: false });
print('\n[3/5] 基于 mysql1 创建 InnoDB 集群...\n');
var cluster = dba.createCluster('MyAwesomeCluster');
print('\n[4/5] 添加 mysql2 到集群,并使用原生 Clone 插件同步数据...\n');
cluster.addInstance('root:' + pwd + '@mysql2:3306', { recoveryMethod: 'clone', interactive: false });
print('\n[5/5] 添加 mysql3 到集群,并使用原生 Clone 插件同步数据...\n');
cluster.addInstance('root:' + pwd + '@mysql3:3306', { recoveryMethod: 'clone', interactive: false });
print('\n====== 集群创建成功,当前状态如下 ======\n');
print(JSON.stringify(cluster.status(), null, 4));一键启动指南
请在终端进入包含上述两个文件的目录,依次执行以下命令:
第一步:启动 MySQL 节点与 Shell 工具
docker-compose up -d注:容器启动后,MySQL内部初始化需要大概 10~20 秒,请稍微等待一下再执行下一步。
第二步:一键初始化 MGR 集群 告诉 Shell 容器去执行刚才准备好的 JS 脚本:
docker exec -it mysql-shell mysqlsh --js -f /scripts/init-cluster.js这个过程中会看到 mysql2 和 mysql3 通过 Clone 插件全量同步主库数据,加入 Paxos 复制组,整个过程全自动。打印出 “Cluster Status” 以及拓扑 json 数据时,表示集群就绪。
第三步:启动 MySQL Router 在集群搭建成功后,才能启动 Router 进行自举引导(Bootstrap):
docker-compose --profile router up -d启动后,Router 会读取集群的元数据并配置路由规则。
MySQL Shell 的日常使用场景
这是一个常见的误区:MySQL Shell 不是一个需要 24 小时后台运行的后台服务(Daemon)!
- 它的定位:它仅仅是一个高级 DBA 命令行客户端工具。类似于增强版的
mysql-client或者 Navicat。 - 高可用要求:完全不需要高可用。
- 如何部署:可以把它装在任何一台能够连通数据库内网的机器上(比如堡垒机、中控机,甚至 DBA 的办公电脑上)。
MySQL Shell 的日常使用场景: 当需要查看集群状态、重启节点、或者平滑切换主库时,打开 Shell 执行:
// 登录任意集群节点
mysqlsh root@mysql1:3306
// 在 JS 模式下获取集群对象
var cluster = dba.getCluster();
// 1. 查看 3 节点健康状态
cluster.status();
// 2. 将 mysql2 手动切换为新的主库(平滑切换,不丢数据)
cluster.setPrimaryInstance('mysql2:3306');
// 3. 踢出故障节点
cluster.removeInstance('mysql3:3306');用完即走,关闭终端即可,对集群运行没有任何影响。
MySQL router 高可用
为什么不需要 VIP 飘移?
在传统架构中,如果使用 Keepalived + VIP,往往会面临“脑裂”或“误绑”的风险(比如原主库网络短暂抖动,VIP 飘走后原主库又恢复,导致双写)。
MySQL Router 的降维打击: MySQL Router 不是简单的网络层转发,它是感知 MySQL MGR 集群拓扑的智能网关。
- Router 启动时,会去读取集群底层的 Metadata(元数据)。
- Router 内部维护着一份集群状态表,它实时知道 3 个节点里谁是主(Primary)、谁是从(Secondary)。
- 当 MGR 发生主库宕机,底层的 Paxos 协议会自动选出新主库。Router 监测到拓扑变更后,自动将写端口(默认 6446)的流量瞬间切换到新主库上。
整个过程应用层完全不需要像 VIP 那样去操作底层的网络接口,也绝对不会发生写错节点的问题。
MySQL Router 怎么配置?如何保证 Router 本身的高可用?
虽然数据库节点不需要 VIP,但应用总得有个连接地址。如果只部署一台 MySQL Router,那 Router 本身不就成了单点故障(SPOF)了吗?
针对这个问题,企业级架构有两种标准的 Router 部署方式:
方案 A:Sidecar(伴生)模式 —— 官方强烈推荐的终极方案
这是目前微服务架构下最主流的玩法。
- 部署方式:不在数据库服务器上装 Router,而是在每一台应用服务器(App Server)上部署一个 MySQL Router。
- 应用配置:所有应用代码里的数据库连接池,统一连接
127.0.0.1:6446。 - 高可用逻辑:Router 与应用同生共死。只要应用服务器活着,本地的 Router 就活着;哪怕某个 Router 挂了,只影响当前这一台应用,整个业务集群不受影响。完全不需要 VIP。
方案 B:集中式代理模式 + 负载均衡(适合传统架构)
如果应用服务器太多,或者不方便在应用端部署 Router。
- 部署方式:准备 2~3 台独立的代理服务器,每台都部署 MySQL Router。
- 高可用逻辑:在前端挂一个高可用负载均衡器(比如硬件 F5,或者 HAProxy + Keepalived 做 VIP)。
- 架构链路:应用层 -> LB VIP -> 多个 MySQL Router -> 3节点 MGR。
Router 的配置方法(Bootstrap 自举)
不管哪种架构,Router 的配置都极其简单,不需要手写配置文件,一条命令自动搞定:
# 在部署 Router 的机器上执行,假设 mysql1 是集群中任意一个存活节点
mysqlrouter --bootstrap root@mysql1:3306 --user=mysqlrouter
# 启动 router
systemctl start mysqlrouter执行上述命令后,Router 会自动连接主库拉取拓扑,并在本地生成完美的路由配置文件 mysqlrouter.conf。
企业级 3 节点 HA 最终架构图总结
无需依赖数据库 VIP 的现代化高可用架构长这样:
[应用层集群] (包含多个微服务节点)
│
├── App Server 1
│ └── 本地运行 MySQL Router (监听本地 127.0.0.1:6446)
│ │
├── App Server 2
│ └── 本地运行 MySQL Router (监听本地 127.0.0.1:6446)
│ │
... │ (Router 通过内网直连底层的 3 个数据库节点)
│
============== 内部网络边界 ===============================
│
[MySQL InnoDB Cluster 数据层] (MGR 无中心复制)
│
┌──────────┼──────────┐ (Router 自动将 6446 流量发给 Primary,6447 发给 Secondary)
▼ ▼ ▼
[节点 1] [节点 2] [节点 3]
Primary Secondary Secondary
(读写) (只读) (只读)
▲ ▲ ▲
└──────────┴──────────┘
│
(DBA 偶尔通过中控机的 MySQL Shell 连接进行管理维护)核心优势:
- 零 VIP 运维成本:不用再写复杂的 Keepalived 检测脚本,不用担心网卡绑错。
- 秒级故障转移:MGR 依靠 Paxos 多数派协议(3 节点允许 1 节点挂掉),保证数据绝对不丢(RPO=0),且 Router 感知切换速度极快(通常在几秒内恢复业务)。
- 读写分离自带:Router 天生自带端口分流功能,写请求打
6446,读请求打6447,大大减轻主库压力。
如何测试与使用集群?
现在,应用不需要连接任何一个真实的物理节点(mysql1/2/3),而是直接连接到 mysql-router。
- 进行写操作(会路由至当前 Primary 主节点 mysql1):
使用任何数据库工具连接
127.0.0.1端口6446,账号root,密码rootpassword。 - 进行读操作(会负载均衡至各个 Secondary 从节点):
连接
127.0.0.1端口6447。
高可用测试:
你可以直接把 mysql1 强制干掉:
docker stop mysql1然后再通过端口 6446 查询 SELECT @@hostname;,会发现读写端口被 Router 秒级自动切换到了 mysql2 或 mysql3,实现了真正的自动高可用接管机制。