在Spring Cloud微服务架构中实现多数据源和分库分表,可以有效解决数据库性能瓶颈和数据量增长问题。以下是完整的实现方案。
实现思路
-
多数据源设计:
- 基于Spring的AbstractRoutingDataSource实现动态数据源路由
- 使用ThreadLocal存储当前线程的数据源标识
- 通过AOP+注解实现数据源的灵活切换
-
分库分表设计:
- 集成ShardingSphere作为分库分表中间件
- 采用用户ID哈希作为分片策略
- 实现读写分离,主库写入,从库读取
-
技术栈:
- Spring Cloud/Spring Boot
- MyBatis-Plus(ORM框架)
- ShardingSphere-JDBC(分库分表)
- Druid(连接池)
- MySQL(数据库)
代码实现
首先,添加核心依赖:
python
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.5</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>multi-datasource-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<java.version>11</java.version>
<spring-cloud.version>2021.0.5</spring-cloud.version>
<shardingsphere.version>5.1.1</shardingsphere.version>
</properties>
<dependencies>
<!-- Spring Cloud -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
</dependency>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- MyBatis-Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<!-- ShardingSphere -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
<version>${shardingsphere.version}</version>
</dependency>
<!-- 数据库 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- 连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.15</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>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
多数据源核心配置:
python
package com.example.multidatasource.config;
import lombok.extern.slf4j.Slf4j;
/**
* 数据源上下文持有者,用于管理当前线程使用的数据源
*/
@Slf4j
public class DynamicDataSourceContextHolder {
/**
* 线程本地环境
*/
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
/**
* 设置数据源
*/
public static void setDataSourceType(String dataSourceType) {
log.info("切换到数据源: {}", dataSourceType);
CONTEXT_HOLDER.set(dataSourceType);
}
/**
* 获取数据源
*/
public static String getDataSourceType() {
return CONTEXT_HOLDER.get();
}
/**
* 清除数据源
*/
public static void clearDataSourceType() {
CONTEXT_HOLDER.remove();
}
}
分库分表配置:
python
package com.example.multidatasource.config;
import org.apache.shardingsphere.api.config.sharding.ShardingRuleConfiguration;
import org.apache.shardingsphere.api.config.sharding.TableRuleConfiguration;
import org.apache.shardingsphere.api.config.sharding.strategy.InlineShardingStrategyConfiguration;
import org.apache.shardingsphere.api.config.sharding.strategy.StandardShardingStrategyConfiguration;
import org.apache.shardingsphere.driver.api.ShardingSphereDataSourceFactory;
import org.apache.shardingsphere.infra.config.algorithm.AlgorithmConfiguration;
import org.apache.shardingsphere.sharding.api.sharding.standard.PreciseShardingValue;
import org.apache.shardingsphere.sharding.api.sharding.standard.RangeShardingValue;
import org.apache.shardingsphere.sharding.api.sharding.standard.StandardShardingAlgorithm;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/**
* ShardingSphere分库分表配置
*/
@Configuration
public class ShardingSphereConfig {
/**
* 配置分库分表规则
*/
@Bean
public DataSource dataSource() throws SQLException {
// 配置真实数据源
Map<String, DataSource> dataSources = new HashMap<>();
// 主库数据源
dataSources.put("master_db0", createDataSource("master_db0"));
dataSources.put("master_db1", createDataSource("master_db1"));
// 从库数据源
dataSources.put("slave_db0", createDataSource("slave_db0"));
dataSources.put("slave_db1", createDataSource("slave_db1"));
// 配置user表规则
TableRuleConfiguration userTableRuleConfig = new TableRuleConfiguration("t_user", "master_db${0..1}.t_user${0..3},slave_db${0..1}.t_user${0..3}");
// 配置分库策略
userTableRuleConfig.setDatabaseShardingStrategyConfig(
new StandardShardingStrategyConfiguration("user_id", new DatabaseShardingAlgorithm()));
// 配置分表策略
userTableRuleConfig.setTableShardingStrategyConfig(
new StandardShardingStrategyConfiguration("user_id", new TableShardingAlgorithm()));
// 配置读写分离
userTableRuleConfig.setDatabaseShardingStrategyConfig(
new InlineShardingStrategyConfiguration("user_id", "master_db${user_id % 2}"));
// 主从分离配置
Properties masterSlaveProps = new Properties();
masterSlaveProps.setProperty("sql.show", "true");
// 配置分片规则
ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
shardingRuleConfig.getTables().add(userTableRuleConfig);
// 创建数据源
return ShardingSphereDataSourceFactory.createDataSource(dataSources, shardingRuleConfig, new Properties());
}
/**
* 创建数据源
*/
private DataSource createDataSource(String dataSourceName) {
com.alibaba.druid.pool.DruidDataSource dataSource = new com.alibaba.druid.pool.DruidDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl(String.format("jdbc:mysql://localhost:3306/%s?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8", dataSourceName));
dataSource.setUsername("root");
dataSource.setPassword("root");
dataSource.setInitialSize(5);
dataSource.setMaxActive(20);
return dataSource;
}
/**
* 事务管理器
*/
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
/**
* 数据库分片算法
*/
public static class DatabaseShardingAlgorithm implements StandardShardingAlgorithm<Long> {
@Override
public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Long> shardingValue) {
// 按用户ID取模2,分配到不同的库
long userId = shardingValue.getValue();
String suffix = String.valueOf(userId % 2);
for (String dbName : availableTargetNames) {
if (dbName.endsWith(suffix)) {
return dbName;
}
}
throw new IllegalArgumentException("未找到匹配的数据库: " + availableTargetNames + ", shardingValue: " + shardingValue);
}
@Override
public Collection<String> doSharding(Collection<String> availableTargetNames, RangeShardingValue<Long> shardingValue) {
// 范围查询时的处理
return availableTargetNames;
}
@Override
public void init(Properties props) {
// 初始化方法
}
@Override
public String getType() {
return "DATABASE_SHARDING";
}
}
/**
* 表分片算法
*/
public static class TableShardingAlgorithm implements StandardShardingAlgorithm<Long> {
@Override
public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Long> shardingValue) {
// 按用户ID取模4,分配到不同的表
long userId = shardingValue.getValue();
String suffix = String.valueOf(userId % 4);
for (String tableName : availableTargetNames) {
if (tableName.endsWith(suffix)) {
return tableName;
}
}
throw new IllegalArgumentException("未找到匹配的表: " + availableTargetNames + ", shardingValue: " + shardingValue);
}
@Override
public Collection<String> doSharding(Collection<String> availableTargetNames, RangeShardingValue<Long> shardingValue) {
// 范围查询时的处理
return availableTargetNames;
}
@Override
public void init(Properties props) {
// 初始化方法
}
@Override
public String getType() {
return "TABLE_SHARDING";
}
}
}
业务代码示例:
python
package com.example.multidatasource.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* 用户实体类
*/
@Data
@TableName("t_user")
public class User {
@TableId(type = IdType.AUTO)
private Long userId;
private String username;
private String password;
private Integer age;
private String email;
}
应用启动类:
python
package com.example.multidatasource;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
/**
* 应用启动类
* 排除默认的数据源自动配置,使用我们自定义的
*/
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@MapperScan("com.example.multidatasource.mapper")
public class MultiDataSourceApplication {
public static void main(String[] args) {
SpringApplication.run(MultiDataSourceApplication.class, args);
}
}
方案说明
-
多数据源实现:
- 通过自定义DynamicRoutingDataSource继承AbstractRoutingDataSource实现数据源动态路由
- 使用ThreadLocal存储当前线程的数据源标识
- 结合AOP和@DataSource注解实现方法级别的数据源切换
-
分库分表实现:
- 集成ShardingSphere-JDBC实现分库分表功能
- 采用用户ID哈希取模的方式进行分片:
- 分库:user_id % 2 → 分到2个库
- 分表:user_id % 4 → 每个库分4张表
- 实现读写分离,主库写入,从库读取
-
扩展性考虑:
- 可根据业务需求调整分片策略
- 支持动态增加数据源节点
- 可扩展实现更复杂的分片算法(如按时间范围分片)
该方案适用于数据量较大、需要水平扩展的微服务系统,能够有效提升系统的性能和可用性。