1. ShardingSphere-JDBC读写分离实战指南
在数据库访问压力较大的系统中,读写分离是提升性能的常见手段。ShardingSphere-JDBC作为轻量级的Java框架,提供了透明的读写分离能力,无需修改业务代码即可实现。本文将基于SpringBoot+MyBatis-Plus环境,详细演示如何配置和使用ShardingSphere-JDBC的读写分离功能。
1.1 环境准备与项目搭建
1.1.1 初始化SpringBoot项目
推荐使用阿里云的Spring Initializr服务(http://start.aliyun.com)快速生成项目骨架。选择以下关键配置:
- 项目类型:Maven项目
- 语言:Java
- SpringBoot版本:2.3.7.RELEASE(与ShardingSphere 5.1.1兼容性最佳)
- 打包方式:Jar
- Java版本:8或11
注意:SpringBoot 2.3.x版本与ShardingSphere 5.x的兼容性最佳。若使用SpringBoot 3.x,需要选择ShardingSphere的更高版本并注意JDK版本要求。
1.1.2 关键依赖说明
完整的pom.xml依赖配置如下:
xml复制<dependencies>
<!-- SpringBoot Web基础依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- ShardingSphere JDBC核心依赖 -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
<version>5.1.1</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
<version>8.0.26</version>
</dependency>
<!-- MyBatis-Plus增强工具 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.1</version>
</dependency>
<!-- Lombok简化代码 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
依赖选择要点:
shardingsphere-jdbc-core-spring-boot-starter是核心依赖,提供了与SpringBoot的自动配置集成- MyBatis-Plus版本建议使用3.3.x系列,与SpringBoot 2.3.x兼容性好
- MySQL驱动建议使用8.0.x版本,支持新特性且性能更好
1.2 数据层设计与实现
1.2.1 实体类定义
创建用户实体类,对应数据库中的t_user表:
java复制package com.example.shardingdemo.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@TableName("t_user")
@Data
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String uname;
}
实体类设计注意事项:
@TableName注解明确指定表名,避免ORM框架自动转换导致的表名不一致问题- 主键使用自增策略(IdType.AUTO),确保主库写入时ID连续
- Lombok的@Data注解自动生成getter/setter等方法,简化代码
1.2.2 Mapper接口定义
创建基础的MyBatis-Plus Mapper接口:
java复制package com.example.shardingdemo.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.shardingdemo.entity.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
Mapper开发建议:
- 继承BaseMapper可获得基础的CRUD方法
- 复杂查询可自定义方法并配合@Select等注解
- 建议为每个实体创建独立的Mapper接口
1.3 读写分离配置详解
1.3.1 基础数据源配置
在application.properties中配置主从数据源:
properties复制# 应用基础配置
spring.application.name=sharding-jdbc-demo
server.port=8080
# ShardingSphere运行模式(内存模式)
spring.shardingsphere.mode.type=Memory
# 数据源名称列表
spring.shardingsphere.datasource.names=master,slave1
# 主库配置
spring.shardingsphere.datasource.master.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.master.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.master.jdbc-url=jdbc:mysql://master-host:3306/db_user?useSSL=false&serverTimezone=Asia/Shanghai
spring.shardingsphere.datasource.master.username=root
spring.shardingsphere.datasource.master.password=123456
spring.shardingsphere.datasource.master.maximum-pool-size=20
# 从库1配置
spring.shardingsphere.datasource.slave1.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.slave1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.slave1.jdbc-url=jdbc:mysql://slave1-host:3306/db_user?useSSL=false&serverTimezone=Asia/Shanghai
spring.shardingsphere.datasource.slave1.username=root
spring.shardingsphere.datasource.slave1.password=123456
spring.shardingsphere.datasource.slave1.maximum-pool-size=20
配置优化建议:
- 使用HikariCP连接池,性能优于传统的DBCP
- 为生产环境配置合理的连接池大小(maximum-pool-size)
- 建议开启SSL加密连接(useSSL=true)并配置正确的证书
- 为不同数据源设置不同的连接池参数,如主库可以设置更大的连接数
1.3.2 读写分离规则配置
配置核心的读写分离规则:
properties复制# 读写分离规则配置
# 数据源名称
spring.shardingsphere.rules.readwrite-splitting.data-sources.myds.type=Static
# 写数据源(主库)
spring.shardingsphere.rules.readwrite-splitting.data-sources.myds.props.write-data-source-name=master
# 读数据源(从库,多个用逗号分隔)
spring.shardingsphere.rules.readwrite-splitting.data-sources.myds.props.read-data-source-names=slave1
# 负载均衡算法配置
spring.shardingsphere.rules.readwrite-splitting.data-sources.myds.load-balancer-name=alg_weight
# 负载均衡算法定义
# 轮询算法
spring.shardingsphere.rules.readwrite-splitting.load-balancers.alg_round.type=ROUND_ROBIN
# 随机算法
spring.shardingsphere.rules.readwrite-splitting.load-balancers.alg_random.type=RANDOM
# 权重算法
spring.shardingsphere.rules.readwrite-splitting.load-balancers.alg_weight.type=WEIGHT
spring.shardingsphere.rules.readwrite-splitting.load-balancers.alg_weight.props.slave1=1
# 打印SQL日志
spring.shardingsphere.props.sql-show=true
配置解析:
myds是逻辑数据源名称,业务代码中不感知物理数据源- Static类型表示静态配置(还有Dynamic类型支持动态发现)
- 支持三种负载均衡算法:轮询(ROUND_ROBIN)、随机(RANDOM)、权重(WEIGHT)
- sql-show=true会打印实际执行的SQL,方便调试
2. 功能测试与验证
2.1 基础读写分离测试
2.1.1 写入测试
创建测试类验证写操作:
java复制@SpringBootTest
class WriteReadTest {
@Autowired
private UserMapper userMapper;
@Test
void testInsert() {
User user = new User();
user.setUname("测试用户");
int affectRows = userMapper.insert(user);
assertEquals(1, affectRows);
}
}
预期行为:
- 写入操作会自动路由到主库(master)
- 通过主从复制机制,数据会同步到从库(slave1)
- 返回影响行数为1表示写入成功
2.1.2 读取测试
验证读操作的路由情况:
java复制@Test
void testSelect() {
List<User> users = userMapper.selectList(null);
assertFalse(users.isEmpty());
}
观察日志可以看到:
- 读操作被路由到从库(slave1)
- 实际执行的SQL会带有从库的连接信息
- 返回结果与主库数据一致(需确保主从同步延迟在可接受范围内)
2.2 事务场景测试
2.2.1 事务中的读写行为
java复制@Transactional
@Test
void testTransactional() {
// 写入操作
User user = new User();
user.setUname("事务用户");
userMapper.insert(user);
// 读取操作
User queryUser = userMapper.selectById(user.getId());
assertNotNull(queryUser);
}
事务特性说明:
- 在@Transactional注解的方法内,所有读写操作都会使用主库
- 这是为了保证事务内数据的一致性(避免读到未提交的数据)
- 测试环境下事务默认回滚,可通过@Rollback(false)关闭
2.2.2 事务隔离级别建议
在application.properties中添加:
properties复制# 事务隔离级别配置
spring.shardingsphere.props.max.connections.size.per.query=5
spring.shardingsphere.props.acquire-retry-attempts=3
spring.shardingsphere.props.acquire-retry-delay-milliseconds=500
事务配置建议:
- 对于读多写少的应用,可以适当增加从库的连接数
- 网络不稳定的环境可配置重试机制
- 根据业务特点选择合适的隔离级别
2.3 负载均衡测试
2.3.1 多从库负载均衡
当有多个从库时,修改配置:
properties复制# 多个从库配置
spring.shardingsphere.datasource.names=master,slave1,slave2
# 从库2配置
spring.shardingsphere.datasource.slave2.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.slave2.jdbc-url=jdbc:mysql://slave2-host:3306/db_user
# 更新读写分离配置
spring.shardingsphere.rules.readwrite-splitting.data-sources.myds.props.read-data-source-names=slave1,slave2
# 权重配置
spring.shardingsphere.rules.readwrite-splitting.load-balancers.alg_weight.props.slave1=2
spring.shardingsphere.rules.readwrite-splitting.load-balancers.alg_weight.props.slave2=1
测试负载均衡效果:
java复制@Test
void testLoadBalance() {
// 多次查询观察路由情况
for (int i = 0; i < 10; i++) {
userMapper.selectList(null);
}
}
负载均衡策略:
- 权重2:1表示slave1会承担约2/3的读请求
- 可通过日志观察请求是否按预期分布
- 对于性能差异较大的从库,可通过权重调整流量分配
3. 生产环境注意事项
3.1 主从延迟问题处理
3.1.1 强制走主库
对于实时性要求高的读操作,可以强制走主库:
java复制// 使用HintManager强制路由到主库
try (HintManager hintManager = HintManager.getInstance()) {
hintManager.setWriteRouteOnly();
userMapper.selectById(1L); // 这次查询会走主库
}
3.1.2 延迟监控方案
建议实现监控机制检测主从延迟:
- 在从库执行
SHOW SLAVE STATUS查看Seconds_Behind_Master - 当延迟超过阈值时告警或自动切换路由策略
- 可在应用层添加时间戳判断,只读取已同步的数据
3.2 高可用方案
3.2.1 主库故障转移
建议配置:
- 使用数据库中间件(如ProxySQL)实现自动故障转移
- 配置ShardingSphere的数据库发现功能:
properties复制# 动态数据源发现
spring.shardingsphere.rules.readwrite-splitting.data-sources.myds.type=Dynamic
spring.shardingsphere.rules.readwrite-splitting.data-sources.myds.props.auto-aware-data-source-name=ha-group
3.2.2 从库健康检查
配置从库健康检查策略:
properties复制# 数据源健康检查
spring.shardingsphere.datasource.health-check.enabled=true
spring.shardingsphere.datasource.health-check.interval-millis=30000
spring.shardingsphere.datasource.health-check.max-retry=3
spring.shardingsphere.datasource.health-check.retry-timeout-millis=5000
3.3 性能优化建议
-
连接池优化:
- 主库连接池适当增大(考虑写并发)
- 从库连接池根据读比例配置
- 设置合理的空闲连接超时时间
-
SQL优化:
- 避免跨库事务
- 复杂查询尽量走主库
- 使用索引优化查询性能
-
监控指标:
- 监控各数据源的连接数、活跃数
- 跟踪SQL执行时间
- 记录路由统计信息
4. 常见问题排查
4.1 配置不生效问题
症状:读写操作没有按预期路由
排查步骤:
- 确认
sql-show=true是否开启,查看实际执行的SQL - 检查数据源名称是否与配置一致(大小写敏感)
- 验证MySQL主从复制是否正常工作
- 检查是否有事务注解影响了路由行为
4.2 性能问题
症状:查询响应慢或超时
解决方案:
- 检查从库负载情况,考虑增加从库数量
- 优化负载均衡策略,将更多流量导向性能好的从库
- 检查网络延迟,特别是跨机房部署的情况
- 分析慢查询日志,优化SQL和索引
4.3 事务一致性异常
症状:事务内读取到旧数据
处理方法:
- 确保事务内的读操作都走主库(默认行为)
- 检查主从复制延迟,优化数据库配置
- 对于关键业务,考虑使用分布式事务
- 实现应用层的补偿机制
在实际使用ShardingSphere-JDBC的过程中,我们发现合理的分库分表策略配合读写分离能显著提升系统性能。特别是在读多写少的场景下,通过增加从库数量可以线性提升系统的查询吞吐量。不过也需要注意,过度拆分会增加系统复杂度,建议根据实际业务压力逐步扩展。