基于MyCat 中间件实现mysql集群读写分离与从库负载均衡教程(详细案例教程)
基于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
# 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
# 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 环境变量(可选,方便命令启动)
# 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:“主从库在哪里,用什么账号连接”。
# 进入 MyCat 配置目录
cd /opt/MyCat-server-2.0.1/conf# 编辑 datasources.xml(先备份原文件,避免配置错误)
sudo cp datasources.xml datasources.xml.bak
sudo vim datasources.xml
将文件内容替换为以下配置(根据实际主从库 IP、账号修改):
<?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
对应); - 数据节点(关联逻辑库与物理数据源);
- 读写分离规则(写走主库,读走从库,从库负载均衡)。
# 备份原文件
sudo cp schema.xml schema.xml.bak
sudo vim schema.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 的账号管理)。
# 备份原文件
sudo cp user.xml user.xml.bak
sudo vim user.xml
替换为以下配置(自定义账号密码,授予逻辑库 test_db
的权限):
<?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
# 进入 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),查看主从库数据源是否正常:
# 连接 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),验证逻辑库和表是否正常访问:
# 连接 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 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 处理)。
server:port: 8080spring:datasource:# 连接 MyCat(而非直接连接主从库)url: jdbc:mysql://192.168.1.200:8066/test_db?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=trueusername: mycat_app # MyCat 配置的应用账号password: MyCat@123456 # MyCat 配置的应用密码driver-class-name: com.mysql.cj.jdbc.Driver# MyBatis-Plus 配置(打印 SQL 日志,方便验证读写分离)
mybatis-plus:mapper-locations: classpath*:mapper/**/*.xmltype-aliases-package: com.example.entityconfiguration:map-underscore-to-camel-case: truelog-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 打印 SQL 日志
3. 应用代码(实体类 + Mapper + Service + Controller)
代码与直接连接 MySQL 完全一致,无需任何修改。
3.1 实体类(User.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)
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)
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 自动路由到主库)@Overridepublic boolean addUser(User user) {return save(user);}// 读操作:按 ID 查询(MyCat 自动路由到从库,负载均衡)@Overridepublic User getUserById(Long id) {return getById(id);}// 读操作:查询所有用户(MyCat 自动路由到从库,负载均衡)@Overridepublic List<User> listAllUsers() {return list();}
}
3.4 Controller 接口(UserController.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 {@Autowiredprivate 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
- 请求体:
{"username": "mycat_test","password": "123456","age": 30 }
步骤2:查看 MyCat 写日志
MyCat 会记录写请求路由到主库的日志:
# 查看 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 会记录读请求路由到的从库,观察是否轮询:
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宕机
# 在从库1(192.168.1.101)执行,停止 MySQL 服务
sudo systemctl stop mysql
步骤2:查看 MyCat 数据源状态
# 连接 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
参数),按从库性能分配读请求。