麒麟v10-MySQL InnoDB Cluster 完整部署与全场景验证(从入门到精通)

环境 :192.168.195.141 / 142 / 143(CentOS 系 Kylin V10,2.8G 内存)

软件 :MySQL 8.0.35 + MySQL Shell 8.0.35 + MySQL Router 8.0.35(均为官方归档源二进制包,国内镜像最高仅 8.0.28)

覆盖 :InnoDB_Cluster.pdf 全部内容(架构、Shell 安装、实例配置、建集群、加节点、状态查看、Router 部署、PyMySQL 高可用测试)+ 12 个补充场景实测

验证状态:全部场景实测通过。所有命令注明执行节点,可按文档逐步复现。


一、InnoDB Cluster 架构与原理(PDF 知识点)

1.1 组件构成

复制代码
        应用 (PyMySQL / JDBC / 任何 MySQL 客户端)
          │
          ▼ 6446(RW) / 6447(RO) / 6448(X-RW) / 6449(X-RO) / 8443(REST API)
   ┌──────────────┐
   │ MySQL Router │  ← 轻量转发器: 读元数据, 找 PRIMARY/SECONDARY, 秒级自动切流
   └──────┬───────┘
          │
   ┌──────▼──────────────────────────────────────┐
   │  InnoDB Cluster (= MGR + 元数据 + Shell 管理) │
   │  ┌────────┐   ┌────────┐   ┌────────┐        │
   │  │141 主RW │◄─►│142 从RO │◄─►│143 从RO│  MGR组复制(Paxos多数派认证)
   │  └────────┘   └────────┘   └────────┘        │
   │        mysql_innodb_cluster_metadata 库       │
   └──────────────────────────────────────────────┘

三层分工

  • MySQL Server + MGR 插件:数据层,组复制提供多写协议/自动故障检测/选主(底层就是前一篇笔记的 MGR)
  • mysql_innodb_cluster_metadata:元数据库,记录集群拓扑(clusters/instances/routers 表),Router 靠它寻址
  • MySQL Shell (mysqlsh):管理入口,dba.* API 把 MGR 复杂操作(配置/建组/加节点/切主/恢复)封装成一条命令
  • MySQL Router:接入层,替代应用的 VIP;bootstrap 时把自身注册进元数据,动态感知主从变化

1.2 Router 工作流程(PDF 原文要点)

  1. 应用向 MySQL Router 发起连接
  2. Router 检查并选择一个可用的后端节点(MySQL Server)
  3. Router 与这个后端节点建立连接
  4. Router 来回转发应用与后端节点之间的数据包
  5. 如果后端节点出现问题,Router 会断开应用端的连接。应用端重试后,Router 会选择另外一个可用节点重复上述流程

端口规划(默认 5 个监听)

端口 协议 用途
6446 Classic 读写(自动路由到 PRIMARY)
6447 Classic 只读(round-robin 到 SECONDARY)
6448 X 读写(X 协议)
6449 X 只读(X 协议)
8443 HTTP REST API(监控 Router 自身)

1.3 与 MGR 的关系(关键认知)

InnoDB Cluster 不是新的复制技术 ,而是「MGR + 元数据 + Shell + Router」的管理套装

  • 底层复制 = Group Replication(单主模式,多数派认证)
  • dba.createCluster() 本质 = 装 MGR 插件 + 建元数据库 + 引导组
  • cluster.addInstance() 本质 = 配置实例 + clone/增量恢复 + 加入组 + 写元数据
  • 故障切换 = MGR 自动选主,Router 盯元数据自动切流 ------ 应用无需感知 IP

二、环境清理(三节点执行)

bash 复制代码
# 执行节点: 141/142/143
ip addr del 192.168.195.200/24 dev ens32 2>/dev/null          # 清 VIP 残留
systemctl stop mysqld mysqlrouter orchestrator orch-smtp 2>/dev/null
systemctl disable mysqld mysqlrouter 2>/dev/null
rm -rf /etc/systemd/system/mysqld.service /etc/systemd/system/mysqlrouter.service \
       /etc/systemd/system/orchestrator.service /etc/systemd/system/orch-smtp.service \
       /data/mysql /data/myrouter /etc/my.cnf /etc/sysconfig/mysql \
       /home/orchestrator /var/lib/orchestrator /var/log/orchestrator /var/mailbox \
       /opt/smtp_server.py /etc/profile.d/orchestrator-client.sh \
       /etc/masterha /var/log/masterha /usr/local/bin/orchestrator-client \
       /usr/local/bin/masterha* /usr/local/mysql /usr/local/mysql-shell /usr/local/mysql-router
rpm -e mha4mysql-node mha4mysql-manager 2>/dev/null
userdel -r mysql 2>/dev/null; userdel -r orchestrator 2>/dev/null
systemctl daemon-reload
# 验证: 无相关服务/进程, /usr/local 下无 mysql*, id mysql 报无此用户

三、软件下载(国内源优先策略)

实测结论(2026-08) :清华/阿里/腾讯/华为/中科大镜像的 MySQL-8.0 目录最高只有 8.0.28 ,Shell/Router 同理。8.0.35 全家桶必须用官方归档源 downloads.mysql.com/archives(直连速度快)。

bash 复制代码
# 执行节点: 141(下载后 scp 分发, 或任一台有外网的机器)
cd /usr/local/src
# MySQL Server 8.0.35 (glibc2.17)
curl -sL -O "https://downloads.mysql.com/archives/get/p/23/file/mysql-8.0.35-linux-glibc2.17-x86_64.tar.xz"
# MySQL Shell 8.0.35
curl -sL -O "https://downloads.mysql.com/archives/get/p/43/file/mysql-shell-8.0.35-linux-glibc2.12-x86-64bit.tar.gz"
# MySQL Router 8.0.35
curl -sL -O "https://downloads.mysql.com/archives/get/p/41/file/mysql-router-8.0.35-linux-glibc2.12-x86_64.tar.xz"

# 校验(md5, 三节点必须一致):
# mysql-8.0.35...tar.xz        c17075cfc58d8cd25cea4be36d77eed2
# mysql-shell-8.0.35...tar.gz  73326660b188520acc02fa49a1172436
# mysql-router-8.0.35...tar.xz 0dcddd49a3de1f7454cdaf52958ea7a3

# 分发(141 上执行):
for h in 192.168.195.142 192.168.195.143; do
  scp mysql-*.tar.* root@$h:/usr/local/src/
done

四、MySQL 安装与 my.cnf(三节点)

4.1 用户/解压/软链(三节点)

bash 复制代码
# 执行节点: 141/142/143
groupadd -f mysql && useradd -g mysql -s /sbin/nologin mysql
cd /usr/local
tar xf /usr/local/src/mysql-8.0.35-linux-glibc2.17-x86_64.tar.xz
ln -sf mysql-8.0.35-linux-glibc2.17-x86_64 mysql
tar xf /usr/local/src/mysql-shell-8.0.35-linux-glibc2.12-x86-64bit.tar.gz
ln -sf mysql-shell-8.0.35-linux-glibc2.12-x86-64bit mysql-shell
mkdir -p /data/mysql/3306/data && chown -R mysql:mysql /data/mysql
# 141/142 额外装 Router(双 Router 容灾):
tar xf /usr/local/src/mysql-router-8.0.35-linux-glibc2.12-x86_64.tar.xz
ln -sf mysql-router-8.0.35-linux-glibc2.12-x86_64 mysql-router

/usr/local/mysql/bin/mysql --version        # Ver 8.0.35
/usr/local/mysql-shell/bin/mysqlsh --version # Ver 8.0.35

4.2 my.cnf(含逐项注释;三节点仅 server-id/report_host/group_seeds 的 IP 不同)

bash 复制代码
# 执行节点: 141(142/143 把 IP 尾段与 server-id 对应改)
cat > /etc/my.cnf << 'EOF'
[client]
socket = /data/mysql/3306/data/mysql.sock

[mysqld]
# ========== 基础参数 ==========
basedir = /usr/local/mysql
datadir = /data/mysql/3306/data
user    = mysql
port    = 3306
socket  = /data/mysql/3306/data/mysql.sock
log_error = /data/mysql/3306/data/mysqld.err
log_timestamps = system

# ========== MGR 前提参数(InnoDB Cluster 底层依赖) ==========
server-id = 1                      # 三节点唯一: 1/2/3
log-bin = mysql-bin                # 必须 binlog
binlog_format = ROW                # MGR 强制 ROW
binlog_row_image = FULL            # 完整行镜像(冲突认证需要)
gtid_mode = ON                     # MGR 强制 GTID
enforce_gtid_consistency = ON
log_slave_updates = ON             # 组内事务需写回 binlog
master_info_repository = TABLE     # 崩溃安全
relay_log_info_repository = TABLE
relay_log_recovery = ON
report_host = 192.168.195.141      # 集群内显示地址(务必写IP,避免主机名解析问题)
report_port = 3306

# ========== Shell configureInstance 会自动检查/补齐的项 ==========
binlog_transaction_dependency_tracking = WRITESET   # 并行回放依赖:WRITESET 大幅提升从库应用速度
transaction_write_set_extraction = XXHASH64         # write set 提取算法(冲突检测用)
slave_preserve_commit_order = ON                    # 从库按主库提交顺序应用(强一致)

# ========== MGR 插件参数(loose-前缀=插件未载入时不报错) ==========
loose-plugin_load_add = 'group_replication.so'      # mysqld 启动自动加载 MGR 插件
loose-group_replication_group_seeds = '192.168.195.141:33061'  # 组种子地址
loose-group_replication_start_on_boot = OFF         # 由 Shell 管理,不随库自启(演示友好)
loose-group_replication_recovery_use_ssl = 0
loose-group_replication_exit_state_action = READ_ONLY  # 异常退出自动只读,保护数据
loose-group_replication_single_primary_mode = ON    # 单主模式(InnoDB Cluster 默认)
loose-group_replication_message_cache_size = 1073741824  # 组消息缓存 1G

# ========== 内存(2.8G 小内存机器) ==========
innodb_buffer_pool_size = 512M
innodb_redo_log_capacity = 256M
EOF

⚠️ 踩坑提醒group_replication_group_seeds 若在 heredoc 里写 ${report_host} 这类变量不会展开,写死完整 IP 最稳。

4.3 systemd 服务 + 初始化(三节点)

bash 复制代码
# 执行节点: 141/142/143
cat > /etc/systemd/system/mysqld.service << 'EOF'
[Unit]
Description=MySQL Server (InnoDB Cluster Node)
After=network.target syslog.target
[Install]
WantedBy=multi-user.target
[Service]
User=mysql
Group=mysql
Type=forking
PIDFile=/data/mysql/3306/data/mysqld.pid
TimeoutSec=0
ExecStart=/usr/local/mysql/bin/mysqld --defaults-file=/etc/my.cnf --pid-file=/data/mysql/3306/data/mysqld.pid --daemonize $MYSQLD_OPTS
EnvironmentFile=-/etc/sysconfig/mysql
LimitNOFILE=65535
Restart=on-failure
RestartPreventExitStatus=1
PrivateTmp=false
EOF
echo 'MYSQLD_OPTS=' > /etc/sysconfig/mysql
systemctl daemon-reload

/usr/local/mysql/bin/mysqld --defaults-file=/etc/my.cnf --initialize   # 无输出即成功
grep 'temporary password' /data/mysql/3306/data/mysqld.err   # 取临时密码
systemctl start mysqld && systemctl enable mysqld

# 改密(三节点各自的临时密码):
mysql -uroot -p'临时密码' -S /data/mysql/3306/data/mysql.sock --connect-expired-password \
  -e "ALTER USER user() IDENTIFIED BY '123456';"

4.4 创建远程管理账号(PDF 步骤2, 三节点)

sql 复制代码
-- 执行节点: 141/142/143(socket 登录)
set session sql_log_bin=0;   -- 关键!不记 binlog,避免节点间 GTID 分叉
create user root@'%' identified by '123456';
grant all on *.* to root@'%' with grant option;
set session sql_log_bin=1;

五、dba.configureInstance 配置实例(PDF 步骤3)

5.1 PDF 原版交互式用法(人工操作推荐)

bash 复制代码
# 执行节点: 141/142/143
mysqlsh
js 复制代码
// MySQL JS >  (PDF 原样; 三个节点各执行一次)
dba.configureInstance('root:123456@192.168.195.141:3306', {
  clusterAdmin: 'cluster_admin', clusterAdminPassword: 'cluster_pass'
})
// 提示需要修复的参数(本环境 my.cnf 已预配, 只差并行 applier):
//   binlog_transaction_dependency_tracking: COMMIT_ORDER -> WRITESET
// Do you want to perform the required configuration changes? [y/n]: y
// -> The instance '192.168.195.141:3306' was configured to be used in an InnoDB cluster.

configureInstance 做了什么

  1. 检查/修复 MGR 必需参数(binlog/GTID/ROW/write_set 等)
  2. 创建 cluster_admin 管理账号(后续建集群/加节点都用它, 权限含 CLONE_ADMIN/REPLICATION_SLAVE 等)
  3. 注册 group_replication 插件加载
  4. 开启并行 appliers(Successfully enabled parallel appliers

5.2 脚本化非交互用法(实测可用, 自动化推荐)

bash 复制代码
# 执行节点: 141/142/143
mysqlsh --uri "root:123456@192.168.195.141:3306" --js \
  -e "dba.configureInstance(null, {clusterAdmin: 'cluster_admin', clusterAdminPassword: 'cluster_pass'})"

⚠️ 实测踩坑(自动化同学必看):

  • configureInstance 第 1 参数为 null 时作用于 --uri 连接的实例;选项放第 2 参数。

  • 直接 mysqlsh -e "dba.configureInstance('root:...')" 不带 --uri 时,Shell 会去连 localhost 且密码解析走 login-path,报 Access denied for 'root'@'localhost' (using password: NO) -- 务必 --uri 全量写

  • 三节点执行后验证(每节点):

    sql 复制代码
    SELECT user,host FROM mysql.user WHERE user='cluster_admin';  -- cluster_admin %

六、创建集群与添加节点(PDF 步骤4/5)

6.1 createCluster(141, PDF 步骤4)

bash 复制代码
# 执行节点: 141
mysqlsh --uri "cluster_admin:cluster_pass@192.168.195.141:3306" --js -e "
dba.createCluster('myCluster', { disableClone: false })
"
text 复制代码
实际输出(节选):
Creating InnoDB Cluster 'myCluster' on '192.168.195.141:3306'...
Adding Seed Instance...
Cluster successfully created. Use Cluster.addInstance() to add MySQL instances.
At least 3 instances are needed for the cluster to be able to withstand up to
one server failure.                <- 提示至少 3 节点才能容 1 节点故障

disableClone: false = 允许用 clone 插件做节点供给(8.0.17+ 默认即支持,显式写出与 PDF 一致)。

6.2 addInstance 添加 142(clone 方式, PDF 步骤5)

bash 复制代码
# 执行节点: 141
mysqlsh --uri "cluster_admin:cluster_pass@192.168.195.141:3306" --js -e "
var cluster = dba.getCluster('myCluster');
cluster.addInstance('cluster_admin:cluster_pass@192.168.195.142:3306',
                    {recoveryMethod: 'clone'});
"
text 复制代码
实际输出(节选, 与 PDF 页5 一致):
NOTE: A server restart is expected to happen as part of the clone process...
NOTE: 192.168.195.142:3306 is being cloned from 192.168.195.141:3306
** Stage DROP DATA: Completed
** Clone Transfer
    FILE COPY  ############  100%  Completed
    PAGE COPY  ############  100%  Completed
    REDO COPY  ############  100%  Completed
NOTE: 192.168.195.142:3306 is shutting down...
* Waiting for server restart...

⚠️ 实测踩坑(systemd 部署必遇) :clone 完成后实例自动 RESTART 需要 supervisor 权限,systemd 托管的 mysqld 无法自启,错误日志出现:

[MY-013462] Clone shutting down server as RESTART failed. Please start server to complete clone operation.

且 mysqlsh 卡在 Waiting for server restart 直到超时(MYSQLSH 51156)。

处置 :到 142 上 systemctl start mysqld 手动拉起,然后在 141 上 cluster.rescan({addInstances: ['192.168.195.142:3306'], interactive: false}) 收编元数据(rescan 会发现"游离"的 GR 成员并写回 metadata)。

6.3 addInstance 添加 143

同 6.2(clone + 超时后 systemctl start mysqld + rescan 收编)。143 添加完成后的最终状态与 PDF 完全一致:

js 复制代码
// 执行节点: 141
mysqlsh --uri "cluster_admin:cluster_pass@192.168.195.141:3306" --js -e "
var c = dba.getCluster('myCluster'); print(c.status());
"
json 复制代码
{
    "clusterName": "myCluster",
    "defaultReplicaSet": {
        "name": "default",
        "primary": "192.168.195.141:3306",
        "ssl": "REQUIRED",
        "status": "OK",                       // <- 全 OK
        "statusText": "Cluster is ONLINE and can tolerate up to ONE failure.",
        "topology": {
            "192.168.195.141:3306": { "memberRole": "PRIMARY",   "mode": "R/W", "status": "ONLINE", "version": "8.0.35" },
            "192.168.195.142:3306": { "memberRole": "SECONDARY", "mode": "R/O", "status": "ONLINE", "version": "8.0.35" },
            "192.168.195.143:3306": { "memberRole": "SECONDARY", "mode": "R/O", "status": "ONLINE", "version": "8.0.35" }
        },
        "topologyMode": "Single-Primary"
    },
    "groupInformationSourceMember": "192.168.195.141:3306"
}

status 字段速查

status 含义 处置
OK 全部在线,可容 1 故障 正常
OK_NO_TOLERANCE / OK_NOT_TOLERANT 在线但无可容故障余量(2节点) 尽快恢复第三节点
OK_NO_TOLERANCE_PARTIAL 部分成员 MISSING 检查失联节点
NO_QUORUM 失去多数派 forceQuorum 或 reboot
UNKNOWN 元数据不可达 检查连接/metadata

七、测试数据与业务账号

sql 复制代码
-- 执行节点: 141 (PRIMARY)
CREATE DATABASE cluster_db;
USE cluster_db;
CREATE TABLE orders (
  order_id INT PRIMARY KEY AUTO_INCREMENT,
  customer VARCHAR(50) NOT NULL,
  product VARCHAR(100) NOT NULL,
  amount DECIMAL(10,2) DEFAULT 0.00,
  status ENUM('pending','paid','shipped') DEFAULT 'pending',
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB;
INSERT INTO orders(customer,product,amount,status) VALUES
 ('张三','iPhone 15',5999.00,'paid'),
 ('李四','MacBook Pro',14999.00,'pending'),
 ('王五','AirPods Pro',1899.00,'shipped'),
 ('赵六','iPad Air',4399.00,'paid'),
 ('钱七','Apple Watch',2999.00,'pending');

CREATE TABLE order_items (
  item_id INT PRIMARY KEY AUTO_INCREMENT,
  order_id INT NOT NULL,
  item_name VARCHAR(100),
  qty INT DEFAULT 1,
  price DECIMAL(10,2),
  FOREIGN KEY (order_id) REFERENCES orders(order_id)
) ENGINE=InnoDB;
INSERT INTO order_items(order_id,item_name,qty,price) VALUES
 (1,'iPhone 15 黑色',1,5999.00),(2,'MacBook 14寸',1,14999.00),(2,'扩展坞',2,199.00),
 (3,'AirPods Pro 2',1,1899.00),(4,'iPad Air 5',1,4399.00),(4,'Pencil 2',1,999.00);

-- 业务账号(经 Router 连接用):
CREATE USER 'test_user'@'%' IDENTIFIED BY '123456';
GRANT SELECT,INSERT,UPDATE,DELETE ON cluster_db.* TO 'test_user'@'%';
FLUSH PRIVILEGES;
sql 复制代码
-- 执行节点: 142/143 (验证秒级同步)
SELECT COUNT(*) orders_cnt FROM cluster_db.orders;   -- 5
SELECT COUNT(*) items_cnt FROM cluster_db.order_items; -- 6

八、MySQL Router 部署(PDF 步骤7)

8.1 bootstrap 初始化

bash 复制代码
# 执行节点: 141
mkdir -p /data/myrouter/6446 && chown -R mysql:mysql /data/myrouter
mysqlrouter --bootstrap cluster_admin:cluster_pass@192.168.195.141:3306 \
  --user=mysql --directory /data/myrouter/6446 \
  --conf-use-sockets --report-host='192.168.195.141'
text 复制代码
实际输出(节选, 与 PDF 一致):
# MySQL Router configured for the InnoDB Cluster 'myCluster'
- Read/Write Connections: 192.168.195.141:6446, /data/myrouter/6446/mysql.sock
- Read/Only Connections:  192.168.195.141:6447, /data/myrouter/6446/mysqlro.sock
- Read/Write Connections: 192.168.195.141:6448, /data/myrouter/6446/mysqlx.sock
- Read/Only Connections:  192.168.195.141:6449, /data/myrouter/6446/mysqlxro.sock

bootstrap 同时做了(要点):

  1. 在集群里建 mysql_router1_xxx 账号(存 keyring, 用于读元数据)
  2. 生成 /data/myrouter/6446/mysqlrouter.conf
  3. 把 Router 自身注册进元数据cluster.listRouters() 可见)

8.2 mysqlrouter.conf 配置文件讲解(关键字段注释)

ini 复制代码
[DEFAULT]
user=mysql                                   # 运行用户
logging_folder=/data/myrouter/6446/log       # 日志目录
dynamic_state=/data/myrouter/6446/data/state.json  # 动态状态缓存(主从拓扑快照,断连元数据也能路由)
client_ssl_mode=PREFERRED                    # 客户端到Router的SSL
server_ssl_mode=AS_CLIENT                    # Router到后端跟随客户端

[logger]
level=INFO

[metadata_cache:bootstrap]                   # 元数据缓存模块
cluster_type=gr                              # 集群类型: Group Replication
router_id=1                                  # 本Router在元数据里的ID
user=mysql_router1_w1i4cuz4097l              # 读元数据的账号(keyring存密码)
metadata_cluster=myCluster                   # 集群名
ttl=0.5                                      # 元数据刷新间隔(秒): 越小故障感知越快
auth_cache_ttl=-1                            # 认证缓存(密码变更即时生效)

[routing:bootstrap_rw]                       # 读写路由段
bind_port=6446                               # 监听端口
destinations=metadata-cache://myCluster/?role=PRIMARY   # 目标=元数据里的PRIMARY
routing_strategy=first-available             # 主只有1个:取第一个可用
protocol=classic

[routing:bootstrap_ro]                       # 只读路由段
bind_port=6447
destinations=metadata-cache://myCluster/?role=SECONDARY # 目标=所有SECONDARY
routing_strategy=round-robin-with-fallback   # 轮询分发;从库全挂则回退主库!
protocol=classic
# (bootstrap_x_rw 6448 / bootstrap_x_ro 6449 为 X 协议段,略)

8.3 启动与验证

bash 复制代码
# 执行节点: 141
sh /data/myrouter/6446/start.sh         # PDF 方式(脚本后台拉起)
# 生产建议 systemd:
cat > /etc/systemd/system/mysqlrouter.service << 'EOF'
[Unit]
Description=MySQL Router for InnoDB Cluster
After=network.target mysqld.service
[Install]
WantedBy=multi-user.target
[Service]
User=mysql
Group=mysql
PIDFile=/data/myrouter/6446/mysqlrouter.pid
ExecStart=/usr/local/mysql-router/bin/mysqlrouter -c /data/myrouter/6446/mysqlrouter.conf
Restart=on-failure
RestartSec=5
LimitNOFILE=65535
EOF
systemctl daemon-reload && systemctl enable --now mysqlrouter

ss -ntl | grep -E '6446|6447|6448|6449|8443'   # 5 端口监听(PDF 同款验证)
bash 复制代码
# 读写分离验证(PDF 核心验证点):
# 执行节点: 141
mysql -utest_user -p123456 -h127.0.0.1 -P6446 -e "SELECT @@hostname;"  # MYSQL141 (主,RW)
mysql -utest_user -p123456 -h127.0.0.1 -P6447 -e "SELECT @@hostname;"  # MYSQL142/143 轮询(从,RO)
js 复制代码
// Router 注册信息(PDF cluster.listRouters()):
mysqlsh --uri "cluster_admin:cluster_pass@192.168.195.141:3306" --js -e "
var c=dba.getCluster(); print(JSON.stringify(c.listRouters()));"
// {"routers":{"192.168.195.141::":{...,"rwPort":"6446","roPort":"6447","lastCheckIn":"..."} }}

九、全场景验证实录(A~J 全部实测通过)

场景A: 主库故障自动 failover + Router 自动切流 ✅

bash 复制代码
# 执行节点: 141 (当时 PRIMARY)
systemctl stop mysqld        # 模拟主库宕机
js 复制代码
// 执行节点: 142 (25秒后查看, mysqlsh 连任意幸存节点)
"primary": "192.168.195.142:3306",      // <- 142 自动当选新主
"status": "OK_NO_TOLERANCE_PARTIAL",    // 141 显示 (MISSING)
bash 复制代码
# Router 验证(执行节点: 141, Router 幸存):
mysql -utest_user -p123456 -h127.0.0.1 -P6446 -e "SELECT @@hostname;"
# -> MYSQL142  <- 6446 无缝指向新主,写能力立即恢复
mysql -utest_user -p123456 -h127.0.0.1 -P6447 -e "SELECT @@hostname;"
# -> MYSQL143  <- 只读流量自动避开故障节点

旧主恢复(实例重启后不会自动入组, 需 rejoin):

js 复制代码
// 执行节点: 142 (新主)
var c=dba.getCluster('myCluster');
c.rejoinInstance('192.168.195.141:3306');   // 只给 host:port, 账号用会话的
// -> The instance '192.168.195.141:3306' was successfully rejoined to the cluster.

注:8.0 的 auto-rejoin 机制下,重启后的实例通常也会自动重入组(本次实测约 30 秒自动回组 SECONDARY);rejoinInstance 是确定性操作手段。

场景B: 手动在线切主 setPrimaryInstance ✅

js 复制代码
// 执行节点: 任意在线节点 (当时主=142, 切到143)
var c=dba.getCluster('myCluster');
c.setPrimaryInstance('192.168.195.143:3306');
// -> The instance '192.168.195.143:3306' was successfully elected as primary.
// status: primary=143, 全员 ONLINE
bash 复制代码
# Router 即时跟随(无需重启):
mysql -utest_user -p123456 -h127.0.0.1 -P6446 -e "SELECT @@hostname;"   # MYSQL143
mysql -utest_user -p123456 -h127.0.0.1 -P6447 -e "SELECT @@hostname;"   # MYSQL142

场景C: 移除节点 removeInstance + 重新加入 ✅

js 复制代码
// 执行节点: 143 (当时主)
var c=dba.getCluster('myCluster');
c.removeInstance('192.168.195.142:3306');
// -> successfully removed from the cluster.
// 142 上查: performance_schema.replication_group_members 只剩自己, MEMBER_STATE=OFFLINE
// (数据保留, 只是被移出组与元数据)

// 重新加入(142数据还在,差量小 -> incremental 恢复, 不重克隆):
c.addInstance('cluster_admin:cluster_pass@192.168.195.142:3306', {recoveryMethod: 'incremental'});
// -> successfully added to the cluster. 全组 OK

recoveryMethod 选择(面试高频):

方式 原理 适用
clone 物理快照全量覆盖 新节点/数据损坏/GTID 缺口大(默认推荐)
incremental GTID 差量 binlog 补 节点短暂离线,GTID 是集群子集

场景D: 多数派故障(2节点宕机) ✅

bash 复制代码
# 执行节点: 141/142
systemctl stop mysqld       # 141、142 全停, 只剩 143
text 复制代码
实测结果:
- 143 视角: status=OK_NO_TOLERANCE_PARTIAL ("2 members are not active")
- 错误日志: 'Primary server switched to: 143的uuid'  <- 143 成单节点组, 继续当主
- 143 本地写入正常(SELECT @@read_only=0), orders=8
- 但! 部署在141上的 Router 不可用(6446 拒连) -- Router 与 DB 同机殉葬

生产启示:Router 必须独立部署或部署在应用侧(见场景G/J 双 Router 方案);本次恢复 141/142 后集群自动回 OK。

场景E: 网络分区(隔离主库) ✅

bash 复制代码
# 执行节点: 143 (当时 PRIMARY) 隔离组通信端口 33061
iptables -A INPUT  -p tcp --dport 33061 -j DROP
iptables -A OUTPUT -p tcp --dport 33061 -j DROP
text 复制代码
实测结果(40秒后):
- 多数派(141+142)视角: 143 仍列在成员里但超时驱逐; 若 143 持续失联最终被踢
- 143(少数派)自保护: INSERT 直接报
  ERROR 1290: The MySQL server is running with the --super-read-only option
  <- 少数派主动只读,杜绝脑裂写(比手搭 MGR 的"事务悬挂"体验更好, 因为
     exitStateAction=READ_ONLY + unreachable_majority 处理即时)
- 解除隔离(iptables -D ...)后 30 秒内自动回组, 数据追平

场景F: 隔离+宕机叠加(极端故障) ✅

bash 复制代码
# 执行节点: 143: iptables 隔离 33061 且 systemctl stop mysqld
text 复制代码
实测: 多数派 35 秒内完成:
1. 驱逐 143 -> 重新选举 -> 141 当选新 PRIMARY
2. Router 6446 自动指向 141
恢复 143(解除iptables+启动mysqld)后自动回组 SECONDARY, 全组 OK

场景G: 双 Router 容灾部署 ✅

bash 复制代码
# 执行节点: 142 (第二台 Router)
cd /usr/local && tar xf /usr/local/src/mysql-router-8.0.35-linux-glibc2.12-x86_64.tar.xz
ln -sf mysql-router-8.0.35-linux-glibc2.12-x86_64 mysql-router
mkdir -p /data/myrouter/6446 && chown -R mysql:mysql /data/myrouter
mysqlrouter --bootstrap cluster_admin:cluster_pass@192.168.195.141:3306 \
  --user=mysql --directory /data/myrouter/6446 --conf-use-sockets \
  --report-host='192.168.195.142'
# (再配 systemd 同 8.3, systemctl enable --now mysqlrouter)
mysql -utest_user -p123456 -h127.0.0.1 -P6446 -e "SELECT @@hostname;"   # -> 当前主
# cluster.listRouters() 现在能看到两台: 192.168.195.141:: 和 192.168.195.142::

场景H: PyMySQL 高可用压轴测试(PDF 最后章节) ✅

测试脚本 check_mysql_connection.py(PDF 原版+注释, 见附录A):

bash 复制代码
# 执行节点: 有 python3+pymysql 的机器(本环境为管理机)
pip3 install pymysql -i https://pypi.tuna.tsinghua.edu.cn/simple   # 国内源
python3 check_mysql_connection.py rw    # 每秒连一次 6446 打印当前节点

压轴实测(跑脚本中途杀主库 141)

text 复制代码
[18:25:21] [rw] 当前节点: MYSQL141     <- 正常
[18:25:26] [rw] 当前节点: MYSQL141     <- 最后一秒正常
[18:25:30] [rw] 操作失败: (2013, 'Lost connection to MySQL server during query')
[18:25:31] [rw] 当前节点: MYSQL142     <- 仅 1 次失败(~4秒),之后全程 MYSQL142
[18:26:15] ... 持续 MYSQL142

结论:应用层(带重试的连接循环)视角,failover = 1 次报错重连,业务无感恢复。这就是 InnoDB Cluster 的完整价值闭环。

场景I: 整集群宕机恢复 rebootClusterFromCompleteOutage ✅

bash 复制代码
# 三节点 systemctl stop mysqld -> 全体重启 systemctl start mysqld
# 重启后各节点 GR 是 OFFLINE(组没被引导)
js 复制代码
// 执行节点: 141
var c = dba.rebootClusterFromCompleteOutage('myCluster');
// -> The instance '192.168.195.142:3306' was successfully rejoined to the cluster.
// -> The instance '192.168.195.141:3306' was successfully rejoined to the cluster.
// status: OK, primary 自动选出(143), 数据完整(orders=8)

⚠️ 函数名易错:是 rebootClusterFromCompleteOutage(不是 rebootFromCompleteOutage)。

该命令会自动比对各节点 GTID/executed 集合, 选数据最新的做种子重组集群。

场景J: Router 自身高可用 ✅

bash 复制代码
# 执行节点: 141 停自己的 Router
systemctl stop mysqlrouter
# 应用切到 142 的 Router(客户端配多 Router 地址或前置 LB):
mysql -utest_user -p123456 -h192.168.195.142 -P6446 -e "SELECT @@hostname;"  # -> 主, 正常
# 恢复: systemctl start mysqlrouter

场景K: 日常检查命令集 ✅

js 复制代码
// 实例级配置体检(可对任意节点, 输出兼容性结论):
dba.checkInstanceConfiguration()
// -> Instance configuration is compatible with InnoDB cluster
// -> No incompatible tables detected   (还会检查是否有无主键表等 GR 不兼容对象)

// 集群拓扑描述(轻量, 不做健康检查):
var c=dba.getCluster(); c.describe()

// 集群参数(含每节点的 autoRejoinTries/consistency/exitStateAction 等):
c.options()

// Router 清单与健康打卡:
c.listRouters()    // lastCheckIn 时间戳即 Router 心跳

十、常见故障与排错汇总(实测踩坑实录)

10.1 addInstance 卡 "Waiting for server restart" 超时 (MYSQLSH 51156)

根因 :clone 完成后要 RESTART 实例, systemd 托管的 mysqld 无 supervisor 权限自启。

错误日志铁证:[MY-013462] Clone shutting down server as RESTART failed. Please start server to complete clone operation.

处置 :目标节点 systemctl start mysqld -> 主控端 cluster.rescan({addInstances: ['ip:3306'], interactive: false}) 收编。

预防 :RPM 包部署(自带 mysqld-sysctl 包装)或在 my.cnf 加 loose-group_replication_clone_threshold 调大减少 clone 触发。

10.2 rescan 报 "Failed to acquire Cluster lock" (MYSQLSH 51500)

根因 :前一个 mysqlsh 会话(卡死的 addInstance/rescan)还持有集群锁。

处置 :主控节点 pkill -9 mysqlsh 后重试。

预防:脚本化操作统一加超时;操作串行化。

10.3 新节点 GTID 分叉 (ERROR 3092 / "more executed transactions")

根因 :建管理账号时没 SET SQL_LOG_BIN=0, 本地多出组外事务。

处置 :全新节点 RESET MASTER 后重新 addInstance(clone 会覆盖,最稳)。

预防 :凡"管理性质"DDL(建用户/授权/装插件)先 SET SQL_LOG_BIN=0

10.4 Router 连不上 (ERROR 2003 Can't connect 6446)

排查链systemctl status mysqlrouter -> /data/myrouter/6446/log/mysqlrouter.log -> 元数据账号是否被删 -> 集群是否 NO_QUORUM(Router 无主可路由时拒连)。

本次实测 :141 宕机时其上的 Router 也随之不可用 -- Router 别和 DB 节点共生亡, 独立部署或双 Router+应用侧重试。

10.5 少数派写入报 1290 super-read-only

这不是故障,是保护:exitStateAction=READ_ONLY + 多数派失联 = 主动只读防脑裂。恢复网络/多数派后自动回 RW。

10.6 全集群宕机后各节点 OFFLINE 不自愈

设计如此 :谁都不许自引导(防脑裂)。用 dba.rebootClusterFromCompleteOutage('myCluster') 人工确认恢复(场景I)。


十一、运维命令速查卡

操作 命令(mysqlsh --uri cluster_admin:... --js)
集群状态 dba.getCluster().status()
拓扑描述 dba.getCluster().describe()
集群参数 dba.getCluster().options()
实例体检 dba.checkInstanceConfiguration()
加节点 c.addInstance('cluster_admin:xxx@ip:3306',{recoveryMethod:'clone'})
删节点 c.removeInstance('ip:3306')
手动切主 c.setPrimaryInstance('ip:3306')
节点重入 c.rejoinInstance('ip:3306')
元数据收编 c.rescan({addInstances:['ip:3306'],interactive:false})
整集群恢复 dba.rebootClusterFromCompleteOutage('myCluster')
Router 清单 c.listRouters()
解散集群 c.dissolve({force:false})
多主切换 c.switchToMultiPrimaryMode()
回单主 c.switchToSinglePrimaryMode('ip:3306')

GR 层排错视图(SQL, 任意节点)

sql 复制代码
SELECT * FROM performance_schema.replication_group_members;         -- 成员与状态
SELECT * FROM performance_schema.replication_group_member_stats\G   -- 事务/认证统计
SELECT * FROM mysql_innodb_cluster_metadata.instances;              -- 元数据拓扑

十二、补充知识点(超越 PDF 的生产必知)

  1. ClusterSet (8.0.27+):多个 InnoDB Cluster 组成"主集群+副本集群"的容灾/异地多活架构, dba.createClusterSet() 创建, 副本集群用异步复制(传统 GTID), 主集群故障可 clusterset.setPrimaryCluster() 整体切换。
  2. 读 replicas 扩展读cluster.addInstance(ip, {label:'rr1'}) + c.setupRouterAccount 后把节点配为 Read Replica 角色(不参与 MGR 投票,纯异步挂载)。
  3. 一致性与 fallbackround-robin-with-fallback 意味着从库全挂时 6447 会回退到主库 -- 读流量永不 100% 中断。
  4. MySQL Router 8.1+ 支持读写分离的新策略 与 REST API 完善(curl http://router:8443/api/20210810/routes), 便于监控探活。
  5. 备份恢复 :InnoDB Cluster 仍用 mysqlsh util.dumpInstance()/loadDump() 或 XtraBackup 集群外备份, 元数据库随实例一起备份。
  6. 与 MHA/Orchestrator 对比:MHA/Orch 是"外挂式"仲裁+VIP 切换; InnoDB Cluster 是"内建式"多数派仲裁+Router 路由, 无 VIP 依赖、无脑裂窗口, 是 8.0 官方主推方案。

附录A: 高可用测试脚本(PDF 完整版+注释)

python 复制代码
# check_mysql_connection.py
import pymysql, time, sys

hostname = '192.168.195.141'   # Router 地址(生产: 应用侧 VIP/LB 或多 Router 地址)
username = 'test_user'
password = '123456'
database = 'information_schema'

def check_mysql(hostname, port, username, password, database, mode):
    try:
        conn = pymysql.connect(host=hostname, port=port, user=username,
                               password=password, database=database,
                               connect_timeout=5, read_timeout=10)
        cursor = conn.cursor()
        cursor.execute("SELECT @@hostname")
        result = cursor.fetchone()
        print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] [{mode}] 当前节点: {result[0]}")
        cursor.close(); conn.close()
        return True
    except pymysql.Error as e:
        print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] [{mode}] 操作失败:", e)
        return False

mode = sys.argv[1] if len(sys.argv) > 1 else None
port = 6446 if mode == 'rw' else 6447   # rw->主 ro->从
while True:
    check_mysql(hostname, port, username, password, database, mode)
    time.sleep(1)

附录B: 环境现状快照(2026-08-16 18:30)

复制代码
集群: myCluster (Single-Primary, status=OK, 可容 1 故障)
  PRIMARY  = 192.168.195.143 (MYSQL143)
  SECONDARY= 192.168.195.141, 192.168.195.142
测试库: cluster_db (orders=8行, order_items=6行, 三节点一致)
账号: root/123456(远程), cluster_admin/cluster_pass(管理), test_user/123456(业务)
Router: 141:6446-6449 + 142:6446-6449 (双 Router, systemd 常驻, listRouters 可见)
组通信端口: 33061 | 元数据库: mysql_innodb_cluster_metadata
md5: server=c17075... shell=7332666... router=0dcddd4...

本笔记所有命令均为实测执行、输出为真实回显。按第四章起逐步复现即可从零搭出同款集群。

相关推荐
杨云龙UP1 小时前
生产环境MySQL多实例XtraBackup全量备份与rsync异地自动传输实践
linux·运维·数据库·mysql·xtrabackup·主从复制·备份恢复
熊文豪1 小时前
SQLServer数据迁移之后,那张报表还能不能秒出
数据库·sqlserver·电科金仓
JosieBook2 小时前
【数据库】把数据优势转化为决策优势:TimechoAI时序大模型使用解析
数据库
会编程的吕洞宾2 小时前
MySQL 慢查询从5秒到50毫秒:索引失效六大场景定位与实战调优
数据库·mysql
浅念-2 小时前
MySQL 索引底层完整详解|磁盘Page|B+树推导|聚簇非聚簇索引|索引SQL操作
大数据·数据库·b树·sql·mysql·面试·职场和发展
小马过河R2 小时前
数据仓库入门:是什么、怎么做、和数据库有什么区别?
大数据·数据库·数据仓库·架构·驾驭工程·fde
Mico182 小时前
Percona Toolkit 3.5.7 完整实战笔记(从入门到精通)
mysql
小白勇闯网安圈3 小时前
Django Form 组件详解:从表单生成到 ModelForm 数据校验
数据库·django·sqlite
梦想不只是梦与想3 小时前
MySQL 数据库(二):数据类型
数据库·mysql·数据类型