一、业务背景

项目 详情
时间 2014年6月 - 2015年9月
产品类型 互联网动态站点 商城
用户数量 10000 - 12000(用户量猛增)
PV 100000 - 500000(24小时访问次数总和)
QPS 50 - 100*(每秒访问次数)
DAU 2000(每日活跃用户数)

随着活跃用户数增多,读取数据的请求变多,单台MySQL已无法满足需求。核心诉求是:使用多台MySQL数据库服务器降低单台压力,实现集群架构的稳定性和高可用性,同时保证数据的一致性和完整性。解决方案聚焦于主从复制 + 读写分离


二、MySQL读写分离概述

2.1 读写分离的目的

数据库负载均衡:当数据库请求增多时,单例数据库无法满足业务需求,需要进行数据库实例的扩容,多台数据库同时响应请求。

读写分离:数据库的负载均衡不同于其他服务的负载均衡,因为数据要求一致性。基于主从复制的基础上,常见的数据库负载均衡使用读写分离方式——写操作走主数据库,读操作走从数据库。可以认为数据库读写分离是一种特殊的负载均衡实现。

2.2 读写分离的常见实现方式

方式一:业务代码实现读写分离

在业务代码中判断数据操作是读还是写,读连接从数据库服务器,写连接主数据库服务器。以LNMP架构为例,需要通过PHP代码实现读写分离。

方式二:中间件代理方式

业务代码不直接连接数据库,而是先请求到中间件服务器(代理),由代理服务器判断读操作走从库、写操作走主库。

常见中间件方案:

中间件 来源 说明
MySQL Proxy MySQL官方 测试版,不再维护
Atlas 奇虎360 基于MySQL Proxy
DBProxy 美团点评 -
Amoeba 早期阿里巴巴 -
Cobar 阿里巴巴 -
MyCat 基于阿里开源的Cobar 国内最活跃
Kingshard - Go语言开发
ProxySQL - -

如何选择?

  • 业务实现读写分离:操作方便,成本低,当前开发框架基本支持。
  • 中间件代理:除了实现读写分离,还可以作为数据库集群的管理平台。

三、MySQL主从复制的三种方式

3.1 基于SQL语句的复制(SBR)

优点:

  • 历史悠久,技术成熟
  • binlog文件较小
  • binlog中包含所有数据库更改信息,可用于审核数据库安全
  • binlog可用于实时还原,而不仅仅用于复制
  • 主从版本可以不一样,从服务器版本可以比主服务器版本高

缺点:

  • 不是所有UPDATE语句都能被复制,尤其是包含不确定操作时
  • 复制需要进行全表扫描的UPDATE时,需要比RBR请求更多的行级锁
  • 对于复杂语句,在从服务器上的耗资源情况更严重
  • 数据表必须几乎和主服务器保持一致,否则可能导致复制出错
  • 执行复杂语句出错时会消耗更多资源

3.2 基于行的复制(RBR)

优点:

  • 任何情况都可以被复制,最安全可靠
  • 和其他大多数数据库系统的复制技术一样
  • 多数情况下,从服务器上的表如果有主键,复制会快很多

缺点:

  • binlog大了很多
  • 复杂的回滚时binlog中会包含大量数据
  • 主服务器执行UPDATE时,所有发生变化的记录都会写入binlog,频繁导致binlog并发写问题
  • 无法从binlog中看到复制了什么语句

3.3 混合模式复制(MBR)

MySQL 5以后出现的复制方式,结合SBR和RBR的特点。


四、MySQL主从复制的原理

4.1 什么是主从复制?

主从复制用来建立一个和主数据库完全一样的数据库环境(从数据库),主数据库一般是准实时的业务数据库。

4.2 主从复制的作用

  1. 数据热备:主数据库故障后可切换到从数据库继续工作,避免数据丢失。
  2. 架构扩展:业务量增大、I/O访问频率过高时,做多库存储,降低磁盘I/O访问频率,提高单机I/O性能。
  3. 读写分离:使数据库能支撑更大并发,尤其在报表场景中,报表SQL走从库不会影响前台服务。

4.3 主从复制原理详解

核心机制:主数据库的 bin-log二进制日志 记录了所有SQL语句,目标是将主库bin-log的SQL语句复制过来,让从库在 relay-log重做日志文件 中再执行一次这些SQL语句。

主从复制涉及 三个线程

线程 位置 作用
binlog dump thread 主库 每当从库连接时,主库创建一个线程发送binlog内容到从库
I/O线程 从库 连接主库,请求主库发送binlog更新记录,拷贝到本地relay log文件
SQL线程 从库 读取relay log中的更新事件并执行

复制流程五步骤:

步骤一:主库的更新事件(update、insert、delete)被写入binlog
    ↓
步骤二:从库发起连接,连接到主库
    ↓
步骤三:主库创建binlog dump thread线程,把binlog内容发送到从库
    ↓
步骤四:从库启动后创建I/O线程,读取主库传来的binlog内容并写入relay log
    ↓
步骤五:从库创建SQL线程,从relay log中读取内容,从Exec_Master_Log_Pos位置开始执行更新事件,
        将更新内容写入slave的数据库

五、实验环境规划

一、环境信息

项目 版本/说明
操作系统 CentOS 7.x / Rocky Linux 8.x(64位)
MySQL 8.0.x
JDK jdk1.8.0_192(仅 MyCAT 节点需要)
MyCAT Mycat-server-1.6.5
主从复制方式 基于 bin-log 的异步复制
读写分离实现 MyCAT 中间件代理方式

二、IP 地址规划

主机 IP地址 角色 安装内容
服务器A 192.168.30.122 MySQL Master(主库) MySQL 服务端
服务器B 192.168.30.124 MySQL Slave(从库) MySQL 服务端
服务器C 192.168.30.126 MyCAT 中间件 JDK + MySQL 客户端

三、架构拓扑

                    ┌──────────────────────────┐
                    │     192.168.30.126        │
                    │       MyCAT 中间件          │
                    │    客户端:8066  管理:9066   │
                    └────────────┬─────────────┘
                                 │
                ┌────────────────┴────────────────┐
                ▼                                  ▼
     ┌──────────────────┐              ┌──────────────────┐
     │  192.168.30.122  │   bin-log    │  192.168.30.124  │
     │   MySQL Master   │ ──────────▶ │   MySQL Slave     │
     │     (写操作)      │   异步复制   │     (读操作)      │
     └──────────────────┘              └──────────────────┘

实验前准备:三台服务器统一操作

Step 1:关闭防火墙(实验环境)

三台服务器全部执行:

systemctl stop firewalld
systemctl disable firewalld

# 或
setenforce 0
sed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config

Step 2:配置 MySQL 客户端(方便后续操作)

三台都执行,避免每次敲全路径:

echo 'export PATH=/usr/local/mysql/bin:$PATH' >> /etc/profile
source /etc/profile

Step 3:准备依赖包

https://download.csdn.net/download/qq_44769717/92997521


实验一:MySQL 主从复制


1-1 Master 配置(192.168.30.122)

前置按照mysql

yum install -y mariadb-server mariadb

# 按照完之后启动
systemctl start mariadb

# 测试mysql
mysql -uroot -p
#然后回车进入mysql

在这里插入图片描述

① 编辑 MySQL 配置文件
vim /etc/my.cnf

[mysqld] 段添加:

[mysqld]
log-bin = mysql-bin
server-id = 1

在这里插入图片描述

重启 MySQL:

systemctl restart mariadb
② 创建 Slave 复制用户

用 root 登录 MySQL:

mysql -uroot -p
#回车进入mysql
-- MySQL 8.0 正确写法:先创建用户,再授权
CREATE USER 'slave'@'192.168.30.%' IDENTIFIED BY '123456';
GRANT REPLICATION SLAVE ON *.* TO 'slave'@'192.168.30.%';
FLUSH PRIVILEGES;

在这里插入图片描述

验证:

SELECT user, host, plugin FROM mysql.user WHERE user = 'slave';

期望看到 pluginmysql_native_password

在这里插入图片描述

③ 重置 binlog(清理历史遗留)
RESET MASTER;
④ 查看 Master 状态
SHOW MASTER STATUS;

输出示例:

+------------------+----------+
| File             | Position |
+------------------+----------+
| mysql-bin.000001 |      155 |
+------------------+----------+

务必记录这两个值,后续 Slave 配置要用。

在这里插入图片描述

1-2 Slave 配置(192.168.30.124)

① 编辑 MySQL 配置文件
vim /etc/my.cnf

[mysqld] 段添加:

[mysqld]
server-id = 2

重启 MySQL:

systemctl restart mariadb
② 处理克隆实例的 UUID 冲突

提示:如果 Slave 是从 Master 克隆的虚拟机,两者的 auto.cnf 中 server-uuid 相同,主从无法互相识别。必须删除该文件后重启,让 MySQL 自动生成新 UUID。

rm -f /usr/local/mysql/data/auto.cnf
systemctl restart mariadb

在这里插入图片描述

③ 测试网络连通性

在 Slave 上测试能否连接 Master:

mysql -h 192.168.30.122 -uslave -p'123456' -e "SELECT 1;"

如果返回 1 说明网络和用户均正常。

在这里插入图片描述

④ 清除旧的主从配置
STOP SLAVE;
RESET SLAVE ALL;

提示:RESET SLAVE ALLRESET SLAVE 更彻底,会把之前残留的 CHANGE MASTER TO 配置全部清掉,避免指向错误的 binlog 文件。

在这里插入图片描述

⑤ 配置主从关系

先去主mysql里查看
在这里插入图片描述

回到 Slave上用新值执行将 Master上 SHOW MASTER STATUS 的 File 和 Position 值填入:

STOP SLAVE;
RESET SLAVE ALL;

CHANGE MASTER TO
MASTER_HOST='192.168.30.122',
MASTER_USER='slave',
MASTER_PASSWORD='123456',
-- 这里填Master新File值
MASTER_LOG_FILE='mysql-bin.000001', 
-- 这里填Master新Position值
MASTER_LOG_POS=245;

在这里插入图片描述

启动复制:

START SLAVE;

在这里插入图片描述

⑥ 验证主从状态
SHOW SLAVE STATUS\G;

目标:

Slave_IO_Running: Yes
Slave_SQL_Running: Yes

在这里插入图片描述

1-3 主从复制验证

在 Master 上创建测试数据:

CREATE DATABASE IF NOT EXISTS test_db;
USE test_db;
CREATE TABLE test_table (id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50));
INSERT INTO test_table (name) VALUES ('hello_master');

在这里插入图片描述

在 Slave 上查询:

USE test_db;
SELECT * FROM test_table;

能查到数据即主从复制成功。
在这里插入图片描述

实验二:MyCAT 读写分离


注意
读写分离在主从复制的基础上进行操作

2-1 后端 MySQL 准备(Master .122 和 Slave .124 都要做)

① 创建 tp5shop 数据库和用户

两台 MySQL 分别执行:

-- 创建业务数据库
CREATE DATABASE IF NOT EXISTS tp5shop;

-- 尝试删掉可能残留的空壳用户
DROP USER 'tp5shop'@'192.168.30.%';

-- 创建用户(MariaDB 语法)
CREATE USER 'tp5shop'@'192.168.30.%' IDENTIFIED BY '$Abc3721';

-- 授权
GRANT ALL PRIVILEGES ON tp5shop.* TO 'tp5shop'@'192.168.30.%';
GRANT SELECT ON *.* TO 'tp5shop'@'192.168.30.%';
FLUSH PRIVILEGES;

-- 确认
SELECT user, host FROM mysql.user WHERE user = 'tp5shop';


FLUSH PRIVILEGES;

主mysql
在这里插入图片描述

从mysql:

在这里插入图片描述

验证:

SELECT user, host, plugin FROM mysql.user WHERE user = 'tp5shop';
SHOW DATABASES LIKE 'tp5shop';

主mysql

在这里插入图片描述

从mysql

在这里插入图片描述

2-2 MyCAT 服务器安装(192.168.30.126)

① 安装 JDK
# 解压
tar -xvzf jdk-8u261-linux-x64.tar.gz 
 mv jdk1.8.0_261/ /usr/local/java

# 配置环境变量
# 1.简单版
echo 'PATH=/usr/local/java/bin:$PATH' >> /etc/profile
source /etc/profile
# 2.完整版
cat >> /etc/profile << 'EOF'
# Java Environment
export JAVA_HOME=/usr/local/java
export JRE_HOME=$JAVA_HOME/jre
export CLASSPATH=.:$JAVA_HOME/lib:$JRE_HOME/lib
export PATH=$JAVA_HOME/bin:$PATH
EOF
source /etc/profile


# 验证
java -version

在这里插入图片描述
简单版
在这里插入图片描述
完整版
在这里插入图片描述

② 安装 MySQL 客户端(仅客户端,不需要服务端)
yum install -y mariadb

提示:MyCAT 只是 Java 中间件,不存数据,不需要安装 mysqld 服务端。装 mariadb 包只为了获得 mysql 命令行工具用于测试连接。

在这里插入图片描述

验证:

which mysql

在这里插入图片描述

③ 安装 MyCAT
tar -xvzf Mycat-server-1.6.7.3-release-20190927161129-linux.tar.gz -C /usr/local

在这里插入图片描述


2-3 配置 MyCAT

① 配置 server.xml

编辑 /usr/local/mycat/conf/server.xml,确保 root 用户配置如下:

vim /usr/local/mycat/conf/server.xml
<user name="root" defaultAccount="true">
    <property name="password">123456</property>
    <property name="schemas">TESTDB</property>
</user>

在这里插入图片描述

② 配置 schema.xml(核心文件)
cat > /usr/local/mycat/conf/schema.xml << 'EOF'
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">

    <schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn3">
    </schema>

    <dataNode name="dn3" dataHost="localhost1" database="tp5shop" />

    <dataHost name="localhost1" maxCon="1000" minCon="10" balance="1"
              writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
        <heartbeat>select user()</heartbeat>
        <writeHost host="hostM1" url="192.168.30.122:3306" user="tp5shop" password="$Abc3721">
            <readHost host="hostS2" url="192.168.30.124:3306" user="tp5shop" password="$Abc3721" />
        </writeHost>
    </dataHost>

</mycat:schema>
EOF

提示:

  • schema 标签必须添加 dataNode="dn3" 属性,否则 MyCAT 不知道路由到哪个后端数据库
  • dataNodedatabase 必须和后端真实数据库名一致(这里是 tp5shop,不是默认的 db3
  • balance="1" 必须设置,balance="0" 表示不开启读写分离
  • writeHostreadHost 分别指向 Master(.122)和 Slave(.124)

在这里插入图片描述

③ 配置 MySQL 客户端默认认证(解决 MySQL 8.0 客户端兼容性)
cat >> /etc/my.cnf << 'EOF'
[client]
default-auth=mysql_native_password
EOF

提示:MySQL 8.0 客户端默认使用 caching_sha2_password 连接,MyCAT 1.6.5 前后端握手都不支持该协议。不加此配置,连接 MyCAT 8066 端口时会报 ERROR 1045 Access denied for user 'root'

在这里插入图片描述

2-4 启动与验证

① 启动 MyCAT
# 方式1
/usr/local/mycat/bin/mycat start
# 方式2
/usr/local/mycat/bin/mycat console

mycat console 和 mycat start 的区别
两个命令都在 /usr/local/mycat/bin/ 目录下,用来启动 MyCAT,但运行方式不同

命令 运行方式 日志输出 终端关闭后 适用场景
mycat console 前台运行 直接打印在终端屏幕上 MyCAT 会停止 调试、排错、看实时日志
mycat start 后台守护进程 写入 logs/wrapper.log 文件 继续运行 生产环境、正式使用

检查进程和端口:

netstat -lntp | grep 8066

应看到 java 进程监听 8066。

在这里插入图片描述

② 看日志确认后端连接正常
tail -20 /usr/local/mycat/logs/mycat.log

应该看到 connectionAcquired 信息,没有任何 connectionErrorAccess denied
在这里插入图片描述

③ mycat客户端连接测试(8066 端口)
mysql -h127.0.0.1 -uroot -p123456 -P8066

在这里插入图片描述

进入后测试:

SHOW DATABASES;
-- 应看到 TESTDB

USE TESTDB;
SHOW TABLES;

-- 建表测试
CREATE TABLE test(id INT, name VARCHAR(20));
INSERT INTO test VALUES(1, 'mycat_test');
SELECT * FROM test;

在这里插入图片描述

④ mycat客户端测试(9066 端口)
mysql -h127.0.0.1 -uroot -p123456 -P9066
SHOW @@help;
SHOW @@heartbeat;

心跳正常说明读写分离已生效。

在这里插入图片描述

查看是否心跳正常

-- 查看数据源详情(连接数、读写计数、状态)
show @@datasource;

-- 查看心跳状态(你已经验证过)
show @@heartbeat;

-- 查看所有管理命令帮助
show @@help;

-- 查看MyCat运行时参数
show @@server;

心跳状态:
执行 SHOW @@heartbeat; 后,关键字段含义如下:

  • RS_CODE = 1:相当于“ping 通了”。MyCAT 向后端 MySQL 发送 SELECT user() 心跳检测,MySQL 正常返回结果即为 1,这是判断后端节点健康的核心依据。
  • RETRY = 0:重试次数为 0,说明从 MyCAT 启动至今,该节点心跳一次都未失败过,无需重试。
  • EXECUTE_TIME = 1,1,1:最近三次心跳的执行耗时(单位:毫秒)。三个 1 说明响应极快,网络通畅。如果心跳失败,此处会显示为空或异常值。
  • STATUS = idle:连接池当前状态为空闲,等待处理 SQL 请求。如果状态变为 error,则说明该节点已挂掉。
  • STOP = false:表示该节点未被手动停用。可以通过命令 STOP @@HEARTBEAT 'hostM1' 将其设为 true,届时 MyCAT 将不再向此节点转发请求。false 表示节点正常工作。
  • LAST_ACTIVE_TIME:最后一次心跳成功的时间戳。可以间隔一段时间(如10秒)再次执行 SHOW @@heartbeat;,观察此时间戳是否更新,以确认心跳在持续运行。

2-5 读写分离验证

方法一:分别在 Master 和 Slave 查看写入数据

# Master(.122)
mysql -uroot -p 
SELECT * FROM tp5shop.test;

# Slave(.124)
mysql -uroot -p
SELECT * FROM tp5shop.test;

主mysql
在这里插入图片描述
从mysql
在这里插入图片描述

两边数据一致,说明:

  • MyCAT 写操作 → 路由到 Master(.122)
  • Master → binlog 复制到 Slave(.124)
  • MyCAT 读操作 → 路由到 Slave(.124)

方法二:主从mysql远程连接mycat测试

主mysql测试
在这里插入图片描述
从mysql测试
在这里插入图片描述

方法三:更直观的测试读写分离
1.在 Slave 上直接插入一条独有数据
在 Slave 服务器(192.168.30.124)上执行:

USE tp5shop;

INSERT INTO test VALUES (999, 'slave_only_data_from_124');

SELECT * FROM test;

在这里插入图片描述
2.在 Master 上确认没有这条数据

USE tp5shop;

SELECT * FROM test;

在这里插入图片描述
3.通过 MyCAT 查询

mysql -h192.168.30.126 -uroot -p123456 -P8066 --default-auth=mysql_native_password
USE TESTDB;

SELECT * FROM test;

在这里插入图片描述
4.判断结果

场景 MyCAT 能否查到 Slave 独有数据 结论
✅ 能查到 读操作确实路由到了 Slave 读写分离生效
❌ 查不到 读操作走了 Master 或未开启读写分离 读写分离未生效

核心逻辑:Slave 上这条数据是绕过主从复制直接插入的,Master 上根本没有。如果 MyCAT 能查出来,唯一的解释就是 MyCAT 把这条 SELECT 请求分发给了 Slave。物理隔离证明比任何日志都直观。

完整操作速查表

实验 步骤 服务器 关键命令
主从复制 1 Master .122 vim /etc/my.cnflog-bin + server-id=1
主从复制 2 Master .122 CREATE USER 'slave'@'...' IDENTIFIED WITH mysql_native_password
主从复制 3 Master .122 RESET MASTER; SHOW MASTER STATUS;
主从复制 4 Slave .124 vim /etc/my.cnfserver-id=2
主从复制 5 Slave .124 rm -f auto.cnf(克隆环境)
主从复制 6 Slave .124 RESET SLAVE ALL; CHANGE MASTER TO ...; START SLAVE;
主从复制 7 Slave .124 SHOW SLAVE STATUS\G → 双 Yes
读写分离 1 Master+Slave CREATE USER 'tp5shop'@'...' IDENTIFIED WITH mysql_native_password
读写分离 2 MyCAT .126 装 JDK + MySQL 客户端
读写分离 3 MyCAT .126 server.xml + schema.xml
读写分离 4 MyCAT .126 /etc/my.cnfdefault-auth=mysql_native_password
读写分离 5 MyCAT .126 mycat startmysql -P8066 连接验证
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐