01高可用方案介绍
mysqlCluster+mysqlRouter这是一套由 MySQL 官方提供的、开箱即用的、完整的数据库高可用与扩展方案。结合了多个组件,以实现自动故障转移、读写分离和负载均衡。
MySQL Cluster 简介:
MySQL Cluster(也称为 NDB Cluster)是一个基于内存的分布式数据库系统,具备以下核心特性:
- Shared-nothing 架构:每个节点独立拥有自己的内存和磁盘,无单点瓶颈。
- 自动分片(Sharding):数据自动分布在多个数据节点(Data Nodes)上。
- 同步复制:数据在多个副本之间同步复制,确保高可用和强一致性。
- 节点角色:
- 管理节点(Management Node):负责配置和监控。
- 数据节点(Data Node):存储实际数据,支持多副本。
- SQL 节点(SQL Node):提供 MySQL Server 接口,供应用连接。
MySQL Cluster 本身已具备高可用能力(如节点故障自动切换、数据冗余等),但客户端直接连接 SQL 节点仍可能因某 SQL 节点宕机而中断连接。
MySQL Router 的作用
MySQL Router 是 MySQL 官方提供的轻量级中间件,用于透明地将客户端请求路由到后端 MySQL 实例(说白了就是nginx反向代理),主要功能包括:
- 连接路由:根据配置将读/写请求转发到合适的 MySQL 实例
- 故障转移(Failover):当主节点不可用时,自动切换到备用节点
- 负载均衡:支持多个只读实例间的读请求分发
- 透明性:应用无需感知后端拓扑变化
架构图如下:

02主机规划
下面做一个简单的daemon演示一下,装备3个主机
| IP | hosts | 角色 |
|---|---|---|
| 10.0.0.31 | node1 | InnodbCluster PRIMARY |
| 10.0.0.41 | node2 | InnodbCluster SECONDARY |
| 10.0.0.232 | node3 | InnodbCluster SECONDARY |
| 10.0.0.51 | 无 | mysqlRouter |
也就是这样:

03 配置Innodb Cluster
温馨提示:
数据库安装过程和相关工具安装过程我简写了,本文适用于有数据管理、linux使用经验的人士使用。如何二进制安装mysql8.0.26请参考我的博客:
https://blog.csdn.net/qq_73797346/article/details/142687873?fromshare=blogdetail&sharetype=blogdetail&sharerId=142687873&sharerefer=PC&sharesource=qq_73797346&sharefrom=from_link
1.二进制安装mysql8.0.26,配置文件使用如下内容,每个节点只需要修改server_id
shell
[mysqld]
skip-name-resolve
server_id=2
port=3306
user=mysql
# mysqlx=0 mysql-8.0有这个功能
basedir=/usr/local/mysql-8.0.26
datadir=/app/mysql-8.0.26/data
socket=/app/mysql-8.0.26/mysql.sock
character-set-server=utf8mb4
# 关闭区分表名大小写
# lower_case_table_names=1
max_allowed_packet=1024M
#init-file=/app/mysql-8.0.26/conf/init.sql
# 日志相关配置
# binlog
sync_binlog=1
log_bin=/app/mysql-8.0.26/logs/binlog
max_binlog_size=1024M
expire-logs-days=30
# error log
log_error=/app/mysql-8.0.26/logs/error.log
# slow log
slow_query_log=1
slow_query_log_file=/app/mysql-8.0.26/logs/slow.log
long_query_time=1
log_queries_not_using_indexes=0
# 事务相关配置
gtid_mode=on
enforce_gtid_consistency=on
transaction_isolation=READ-COMMITTED
binlog_transaction_dependency_tracking = WRITESET
transaction_write_set_extraction = XXHASH64
# innodb相关设置
innodb_buffer_pool_size=2G
innodb_log_file_size=512M
innodb_file_per_table=1
innodb_flush_log_at_trx_commit=1
# 主从同步相关设置
relay_log=mysql-relay-bin
slave_parallel_type = LOGICAL_CLOCK
slave_preserve_commit_order = ON
slave_parallel_workers = 4
下载mysqlShell,用于管理msyqlCluster和mysqlRouter。在任意一个节点安装都可以。
下载地址:https://downloads.mysql.com/archives/shell/
下载解压后设置环境变量:/app/mysql-shell-8.0.26/bin
所有节点设置hsots解析
shell
[root@db51 ~]# tail -3 /etc/hosts
10.0.0.31 node1
10.0.0.41 node2
10.0.0.232 node3
所有节点提前改好root密码
sql
set session sql_log_bin=0;
create user root@'%' identified with mysql_native_password by 'aa';
grant all on *.* to root@'%' with grant option;
alter user root@'localhost' identified by 'aa';
set session sql_log_bin=1;
1.在node1输入mysqlsh。注意不要开启mysql的safe_update,否则不能创建集群
添加主机
sql
dba.configureInstance('root:aa@10.0.0.31:3306', { clusterAdmin:'cluster_admin', clusterAdminPassword: 'aa' })
dba.configureInstance('root:aa@10.0.0.41:3306', { clusterAdmin:'cluster_admin', clusterAdminPassword: 'aa' })
dba.configureInstance('root:aa@10.0.0.232:3306', { clusterAdmin:'cluster_admin', clusterAdminPassword: 'aa' })
输出示例:
shell
MySQL JS > dba.configureInstance('root:aa@10.0.0.31:3306', { clusterAdmin:'cluster_admin', clusterAdminPassword: 'aa' })
Configuring local MySQL instance listening at port 3306 for use in an InnoDB cluster...
This instance reports its own address as node1:3306
Clients and other cluster members will communicate with it through this address by default. If this is not correct, the report_host MySQL system variable should be changed.
Assuming full account name 'cluster_admin'@'%' for cluster_admin
applierWorkerThreads will be set to the default value of 4.
The instance 'node1:3306' is valid to be used in an InnoDB cluster.
Cluster admin user 'cluster_admin'@'%' created.
The instance 'node1:3306' is already ready to be used in an InnoDB cluster.
Successfully enabled parallel appliers
2.再添加主机完成后,开始创建集群
sql
shell.connect('cluster_admin:aa@10.0.0.31:3306')
dba.createCluster('myCluster', { disableClone:false })
3.把节点加入集群,默认使用克隆的方式。如果mysql是通过systemd启动的,克隆之后的实例并不会自动启动,需要手动去拉起。
在node1的mysqlshell执行:
sql
cluster=dba.getCluster('myCluster')
cluster.addInstance('cluster_admin:aa@10.0.0.41:3306')
node2启动失败,去node2的命令行执行 systemctl start mysqld-8.0.26.service
shell
NOTE: node2:3306 is shutting down...
* Waiting for server restart... timeout
WARNING: Clone process appears to have finished and tried to restart the MySQL server, but it has not yet started back up.
然后新检测节点状态,并继续添加流程
sql
cluster.rescan()
再次查看node2成功加入,也可以使用 cluster.status() 查看节点列表
sql
MySQL 10.0.0.31:3306 ssl JS > cluster.rescan()
Rescanning the cluster...
Result of the rescanning operation for the 'myCluster' cluster:
{
"name": "myCluster",
"newTopologyMode": null,
"newlyDiscoveredInstances": [
{
"host": "node2:3306",
"member_id": "1c0124cb-acce-11f0-a107-000c292ef90e",
"name": null,
"version": "8.0.26"
}
],
"unavailableInstances": [],
"updatedInstances": []
}
A new instance 'node2:3306' was discovered in the cluster.
Would you like to add it to the cluster metadata? [Y/n]: y
Adding instance to the cluster metadata...
The instance 'node2:3306' was successfully added to the cluster metadata
接下来继续添加node3
4.查看节点列表
json
cluster.status()
{
"clusterName": "myCluster",
"defaultReplicaSet": {
"name": "default",
"primary": "node1:3306",
"ssl": "REQUIRED",
"status": "OK",
"statusText": "Cluster is ONLINE and can tolerate up to ONE failure.",
"topology": {
"node1:3306": {
"address": "node1:3306",
"memberRole": "PRIMARY",
"mode": "R/W",
"readReplicas": {},
"replicationLag": null,
"role": "HA",
"status": "ONLINE",
"version": "8.0.26"
},
"node2:3306": {
"address": "node2:3306",
"memberRole": "SECONDARY",
"mode": "R/O",
"readReplicas": {},
"replicationLag": null,
"role": "HA",
"status": "ONLINE",
"version": "8.0.26"
},
"node3:3306": {
"address": "node3:3306",
"memberRole": "SECONDARY",
"mode": "R/O",
"readReplicas": {},
"replicationLag": null,
"role": "HA",
"status": "ONLINE",
"version": "8.0.26"
}
},
"topologyMode": "Single-Primary"
},
"groupInformationSourceMember": "node1:3306"
}
04 配置mysqlRouter
下载地址:https://downloads.mysql.com/archives/router/
直接下载:https://downloads.mysql.com/archives/get/p/41/file/mysql-router-8.0.26-linux-glibc2.12-x86_64.tar.xz
下载解压后设置环境变量 /app/mysql-router-8.0.26/bin
1.基于刚刚创建的 Cluster 初始化 MySQL Router
shell
mkdir -p /app/mysql-router-data
mysqlrouter --bootstrap cluster_admin@10.0.0.31:3306 \
--user=mysql \
--directory /app/mysql-router-data \
--conf-use-sockets --report-host='10.0.0.51'
# Bootstrapping MySQL Router instance at '/app/mysql-router-data'...
- Creating account(s) (only those that are needed, if any)
- Verifying account (using it to run SQL queries that would be run by Router)
- Storing account in keyring
- Adjusting permissions of generated files
- Creating configuration /app/mysql-router-data/mysqlrouter.conf
# MySQL Router configured for the InnoDB Cluster 'myCluster'
After this MySQL Router has been started with the generated configuration
$ mysqlrouter -c /app/mysql-router-data/mysqlrouter.conf
the cluster 'myCluster' can be reached by connecting to:
## MySQL Classic protocol
- Read/Write Connections: 10.0.0.51:6446, /app/mysql-router-data/mysql.sock
- Read/Only Connections: 10.0.0.51:6447, /app/mysql-router-data/mysqlro.sock
## MySQL X protocol
- Read/Write Connections: 10.0.0.51:6448, /app/mysql-router-data/mysqlx.sock
- Read/Only Connections: 10.0.0.51:6449, /app/mysql-router-data/mysqlxro.sock
6446:对应 Classic 协议的读写操作。
6447 :对应 Classic 协议的只读操作。
6448 :对应 X 协议的读写操作。
6449: 对应 X 协议的只读操作。
8443 :提供 REST API 的 HTTP 端口。
在mysqlshell中查看router
json
MySQL 10.0.0.31:3306 ssl JS > cluster.listRouters()
{
"clusterName": "myCluster",
"routers": {
"10.0.0.51::": {
"hostname": "10.0.0.51",
"lastCheckIn": null,
"roPort": 6447,
"roXPort": 6449,
"rwPort": 6446,
"rwXPort": 6448,
"version": "8.0.26"
}
}
}
2.启动Router
shell
# 启动 MySQL Router
sh /data/myrouter/6446/start.sh
# 除了脚本,也可通过 mysqlrouter 命令直接启动
mysqlrouter -c /app/mysql-router-data/mysqlrouter.conf --user=mysql &
05读写分离测试
3.在任意一个节点创建用户 create user app@'%' identified with mysql_native_password by 'aa'; 测试脚本
python
#!/usr/bin/env python3
import pymysql
import time
import sys
hostname = '10.0.0.51'
username = 'app'
password = 'aa'
database = 'information_schema'
def check_mysql(host, port, user, passwd, db):
try:
conn = pymysql.connect(
host=host,
port=port,
user=user,
password=passwd,
database=db,
connect_timeout=3
)
cursor = conn.cursor()
cursor.execute("SELECT @@hostname;")
result = cursor.fetchone()
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] 已连接 {host}:{port} -> 当前节点: {result[0]}")
cursor.close()
conn.close()
return True
except pymysql.Error as e:
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] 连接 {host}:{port} 失败: {e}")
return False
if len(sys.argv) < 2:
print("❌ 用法: python check_router.py [rw|ro]")
sys.exit(1)
mode = sys.argv[1].lower()
if mode == 'rw':
port = 6446
elif mode == 'ro':
port = 6447
else:
print("❌ 参数错误,应为 rw 或 ro")
sys.exit(1)
print(f"✅ 开始测试 {mode.upper()} 模式 ({hostname}:{port}) ...")
while True:
check_mysql(hostname, port, username, password, database)
time.sleep(2)

4.如果报错遇到
shell
"check_mysql.py" 50L, 1259B written
[root@node3 ~]# python3 check_mysql.py ro
✅ 开始测试 RO 模式 (10.0.0.51:6447) ...
[2025-10-19 19:49:19] 连接 10.0.0.51:6447 失败: (2003, "Can't connect to remote MySQL server for client connected to '0.0.0.0:6447'")
[2025-10-19 19:49:21] 连接 10.0.0.51:6447 失败: (2003, "Can't connect to remote MySQL server for client connected to '0.0.0.0:6447'")
[2025-10-19 19:49:23] 连接 10.0.0.51:6447 失败: (2003, "Can't connect to remote MySQL server for client connected to '0.0.0.0:6447'")
[2025-10-19 19:49:25] 连接 10.0.0.51:6447 失败: (2003, "Can't connect to remote MySQL server for client connected to '0.0.0.0:6447'")
^CTraceback (most recent call last):
File "/root/check_mysql.py", line 49, in <module>
time.sleep(2)
KeyboardInterrupt
[root@node3 ~]# telnet 10.0.0.51 64447
Trying 10.0.0.51...
telnet: Unable to connect to remote host: Connection refused
[root@node3 ~]# telnet 10.0.0.51 6447
Trying 10.0.0.51...
Connected to 10.0.0.51.
Escape character is '^]'.
NCan't connect to remote MySQL server for client connected to '0.0.0.0:6447'Connection closed by foreign host.
tail /app/mysql-router-data/log/mysqlrouter.log
2025-10-19 19:53:48 metadata_cache ERROR [7f16acf33700] Failed to connect to metadata server
2025-10-19 19:53:48 metadata_cache ERROR [7f16acf33700] Failed fetching metadata from any of the 3 metadata servers.
2025-10-19 19:53:48 metadata_cache WARNING [7f16acf33700] Failed connecting with Metadata Server node1:3306: Unknown MySQL server host 'node1' (2) (2005)
2025-10-19 19:53:48 metadata_cache ERROR [7f16acf33700] Failed to connect to metadata server
2025-10-19 19:53:48 metadata_cache WARNING [7f16acf33700] Failed connecting with Metadata Server node2:3306: Unknown MySQL server host 'node2' (2) (2005)
2025-10-19 19:53:48 metadata_cache ERROR [7f16acf33700] Failed to connect to metadata server
2025-10-19 19:53:48 metadata_cache WARNING [7f16acf33700] Failed connecting with Metadata Server node3:3306: Unknown MySQL server host 'node3' (2) (2005)
那是因为数据库节点都设置了其他节点hosts解析,而router节点没有,添加进去就好了
shell
[root@db51 ~]# hostname -I
10.0.0.51 172.17.0.1
[root@db51 ~]# tail -3 /etc/hosts
10.0.0.31 node1
10.0.0.41 node2
10.0.0.232 node3
或者提前指定使用ip+端口号建立集群,my.cnf
[mysqld]
# node1 改为自己的 IP
report_host=10.0.0.31
report_port=3306