MySQL高可用方案MIC&mysqlCluster+mysqlRouter

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/

直接下载:https://downloads.mysql.com/archives/get/p/43/file/mysql-shell-8.0.26-linux-glibc2.12-x86-64bit.tar.gz

下载解压后设置环境变量:/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
相关推荐
Dying.Light几秒前
MySQL相关问题
数据库·mysql
蜡笔小炘34 分钟前
LVS -- 利用防火墙标签(FireWall Mark)解决轮询错误
服务器·数据库·lvs
韩立学长38 分钟前
基于Springboot泉州旅游攻略平台d5h5zz02(程序、源码、数据库、调试部署方案及开发环境)系统界面展示及获取方式置于文档末尾,可供参考。
数据库·spring boot·旅游
Re.不晚1 小时前
MySQL进阶之战——索引、事务与锁、高可用架构的三重奏
数据库·mysql·架构
老邓计算机毕设1 小时前
SSM智慧社区信息化服务平台4v5hv(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面
数据库·ssm 框架·智慧社区、·信息化平台
麦聪聊数据2 小时前
为何通用堡垒机无法在数据库运维中实现精准风控?
数据库·sql·安全·低代码·架构
2301_790300962 小时前
Python数据库操作:SQLAlchemy ORM指南
jvm·数据库·python
m0_736919102 小时前
用Pandas处理时间序列数据(Time Series)
jvm·数据库·python
亓才孓2 小时前
[JDBC]PreparedStatement替代Statement
java·数据库
m0_466525293 小时前
绿盟科技风云卫AI安全能力平台成果重磅发布
大数据·数据库·人工智能·安全