SpringBoot项目里,如何让ShardingSphere 5.x和dynamic-datasource和平共处?一个配置类搞定混合数据源
SpringBoot项目中ShardingSphere 5.x与dynamic-datasource的混合数据源架构实践
在电商系统这类高并发场景下,订单表通常需要分库分表来应对海量数据,而商品配置、用户签约等业务表则更适合单库存储。这种混合架构带来了技术挑战:如何在同一个SpringBoot项目中同时使用ShardingSphere的分片能力和dynamic-datasource的多数据源路由?
1. 混合数据源架构的核心挑战
当订单表需要水平分片而商品表需要独立存储时,传统方案往往面临两难选择。ShardingSphere擅长分库分表但对非分片数据源支持有限,dynamic-datasource精于多数据源路由却无法处理分片逻辑。
典型痛点包括:
- 分片数据源无法参与多数据源路由
- 事务跨越分片库和独立库时的一致性难题
- 两种数据源框架的配置属性冲突
- 监控指标难以统一收集
关键突破点在于将ShardingSphere管理的分片数据源作为一个逻辑数据源注册到dynamic-datasource体系中。这种架构下,应用层通过统一的DS注解即可透明访问各类数据源。
2. 依赖配置与初始化陷阱
正确的依赖版本组合是成功的第一步。以下是经生产验证的依赖配置:
<!-- 数据源核心 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.8</version> </dependency> <!-- ShardingSphere --> <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId> <version>5.3.2</version> </dependency> <!-- 动态数据源 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <version>3.6.1</version> </dependency> <!-- MyBatis增强 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3.1</version> </dependency>注意:ShardingSphere 5.x版本必须配合dynamic-datasource 3.5+使用,低版本会导致自动配置冲突。
YAML配置需要特别注意属性隔离。示例配置片段:
spring: shardingsphere: datasource: names: db0,db1 db0: url: jdbc:mysql://db-host:3306/db0 type: com.alibaba.druid.pool.DruidDataSource db1: url: jdbc:mysql://db-host:3306/db1 type: com.alibaba.druid.pool.DruidDataSource rules: sharding: tables: orders: actual-data-nodes: db0.orders_$->{0..2} datasource: dynamic: primary: master datasource: master: url: jdbc:mysql://db-host:3306/master config: url: jdbc:mysql://db-host:3306/config_db3. 核心配置类实现
真正的魔法发生在自定义配置类中。以下是一个经过生产验证的增强版配置实现:
@Configuration @AutoConfigureBefore({DynamicDataSourceAutoConfiguration.class}) public class HybridDataSourceConfig { @Autowired private DynamicDataSourceProperties properties; @Lazy // 必须延迟加载 @Autowired(required = false) private DataSource shardingSphereDataSource; @Bean public DynamicDataSourceProvider dynamicDataSourceProvider() { return new AbstractDataSourceProvider() { @Override public Map<String, DataSource> loadDataSources() { Map<String, DataSource> dataSourceMap = createDataSourceMap( properties.getDatasource()); if (shardingSphereDataSource != null) { dataSourceMap.put("sharding", shardingSphereDataSource); } return dataSourceMap; } }; } @Primary @Bean public DataSource dataSource(DynamicDataSourceProvider provider) { DynamicRoutingDataSource ds = new DynamicRoutingDataSource(); ds.setPrimary(properties.getPrimary()); ds.setStrict(properties.getStrict()); ds.setProvider(provider); return enhanceDataSource(ds); } private DataSource enhanceDataSource(DataSource dataSource) { // 添加监控埋点等增强逻辑 return dataSource; } }关键实现要点:
@AutoConfigureBefore确保在动态数据源自动配置前执行@Lazy解决ShardingSphere数据源初始化时机问题- 空检查避免未启用ShardingSphere时报错
- 增强方法预留监控扩展点
4. 业务层使用规范
在Mapper层通过DS注解指定数据源时,推荐采用以下策略:
// 分片数据源访问 @DS("sharding") public interface OrderMapper { @Select("SELECT * FROM orders WHERE order_no = #{orderNo}") Order findByOrderNo(@Param("orderNo") String orderNo); } // 独立数据源访问 @DS("config") public interface ProductConfigMapper { @Update("UPDATE product SET price = #{price} WHERE id = #{id}") int updatePrice(@Param("id") Long id, @Param("price") BigDecimal price); } // 动态切换示例 public class OrderService { @DS("sharding") public Order getOrder(String orderNo) { // 访问分片库 } @DS("config") public void updateProductConfig(Product product) { // 访问独立配置库 } }对于需要跨数据源事务的场景,建议:
- 使用Seata分布式事务
- 或将本地事务拆分为最终一致性模式
- 避免在同一个方法内混用不同DS注解
5. 生产环境优化策略
经过多个线上项目验证,以下优化措施能显著提升稳定性:
连接池配置优化
| 参数 | 分片数据源建议值 | 独立数据源建议值 |
|---|---|---|
| initialSize | 10 | 5 |
| maxActive | 50 | 20 |
| minIdle | 10 | 5 |
| maxWait | 3000ms | 1000ms |
| timeBetweenEviction | 60000ms | 30000ms |
监控集成方案
public class DataSourceMonitor implements DataSourceCreator { @Override public DataSource createDataSource(DataSourceProperty property) { DruidDataSource ds = property.createDruidDataSource(); // 添加Prometheus监控 DruidStatManager.addDataSource(ds, property.getPollName()); return ds; } }常见故障排查指南
- 连接泄漏:启用druid的removeAbandoned配置
- 分片不生效:检查SQL是否包含分片键
- 路由错误:确认DS注解值是否与注册名称一致
- 事务失效:确保没有绕过Spring代理
6. 架构演进建议
当系统发展到一定规模后,可以考虑:
- 将配置类数据迁移到配置中心
- 使用ShardingSphere-Proxy替代嵌入式分片
- 引入数据同步工具保证跨数据源数据一致性
- 对高频访问的非分片数据实施缓存策略
这套混合架构已在多个日订单百万级的电商系统中验证,既能享受分库分表的扩展性,又能保持简单业务的开发效率。关键在于合理划分数据边界,避免过度设计。
