SpringBoot3实战:ShardingJDBC 5.5.0读写分离与主库路由高阶技巧
最近在重构一个订单管理系统时,遇到了数据库性能瓶颈——高峰期查询请求导致主库负载飙升,而多个从库却处于闲置状态。这让我意识到,是时候引入读写分离架构了。经过技术选型,最终选择了ShardingJDBC 5.5.0作为解决方案,不仅因为它与SpringBoot3完美兼容,更因其轻量级、无侵入的特性。本文将分享从基础配置到高级特性的完整实现路径,特别是那些官方文档没有明确说明的实战细节。
1. 环境准备与依赖配置
在开始之前,需要确认几个关键点:SpringBoot3要求Java17+环境,而ShardingJDBC 5.5.0是首个全面支持SpringBoot3的版本。以下是经过生产验证的依赖组合:
<dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-jdbc</artifactId> <version>5.5.0</version> <exclusions> <exclusion> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-test-util</artifactId> </exclusion> </exclusions> </dependency>关键排除项:必须移除test-util依赖,否则会与SpringBoot3的测试框架冲突。同时建议使用HikariCP连接池,它在ShardingJDBC环境下表现最优:
spring: datasource: driver-class-name: org.apache.shardingsphere.driver.ShardingSphereDriver url: jdbc:shardingsphere:classpath:sharding.yaml注意:allow-bean-definition-overriding必须设为true,否则启动时会报Bean冲突
2. 核心配置解剖:sharding.yaml详解
配置文件是ShardingJDBC的核心,以下是经过优化的读写分离配置模板:
dataSources: master: driverClassName: com.mysql.cj.jdbc.Driver jdbcUrl: jdbc:mysql://master-host:3306/db username: user password: pass slave1: driverClassName: com.mysql.cj.jdbc.Driver jdbcUrl: jdbc:mysql://slave1-host:3306/db username: user password: pass rules: - !SINGLE tables: - "*.*" defaultDataSource: master - !READWRITE_SPLITTING dataSources: readwrite_ds: writeDataSourceName: master readDataSourceNames: - slave1 loadBalancerName: round_robin props: sql-show: true关键配置项解析:
!SINGLE规则:必须配置且放在首位,用于处理系统表查询- 负载均衡策略:
- RANDOM:随机选择(默认)
- ROUND_ROBIN:轮询
- WEIGHT:加权访问
- 事务查询策略:
- PRIMARY:事务内强制走主库(推荐)
- FIXED:固定走从库
3. 高级特性:注解式主库路由
某些业务场景必须读取最新数据(如支付状态校验),这时需要强制走主库。我们设计了一套优雅的解决方案:
3.1 定义主库路由注解
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface MasterRoute { String value() default ""; }3.2 实现AOP切面
@Aspect @Component @RequiredArgsConstructor public class MasterRouteAspect { private final DataSource dataSource; @Around("@annotation(masterRoute)") public Object around(ProceedingJoinPoint joinPoint, MasterRoute masterRoute) throws Throwable { if (dataSource instanceof ShardingSphereDataSource) { try (HintManager hintManager = HintManager.getInstance()) { hintManager.setWriteRouteOnly(); return joinPoint.proceed(); } } return joinPoint.proceed(); } }3.3 使用示例
@Service public class OrderService { @MasterRoute public Order getLatestOrder(Long orderId) { // 该方法会强制走主库 return orderMapper.selectById(orderId); } }4. 性能优化与监控
实施读写分离后,需要建立完善的监控体系:
关键监控指标:
| 指标项 | 监控方式 | 健康阈值 |
|---|---|---|
| 主从延迟 | SHOW SLAVE STATUS | <500ms |
| 从库负载均衡 | 自定义负载统计 | 偏差<20% |
| SQL执行耗时 | ShardingSphere的sql-show功能 | 慢查询<100ms |
启用SQL日志分析:
@Configuration public class ShardingConfig { @Bean public ShardingSphereDataSourceCustomizer dataSourceCustomizer() { return dataSource -> { Properties props = new Properties(); props.setProperty("sql-show", "true"); props.setProperty("sql-simple", "false"); dataSource.getContextManager().alterProperties(props); }; } }5. 常见问题解决方案
问题1:启动时报Table 'xxx' doesn't exist
- 解决方案:检查
!SINGLE规则是否正确定义,且必须放在rules首位
问题2:事务内读不到刚写入的数据
- 解决方案:配置
transactionalReadQueryStrategy: PRIMARY
问题3:从库负载不均衡
- 优化方案:调整负载策略为WEIGHT并配置权重:
loadBalancers: slave_weight: type: WEIGHT props: slave1: 2 slave2: 1在电商项目实践中,这套方案将数据库查询性能提升了3倍,主库负载下降60%。特别是在秒杀场景下,通过@MasterRoute精确控制关键路径的数据库访问,既保证了数据一致性,又充分利用了从库资源。