基于MyCat 中间件实现mysql集群读写分离与从库负载均衡(详细案例教程)
本案例基于 MyCat 2.0 + MySQL 8.0 一主两从集群 + Spring Boot 2.7.x 实现,通过 MyCat 作为中间件代理层,让应用无感知实现读写分离(写走主库、读走从库)与从库负载均衡,适合中大型项目或多服务集群场景。
一、案例环境准备
1. 服务器规划
角色 | IP地址 | 操作系统 | 核心软件 | 用途说明 |
---|---|---|---|---|
主库(Master) | 192.168.1.100 | Ubuntu 24.04 | MySQL 8.0.42 | 处理所有写请求,同步数据到从库 |
从库1(Slave1) | 192.168.1.101 | Ubuntu 24.04 | MySQL 8.0.42 | 处理读请求,负载均衡节点1 |
从库2(Slave2) | 192.168.1.102 | Ubuntu 24.04 | MySQL 8.0.42 | 处理读请求,负载均衡节点2 |
MyCat 中间件 | 192.168.1.200 | Ubuntu 24.04 | MyCat 2.0.1 | 代理数据库请求,实现读写分离 |
应用服务器 | 192.168.1.300 | Ubuntu 24.04 | Spring Boot 2.7.x | 业务应用,连接 MyCat 访问数据库 |
2. 前置条件
- 已完成 MySQL 一主两从集群配置(主从同步正常,从库
read_only=1
); - 所有服务器网络互通,关闭防火墙或开放关键端口:
- MySQL 端口:3306(主从库需开放给 MyCat);
- MyCat 端口:8066(应用连接 MyCat 的服务端口)、9066(MyCat 管理端口);
- 主库已创建业务数据库(如
test
)和表(如user
),从库已同步该数据。
二、Step 1:部署 MyCat 中间件(192.168.1.200 服务器)
MyCat 2.0 需依赖 JDK 1.8+,先安装 JDK,再部署 MyCat。
1. 安装 JDK 1.8
bash
# 1. 安装 OpenJDK 1.8
sudo apt update
sudo apt install openjdk-8-jdk -y
# 2. 验证 JDK 安装(输出 java version "1.8.0_xxx" 即成功)
java -version
2. 下载并解压 MyCat 2.0
bash
# 1. 进入安装目录(如 /opt)
cd /opt
# 2. 下载 MyCat 2.0.1 压缩包(官方地址,也可手动下载上传)
sudo wget https://github.com/MyCATApache/MyCat2/releases/download/2.0.1/MyCat-server-2.0.1-linux.tar.gz
# 3. 解压压缩包
sudo tar -zxvf MyCat-server-2.0.1-linux.tar.gz
# 4. 查看解压后的目录(MyCat 核心目录:bin(启动脚本)、conf(配置文件)、lib(依赖))
ls /opt/MyCat-server-2.0.1
3. 配置 MyCat 环境变量(可选,方便命令启动)
bash
# 1. 编辑环境变量配置文件
sudo vim /etc/profile
# 2. 在文件末尾添加以下内容(MyCat 解压路径)
export MYCAT_HOME=/opt/MyCat-server-2.0.1
export PATH=$PATH:$MYCAT_HOME/bin
# 3. 生效环境变量
source /etc/profile
# 4. 验证(输入 mycat 命令,显示用法即成功)
mycat
三、Step 2:配置 MyCat 核心参数(实现读写分离)
MyCat 实现读写分离的核心是 3 个配置文件:
conf/datasources.xml
:配置主从库的物理数据源(IP、账号、密码);conf/schema.xml
:定义逻辑库、逻辑表、数据节点,关联物理数据源与路由规则;conf/user.xml
:配置 MyCat 管理账号(应用连接 MyCat 时使用)。
1. 配置物理数据源(datasources.xml)
该文件用于告诉 MyCat:"主从库在哪里,用什么账号连接"。
bash
# 进入 MyCat 配置目录
cd /opt/MyCat-server-2.0.1/conf
# 编辑 datasources.xml(先备份原文件,避免配置错误)
sudo cp datasources.xml datasources.xml.bak
sudo vim datasources.xml
将文件内容替换为以下配置(根据实际主从库 IP、账号修改):
xml
<?xml version="1.0" encoding="UTF-8"?>
<datasources>
<!-- 1. 主库数据源(master_ds 为自定义名称,需与 schema.xml 对应) -->
<dataSource name="master_ds" type="mysql">
<property name="url">jdbc:mysql://192.168.1.100:3306/test?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true</property>
<property name="user">root</property> <!-- 主库账号(需有读写权限) -->
<property name="password">Root@123456</property> <!-- 主库密码 -->
<property name="driverClassName">com.mysql.cj.jdbc.Driver</property>
<property name="initSqls">SET NAMES utf8mb4</property> <!-- 初始化字符集 -->
<property name="maxConn">100</property> <!-- 最大连接数 -->
<property name="minConn">10</property> <!-- 最小连接数 -->
</dataSource>
<!-- 2. 从库1数据源(slave1_ds 为自定义名称) -->
<dataSource name="slave1_ds" type="mysql">
<property name="url">jdbc:mysql://192.168.1.101:3306/test?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true</property>
<property name="user">read_user</property> <!-- 从库只读账号 -->
<property name="password">Read@123456</property> <!-- 从库密码 -->
<property name="driverClassName">com.mysql.cj.jdbc.Driver</property>
<property name="initSqls">SET NAMES utf8mb4</property>
<property name="maxConn">100</property>
<property name="minConn">10</property>
</dataSource>
<!-- 3. 从库2数据源(slave2_ds 为自定义名称) -->
<dataSource name="slave2_ds" type="mysql">
<property name="url">jdbc:mysql://192.168.1.102:3306/test?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true</property>
<property name="user">read_user</property> <!-- 从库只读账号 -->
<property name="password">Read@123456</property> <!-- 从库密码 -->
<property name="driverClassName">com.mysql.cj.jdbc.Driver</property>
<property name="initSqls">SET NAMES utf8mb4</property>
<property name="maxConn">100</property>
<property name="minConn">10</property>
</dataSource>
</datasources>
2. 配置逻辑库与路由规则(schema.xml)
该文件是 MyCat 读写分离的核心,用于定义:
- 逻辑库(应用连接的库名,与物理库
test
对应); - 数据节点(关联逻辑库与物理数据源);
- 读写分离规则(写走主库,读走从库,从库负载均衡)。
bash
# 备份原文件
sudo cp schema.xml schema.xml.bak
sudo vim schema.xml
替换为以下配置:
xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<!-- 1. 逻辑库配置(name:应用连接的库名,如 test_db;checkSQLschema:是否去掉 SQL 中的库名前缀) -->
<schema name="test_db" checkSQLschema="false" sqlMaxLimit="100">
<!-- 逻辑表配置(name:物理表名 user;dataNode:关联的数据节点 dn1;rule:分片规则,这里单表无需分片,用 auto-sharding-long) -->
<table name="user" dataNode="dn1" rule="auto-sharding-long" />
<!-- 若有其他表,继续添加 <table> 标签,如 <table name="order" dataNode="dn1" rule="auto-sharding-long" /> -->
</schema>
<!-- 2. 数据节点配置(name:数据节点名 dn1;dataHost:关联的数据源集群;database:物理数据库名 test) -->
<dataNode name="dn1" dataHost="mysql_cluster" database="test" />
<!-- 3. 数据源集群配置(核心:定义主从、读写分离、负载均衡) -->
<dataHost name="mysql_cluster" maxCon="1000" minCon="10" balance="1" writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1" slaveThreshold="100">
<!-- 心跳检测 SQL(MyCat 定期执行该 SQL 检测主从库是否存活) -->
<heartbeat>select 1</heartbeat>
<!-- 3.1 写主机(主库):处理所有写请求(INSERT/UPDATE/DELETE/DDL) -->
<writeHost host="master_host" url="master_ds" user="root" password="Root@123456">
<!-- 3.2 读主机(从库):处理所有读请求(SELECT),balance=1 表示从库负载均衡 -->
<readHost host="slave1_host" url="slave1_ds" user="read_user" password="Read@123456" />
<readHost host="slave2_host" url="slave2_ds" user="read_user" password="Read@123456" />
</writeHost>
<!-- (可选)备用主库:若主库宕机,MyCat 自动切换到备用主库(需配置双主复制) -->
<!-- <writeHost host="backup_master_host" url="backup_master_ds" user="root" password="Root@123456">
<readHost host="backup_slave_host" url="backup_slave_ds" user="read_user" password="Read@123456" />
</writeHost> -->
</dataHost>
</mycat:schema>
关键参数说明(决定读写分离与负载均衡)
参数名 | 取值/配置 | 作用说明 |
---|---|---|
balance |
1 | 从库负载均衡模式:1 表示"读请求分发到所有从库(轮询)",0 表示"读请求走主库",2 表示"读写请求均走所有节点" |
writeType |
0 | 写请求路由模式:0 表示"所有写请求走第一个 writeHost(主库)",1 表示"写请求随机分发到 writeHost" |
switchType |
1 | 主库故障切换模式:1 表示"基于心跳检测自动切换",-1 表示"不自动切换" |
slaveThreshold |
100 | 从库延迟阈值(单位:秒):若从库同步延迟超过 100 秒,MyCat 不再将读请求分发到该从库 |
3. 配置 MyCat 访问账号(user.xml)
该文件用于配置应用连接 MyCat 时使用的账号和权限(类似 MySQL 的账号管理)。
bash
# 备份原文件
sudo cp user.xml user.xml.bak
sudo vim user.xml
替换为以下配置(自定义账号密码,授予逻辑库 test_db
的权限):
xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mycat:user SYSTEM "user.dtd">
<mycat:user xmlns:mycat="http://io.mycat/">
<!-- 应用访问 MyCat 的账号(name:账号名;password:密码) -->
<user name="mycat_app" password="MyCat@123456">
<!-- 授予该账号对逻辑库 test_db 的所有权限(readWrite:读写权限;db:逻辑库名) -->
<privileges check="false">
<schema name="test_db" dml="0" showTables="true">
<table name="user" dml="0" /> <!-- dml=0 表示所有 DML 权限(INSERT/UPDATE/DELETE/SELECT) -->
</schema>
</privileges>
</user>
<!-- (可选)MyCat 管理账号(用于通过 9066 端口管理 MyCat,如查看节点状态) -->
<user name="mycat_admin" password="Admin@123456" admin="true">
<privileges check="false">
<schema name="*" dml="0" />
</privileges>
</user>
</mycat:user>
权限说明
dml="0"
:授予所有 DML 权限(SELECT、INSERT、UPDATE、DELETE);dml="1"
:仅授予 SELECT 权限(只读);admin="true"
:标记为管理账号,可通过 9066 端口执行 MyCat 管理命令(如show @@datasource
)。
四、Step 3:启动 MyCat 并验证状态
1. 启动 MyCat
bash
# 进入 MyCat 安装目录(或直接用 mycat 命令,已配置环境变量)
cd /opt/MyCat-server-2.0.1
# 启动 MyCat(start:启动;status:查看状态;stop:停止)
sudo ./bin/mycat start
# 查看 MyCat 状态(显示"MyCat-server is running." 即成功)
sudo ./bin/mycat status
# 查看 MyCat 日志(若启动失败,通过日志排查错误,如端口被占用、数据源配置错误)
tail -f logs/wrapper.log
2. 验证 MyCat 数据源连接(管理端口 9066)
通过 MySQL 客户端连接 MyCat 的管理端口(9066),查看主从库数据源是否正常:
bash
# 连接 MyCat 管理端口(账号:mycat_admin,密码:Admin@123456,端口:9066)
mysql -u mycat_admin -pAdmin@123456 -h 192.168.1.200 -P 9066
# 执行 MyCat 管理命令,查看数据源状态
show @@datasource;
正常输出示例(关键看 STATUS
列)
NAME | TYPE | HOST | PORT | DB_NAME | STATUS | ACTIVE_CONN | IDLE_CONN |
---|---|---|---|---|---|---|---|
master_ds | mysql | 192.168.1.100 | 3306 | test | OK | 0 | 10 |
slave1_ds | mysql | 192.168.1.101 | 3306 | test | OK | 0 | 10 |
slave2_ds | mysql | 192.168.1.102 | 3306 | test | OK | 0 | 10 |
STATUS=OK
:表示数据源连接正常;- 若
STATUS=ERROR
:检查主从库 IP、账号密码是否正确,网络是否互通。
3. 验证 MyCat 服务端口(应用连接端口 8066)
通过 MySQL 客户端连接 MyCat 的服务端口(8066),验证逻辑库和表是否正常访问:
bash
# 连接 MyCat 服务端口(账号:mycat_app,密码:MyCat@123456,端口:8066,逻辑库:test_db)
mysql -u mycat_app -pMyCat@123456 -h 192.168.1.200 -P 8066 -D test_db
# 执行 SQL 验证(查看逻辑库中的表,应显示 user 表)
show tables;
# 查看 user 表数据(应返回主库同步到从库的数据)
select * from user;
- 若能正常显示表和数据,说明 MyCat 与主从库连接正常,逻辑库配置生效。
五、Step 4:应用对接 MyCat(Spring Boot 示例)
应用无需感知主从库,只需连接 MyCat 的服务端口(8066)和逻辑库(test_db),所有读写请求由 MyCat 自动路由。
1. 应用依赖(pom.xml)
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.15</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>mycat-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- Spring Boot Web(测试接口) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MyBatis-Plus(数据访问,也可使用 JPA) -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.4.1</version>
</dependency>
<!-- MySQL 驱动(适配 MySQL 8.0) -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
<scope>runtime</scope>
</dependency>
<!-- Lombok(简化实体类) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
2. 应用配置(application.yml)
核心:数据库连接地址改为 MyCat 的 IP、端口(8066)和逻辑库(test_db),无需任何读写分离相关配置(由 MyCat 处理)。
yaml
server:
port: 8080
spring:
datasource:
# 连接 MyCat(而非直接连接主从库)
url: jdbc:mysql://192.168.1.200:8066/test_db?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
username: mycat_app # MyCat 配置的应用账号
password: MyCat@123456 # MyCat 配置的应用密码
driver-class-name: com.mysql.cj.jdbc.Driver
# MyBatis-Plus 配置(打印 SQL 日志,方便验证读写分离)
mybatis-plus:
mapper-locations: classpath*:mapper/**/*.xml
type-aliases-package: com.example.entity
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 打印 SQL 日志
3. 应用代码(实体类 + Mapper + Service + Controller)
代码与直接连接 MySQL 完全一致,无需任何修改。
3.1 实体类(User.java)
java
package com.example.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("user") // 对应 MyCat 逻辑表 user(实际是主从库的 test.user 表)
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String username;
private String password;
private Integer age;
private LocalDateTime createTime;
}
3.2 Mapper 接口(UserMapper.java)
java
package com.example.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.entity.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper extends BaseMapper<User> {
// 无需额外方法,BaseMapper 提供 CRUD 操作
}
3.3 Service 实现(UserServiceImpl.java)
java
package com.example.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.entity.User;
import com.example.mapper.UserMapper;
import com.example.service.UserService;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
// 写操作:新增用户(MyCat 自动路由到主库)
@Override
public boolean addUser(User user) {
return save(user);
}
// 读操作:按 ID 查询(MyCat 自动路由到从库,负载均衡)
@Override
public User getUserById(Long id) {
return getById(id);
}
// 读操作:查询所有用户(MyCat 自动路由到从库,负载均衡)
@Override
public List<User> listAllUsers() {
return list();
}
}
3.4 Controller 接口(UserController.java)
java
package com.example.controller;
import com.example.entity.User;
import com.example.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
// 写接口:新增用户(MyCat 路由到主库)
@PostMapping("/add")
public String addUser(@RequestBody User user) {
boolean success = userService.addUser(user);
return success ? "新增用户成功(MyCat 路由到主库)" : "新增失败";
}
// 读接口:按 ID 查询(MyCat 路由到从库)
@GetMapping("/get/{id}")
public User getUserById(@PathVariable Long id) {
return userService.getUserById(id);
}
// 读接口:查询所有(MyCat 路由到从库)
@GetMapping("/list")
public List<User> listAllUsers() {
return userService.listAllUsers();
}
}
六、Step 5:验证读写分离与从库负载均衡
启动 Spring Boot 应用,通过接口测试工具(Postman、浏览器)调用接口,结合 MyCat 日志和主从库日志验证效果。
1. 验证"写操作路由到主库"
步骤1:调用新增用户接口
-
请求方式:POST
-
URL:
http://192.168.1.300:8080/user/add
-
请求体:
json{ "username": "mycat_test", "password": "123456", "age": 30 }
步骤2:查看 MyCat 写日志
MyCat 会记录写请求路由到主库的日志:
bash
# 查看 MyCat 业务日志(写请求会显示"route to writeHost")
tail -f /opt/MyCat-server-2.0.1/logs/mycat.log
步骤3:验证主从数据同步
- 主库查询:
SELECT * FROM test.user;
→ 能看到mycat_test
; - 从库1/2查询:
SELECT * FROM test.user;
→ 能看到mycat_test
(主从同步正常)。
2. 验证"读操作路由到从库,负载均衡"
步骤1:多次调用查询接口
- 请求方式:GET
- URL:
http://192.168.1.300:8080/user/get/1
(假设新增用户 ID 为 1) - 连续调用 3 次。
步骤2:查看 MyCat 读日志
MyCat 会记录读请求路由到的从库,观察是否轮询:
bash
tail -f /opt/MyCat-server-2.0.1/logs/mycat.log
正常日志示例(轮询负载均衡)
2025-09-10 15:00:00 [INFO] Route read request to readHost: slave1_host (slave1_ds)
2025-09-10 15:00:05 [INFO] Route read request to readHost: slave2_host (slave2_ds)
2025-09-10 15:00:10 [INFO] Route read request to readHost: slave1_host (slave1_ds)
- 第 1 次请求:路由到
slave1
; - 第 2 次请求:路由到
slave2
; - 第 3 次请求:路由到
slave1
; - 证明从库负载均衡生效(轮询策略)。
3. 验证"从库故障自动剔除"
步骤1:模拟从库1宕机
bash
# 在从库1(192.168.1.101)执行,停止 MySQL 服务
sudo systemctl stop mysql
步骤2:查看 MyCat 数据源状态
bash
# 连接 MyCat 管理端口,查看数据源状态
mysql -u mycat_admin -pAdmin@123456 -h 192.168.1.200 -P 9066
show @@datasource;
输出结果(slave1_ds 状态变为 ERROR)
NAME | TYPE | HOST | PORT | DB_NAME | STATUS | ACTIVE_CONN | IDLE_CONN |
---|---|---|---|---|---|---|---|
master_ds | mysql | 192.168.1.100 | 3306 | test | OK | 0 | 10 |
slave1_ds | mysql | 192.168.1.101 | 3306 | test | ERROR | 0 | 0 |
slave2_ds | mysql | 192.168.1.102 | 3306 | test | OK | 0 | 10 |
步骤3:再次调用读接口
此时所有读请求会自动路由到 slave2
(故障从库 slave1
被剔除),MyCat 日志显示:
2025-09-10 15:05:00 [INFO] Route read request to readHost: slave2_host (slave2_ds)
2025-09-10 15:05:05 [INFO] Route read request to readHost: slave2_host (slave2_ds)
七、常见问题排查
1. MyCat 启动失败,日志显示"数据源连接失败"
- 原因:主从库 IP、账号密码错误,或主从库未开放 3306 端口给 MyCat。
- 解决:
- 验证 MyCat 服务器能否 ping 通主从库:
ping 192.168.1.100
; - 验证主从库 3306 端口是否开放:
telnet 192.168.1.100 3306
; - 检查
datasources.xml
中的账号密码是否正确,主从库是否存在该账号。
- 验证 MyCat 服务器能否 ping 通主从库:
2. 应用连接 MyCat 成功,但查询不到表
- 原因:
schema.xml
中逻辑表配置错误,或主从库中无该表。 - 解决:
- 检查
schema.xml
中<table>
标签的name
是否与主从库表名一致; - 检查主从库是否存在该表:
SELECT * FROM test.user;
。
- 检查
3. 读请求未负载均衡,始终走一个从库
- 原因:
schema.xml
中balance
参数配置错误(如balance=0
)。 - 解决:确保
dataHost
标签的balance="1"
,重启 MyCat 生效:sudo mycat restart
。
八、总结
本案例通过 MyCat 中间件实现了 "应用无感知的读写分离与从库负载均衡",核心优势:
- 应用零侵入:应用只需连接 MyCat,无需修改任何代码,后续主从库扩容/切换不影响应用;
- 自动负载均衡:读请求按轮询策略分发到从库,故障从库自动剔除;
- 可扩展性强:支持分库分表、双主故障切换等高级功能,适合中大型项目。
后续可根据业务需求优化:
- 配置双主复制 + MyCat 自动切换,提升主库高可用;
- 增加从库节点,通过
balance=1
自动扩展读性能; - 配置从库权重(MyCat 支持
readHost
加weight
参数),按从库性能分配读请求。