docker 搭建 MySQL MGR (MySQL Group Replication) 集群

发布时间: 更新时间: 总字数:3229 阅读时间:7m 作者:IP:上海 网址

基于最新版 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

yaml
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 脚本,全自动部署集群。

javascript
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 工具

bash
docker-compose up -d

注:容器启动后,MySQL内部初始化需要大概 10~20 秒,请稍微等待一下再执行下一步。

第二步:一键初始化 MGR 集群 告诉 Shell 容器去执行刚才准备好的 JS 脚本:

bash
docker exec -it mysql-shell mysqlsh --js -f /scripts/init-cluster.js

这个过程中会看到 mysql2mysql3 通过 Clone 插件全量同步主库数据,加入 Paxos 复制组,整个过程全自动。打印出 “Cluster Status” 以及拓扑 json 数据时,表示集群就绪。

第三步:启动 MySQL Router 在集群搭建成功后,才能启动 Router 进行自举引导(Bootstrap):

bash
docker-compose --profile router up -d

启动后,Router 会读取集群的元数据并配置路由规则。

MySQL Shell 的日常使用场景

这是一个常见的误区:MySQL Shell 不是一个需要 24 小时后台运行的后台服务(Daemon)!

  • 它的定位:它仅仅是一个高级 DBA 命令行客户端工具。类似于增强版的 mysql-client 或者 Navicat。
  • 高可用要求完全不需要高可用
  • 如何部署:可以把它装在任何一台能够连通数据库内网的机器上(比如堡垒机、中控机,甚至 DBA 的办公电脑上)。

MySQL Shell 的日常使用场景: 当需要查看集群状态、重启节点、或者平滑切换主库时,打开 Shell 执行:

javascript
// 登录任意集群节点
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 集群拓扑的智能网关

  1. Router 启动时,会去读取集群底层的 Metadata(元数据)。
  2. Router 内部维护着一份集群状态表,它实时知道 3 个节点里谁是主(Primary)、谁是从(Secondary)。
  3. 当 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 的配置都极其简单,不需要手写配置文件,一条命令自动搞定:

bash
# 在部署 Router 的机器上执行,假设 mysql1 是集群中任意一个存活节点
mysqlrouter --bootstrap root@mysql1:3306 --user=mysqlrouter

# 启动 router
systemctl start mysqlrouter

执行上述命令后,Router 会自动连接主库拉取拓扑,并在本地生成完美的路由配置文件 mysqlrouter.conf

企业级 3 节点 HA 最终架构图总结

无需依赖数据库 VIP 的现代化高可用架构长这样:

text
[应用层集群] (包含多个微服务节点)
   ├── 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 连接进行管理维护)

核心优势

  1. 零 VIP 运维成本:不用再写复杂的 Keepalived 检测脚本,不用担心网卡绑错。
  2. 秒级故障转移:MGR 依靠 Paxos 多数派协议(3 节点允许 1 节点挂掉),保证数据绝对不丢(RPO=0),且 Router 感知切换速度极快(通常在几秒内恢复业务)。
  3. 读写分离自带:Router 天生自带端口分流功能,写请求打 6446,读请求打 6447,大大减轻主库压力。

如何测试与使用集群?

现在,应用不需要连接任何一个真实的物理节点(mysql1/2/3),而是直接连接到 mysql-router

  • 进行写操作(会路由至当前 Primary 主节点 mysql1): 使用任何数据库工具连接 127.0.0.1 端口 6446,账号 root,密码 rootpassword
  • 进行读操作(会负载均衡至各个 Secondary 从节点): 连接 127.0.0.1 端口 6447

高可用测试: 你可以直接把 mysql1 强制干掉:

bash
docker stop mysql1

然后再通过端口 6446 查询 SELECT @@hostname;,会发现读写端口被 Router 秒级自动切换到了 mysql2mysql3,实现了真正的自动高可用接管机制。