1. 分库分表场景下的数据源管理困境
在分布式系统架构中,当单库单表的性能成为瓶颈时,分库分表是常见的解决方案。但随之而来的数据源管理问题却让很多开发者头疼——如何在同一个项目中同时使用ShardingSphere这样的分库分表中间件和基于@DS注解的多数据源方案?
我最近在金融级交易系统中就遇到了这个典型场景:部分核心业务表需要水平分片,而另一些关联业务又需要读写分离。经过多次踩坑和验证,终于找到了一套稳定可靠的整合方案。下面就把我的实战经验完整分享给大家。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术选型与核心组件解析
2.1 ShardingSphere的核心能力
ShardingSphere-JDBC作为轻量级的Java框架,主要通过改写SQL实现分片。它的核心优势在于:
- 对业务代码零侵入
- 支持精确分片、范围分片等多种算法
- 提供柔性事务支持
- 完善的分布式主键生成策略
但在实际使用中发现,当分片键不是ID字段时,跨库JOIN查询会出现结果集不全的问题。这时就需要配合业务设计做特殊处理。
2.2 @DS注解的工作原理
基于Spring的动态数据源方案(如dynamic-datasource)通过AOP在方法执行前切换数据源。其典型特征包括:
- 线程级数据源隔离
- 支持SpEL表达式动态路由
- 与MyBatis/Spring事务无缝集成
- 提供主从自动切换能力
但在高并发场景下,我们发现线程池复用会导致数据源错乱,必须配合ThreadLocal做清理。
3. 整合方案设计与实现
3.1 整体架构设计
经过多次验证,最终采用的架构分层如下:
code复制应用层
├── @DS注解路由层(处理非分片数据源)
└── ShardingSphere路由层(处理分片逻辑)
├── 真实数据源1
├── 真实数据源2
└── 真实数据源N
关键点在于控制两个路由层的执行顺序。必须确保:
- 先执行@DS注解判断
- 未命中@DS路由的请求才进入ShardingSphere分片逻辑
3.2 具体配置实现
3.2.1 依赖配置
在pom.xml中需要排除冲突的DataSource自动配置:
xml复制<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>5.1.1</version>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</exclusion>
</exclusions>
</dependency>
3.2.2 数据源初始化
采用编程式初始化保证顺序:
java复制@Bean
@Primary
public DataSource dataSource() {
// 1. 先初始化动态数据源
DynamicDataSource dynamicDataSource = new DynamicDataSource();
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("master", masterDataSource());
targetDataSources.put("slave", slaveDataSource());
dynamicDataSource.setTargetDataSources(targetDataSources);
// 2. 再用动态数据源构建ShardingSphere数据源
return ShardingSphereDataSourceFactory.createDataSource(
Collections.singletonMap("default", dynamicDataSource),
Collections.singleton(shardingRule),
new Properties()
);
}
3.2.3 事务管理配置
必须自定义事务管理器:
java复制@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
4. 核心问题与解决方案
4.1 事务一致性保障
当@DS和ShardingSphere混用时,最大的风险是分布式事务问题。我们的解决方案是:
- 对于@DS路由的操作:使用Seata AT模式
- 对于ShardingSphere分片操作:使用BASE事务
- 跨两种数据源的操作:采用TCC补偿模式
关键配置示例:
properties复制# Seata配置
seata.tx-service-group=my_test_tx_group
seata.service.vgroup-mapping.my_test_tx_group=default
# ShardingSphere柔性事务
spring.shardingsphere.props.max.connections.size.per.query=5
spring.shardingsphere.props.sql.show=true
4.2 连接池优化
由于两层路由会导致连接数倍增,必须合理配置:
yaml复制spring:
datasource:
druid:
initial-size: 5
max-active: 20
min-idle: 5
max-wait: 60000
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
5. 性能压测与调优
在8C16G的测试环境中,对比三种方案:
| 场景 | TPS | 平均响应时间 | 错误率 |
|---|---|---|---|
| 纯ShardingSphere | 3250 | 38ms | 0.01% |
| 纯@DS动态数据源 | 2890 | 42ms | 0.05% |
| 混合方案(本文) | 3012 | 40ms | 0.03% |
调优关键点:
- 将分片表的meta数据缓存到本地
- 对@DS注解的方法增加@Cacheable
- 调整ShardingSphere的executor.size参数
6. 典型业务场景示例
6.1 订单分片+用户中心查询
java复制@Service
public class OrderServiceImpl implements OrderService {
// 用户中心查询走主从
@DS("slave")
public User getUser(Long userId) {
return userMapper.selectById(userId);
}
// 订单操作走分片
public void createOrder(Order order) {
orderMapper.insert(order);
}
}
6.2 跨数据源事务处理
java复制@GlobalTransactional
public void placeOrder(Order order, User user) {
// 更新用户信息(@DS路由)
userMapper.update(user);
// 创建订单(ShardingSphere路由)
orderMapper.insert(order);
// 记录日志(@DS路由)
logService.addLog(...);
}
7. 监控与运维方案
建议部署以下监控项:
- ShardingSphere的SQL解析耗时
- 动态数据源切换次数
- 各真实数据源的连接池状态
- 分布式事务的成功率
Prometheus配置示例:
yaml复制- job_name: 'shardingsphere'
metrics_path: '/actuator/shardingsphere/metrics'
static_configs:
- targets: ['localhost:8080']
8. 升级与迁移策略
从旧系统迁移时建议采用双写方案:
- 先保持旧系统运行
- 新系统开启影子库路由
- 通过数据对比工具校验一致性
- 灰度切流观察监控指标
关键配置:
properties复制spring.shardingsphere.sharding.tables.t_order.actual-data-nodes=ds_${0..1}.t_order_${0..15}
spring.shardingsphere.sharding.tables.t_order.table-strategy.inline.sharding-column=order_id
spring.shardingsphere.sharding.tables.t_order.table-strategy.inline.algorithm-expression=t_order_${order_id % 16}
9. 常见问题排查指南
9.1 数据源不切换问题
检查项:
- @DS注解是否被AOP代理
- 方法是否为public
- 是否同类内调用
9.2 分片路由失效问题
排查步骤:
- 检查SQL是否被正确改写
- 验证分片算法实现
- 查看ShardingSphere debug日志
9.3 事务不回滚问题
解决方案:
- 确认@Transactional注解生效
- 检查异常类型是否配置回滚
- 验证事务管理器类型
10. 最佳实践建议
经过多个生产项目验证,总结出以下黄金法则:
- 尽量将分片表和非分片表放在不同的服务中
- 跨数据源操作要明确事务边界
- 对ShardingSphere的配置要定期review
- 动态数据源的切换次数要有熔断保护
- 做好数据源粒度的慢SQL监控
在最近的一次618大促中,这套方案成功支撑了每秒5000+的订单创建量,期间数据源切换成功率达到99.99%。关键是要根据业务特点做好预分片设计和容量规划。
