一、多数据源场景分类与需求分析
在分布式系统架构中,多数据源配置是解决跨库查询、读写分离、数据分片等场景的核心技术。根据业务需求差异,可划分为两类典型场景:
-
表级跨数据库场景
当多个数据库存在完全相同的表结构时(如表名、字段定义一致),系统需根据业务规则动态选择数据源。典型应用场景包括:- 多租户系统:每个租户独立数据库,但表结构相同
- 地理分区:按地域划分数据库,表结构保持一致
- 灰度发布:新旧版本数据暂存不同数据库
-
非表级跨数据库场景
当各数据源表结构完全不同时,系统需根据操作对象动态路由。常见场景包括:- 微服务架构:不同服务使用独立数据库
- 异构数据整合:关系型数据库与NoSQL混合使用
- 历史数据归档:活跃数据与历史数据分离存储
二、动态数据源核心实现原理
Spring框架通过AbstractRoutingDataSource抽象类实现动态数据源切换,其核心机制包含三个关键组件:
-
数据源路由器(DataSource Router)
继承AbstractRoutingDataSource并重写determineCurrentLookupKey()方法,根据业务上下文返回数据源标识。示例实现:public class DynamicDataSource extends AbstractRoutingDataSource {@Overrideprotected Object determineCurrentLookupKey() {return DataSourceContextHolder.getDataSourceType();}}
-
上下文管理器(Context Holder)
使用ThreadLocal维护当前线程的数据源标识,确保线程安全。示例实现:public class DataSourceContextHolder {private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();public static void setDataSourceType(String dataSourceType) {contextHolder.set(dataSourceType);}public static String getDataSourceType() {return contextHolder.get();}public static void clearDataSourceType() {contextHolder.remove();}}
-
数据源配置中心
通过YAML/Properties文件或配置中心动态管理数据源参数,支持运行时热更新。示例配置:spring:datasource:dynamic:primary: master # 默认数据源datasource:master:url: jdbc
//localhost:3306/db1username: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.Driverslave:url: jdbc
//localhost:3306/db2username: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.Driver
三、完整实现方案详解
1. 依赖引入与基础配置
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency>
2. 数据源初始化逻辑
@Configurationpublic class DynamicDataSourceConfig {@Bean@ConfigurationProperties("spring.datasource.dynamic.datasource.master")public DataSource masterDataSource() {return DataSourceBuilder.create().build();}@Bean@ConfigurationProperties("spring.datasource.dynamic.datasource.slave")public DataSource slaveDataSource() {return DataSourceBuilder.create().build();}@Beanpublic DataSource dynamicDataSource() {Map<Object, Object> targetDataSources = new HashMap<>();targetDataSources.put("master", masterDataSource());targetDataSources.put("slave", slaveDataSource());DynamicDataSource dynamicDataSource = new DynamicDataSource();dynamicDataSource.setTargetDataSources(targetDataSources);dynamicDataSource.setDefaultTargetDataSource(masterDataSource());return dynamicDataSource;}}
3. 动态切换实现方式
方式一:AOP切面实现
@Aspect@Componentpublic class DataSourceAspect {@Before("@annotation(targetDataSource)")public void beforeSwitchDataSource(JoinPoint point, TargetDataSource targetDataSource) {String dsName = targetDataSource.value();DataSourceContextHolder.setDataSourceType(dsName);}@After("@annotation(targetDataSource)")public void afterSwitchDataSource(JoinPoint point, TargetDataSource targetDataSource) {DataSourceContextHolder.clearDataSourceType();}}@Target({ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)public @interface TargetDataSource {String value();}
方式二:MyBatis拦截器实现
@Intercepts({@Signature(type= Executor.class, method="update", args={MappedStatement.class, Object.class}),@Signature(type= Executor.class, method="query", args={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})public class DynamicDataSourceInterceptor implements Interceptor {@Overridepublic Object intercept(Invocation invocation) throws Throwable {MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];String dataSource = getDataSourceFromAnnotation(mappedStatement);if (StringUtils.isNotBlank(dataSource)) {DataSourceContextHolder.setDataSourceType(dataSource);}try {return invocation.proceed();} finally {DataSourceContextHolder.clearDataSourceType();}}private String getDataSourceFromAnnotation(MappedStatement mappedStatement) {// 从SQL注释或XML配置中提取数据源标识return ...;}}
4. 事务管理注意事项
在动态数据源环境下,事务管理需特别注意:
- 事务传播行为:确保事务方法内不发生数据源切换
- 连接池配置:建议使用HikariCP等高性能连接池
- 异常处理:捕获
CannotGetJdbcConnectionException等异常
四、最佳实践与性能优化
-
连接池优化
spring:datasource:hikari:maximum-pool-size: 20minimum-idle: 5connection-timeout: 30000idle-timeout: 600000
-
监控告警集成
通过Prometheus+Grafana监控各数据源连接数、活跃连接数等指标,设置阈值告警。 -
故障转移策略
实现DataSourceHealthIndicator定期检查数据源可用性,自动剔除故障数据源。 -
读写分离优化
结合注解实现自动读写分离:@TargetDataSource("master")public void writeOperation() { ... }@TargetDataSource("slave")public List<Data> readOperation() { ... }
五、常见问题解决方案
-
数据源切换失效
检查是否忘记清除ThreadLocal上下文,或事务方法内发生切换 -
连接泄漏问题
确保所有数据库操作都在try-with-resources块中执行 -
分布式事务挑战
对于跨库事务,建议采用Seata等分布式事务框架 -
动态刷新配置
集成配置中心实现数据源参数动态更新,无需重启应用
通过上述技术方案,开发者可构建高可用、易扩展的动态数据源管理系统。实际项目中,建议结合具体业务场景选择合适的实现方式,并建立完善的监控运维体系确保系统稳定性。