多数据源与分库分表方案设计

在Spring Cloud微服务架构中实现多数据源和分库分表,可以有效解决数据库性能瓶颈和数据量增长问题。以下是完整的实现方案。

实现思路

  1. 多数据源设计

    • 基于Spring的AbstractRoutingDataSource实现动态数据源路由
    • 使用ThreadLocal存储当前线程的数据源标识
    • 通过AOP+注解实现数据源的灵活切换
  2. 分库分表设计

    • 集成ShardingSphere作为分库分表中间件
    • 采用用户ID哈希作为分片策略
    • 实现读写分离,主库写入,从库读取
  3. 技术栈

    • 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);
    }
}

方案说明

  1. 多数据源实现

    • 通过自定义DynamicRoutingDataSource继承AbstractRoutingDataSource实现数据源动态路由
    • 使用ThreadLocal存储当前线程的数据源标识
    • 结合AOP和@DataSource注解实现方法级别的数据源切换
  2. 分库分表实现

    • 集成ShardingSphere-JDBC实现分库分表功能
    • 采用用户ID哈希取模的方式进行分片:
      • 分库:user_id % 2 → 分到2个库
      • 分表:user_id % 4 → 每个库分4张表
    • 实现读写分离,主库写入,从库读取
  3. 扩展性考虑

    • 可根据业务需求调整分片策略
    • 支持动态增加数据源节点
    • 可扩展实现更复杂的分片算法(如按时间范围分片)

该方案适用于数据量较大、需要水平扩展的微服务系统,能够有效提升系统的性能和可用性。

相关推荐
AlexZhao189几秒前
.NET中联合类型的实现(OneOf框架核心机理讲解)
后端·.net
努力的小郑19 分钟前
别再说你会 new Object() 了!JVM 类加载的真相,绝对和你想的不一样
java·jvm·面试
cxyxiaokui00123 分钟前
论如何优雅地让AI“闭嘴”:深入SpringAI的流式停止与记忆难题
java·后端
bobz96527 分钟前
关于 “涌现” 的最初的定义
后端
Warren9833 分钟前
Spring Boot 整合网易163邮箱发送邮件实现找回密码功能
数据库·vue.js·spring boot·redis·后端·python·spring
Aphasia31134 分钟前
react常用hook
前端·react.js·面试
秦禹辰1 小时前
本地Docker部署开源Web相册图库Piwigo与在线远程访问实战方案
开发语言·后端·golang
一乐小哥1 小时前
五分钟就能搭好的socks5为啥我装了一个小时😭 进来看小丑
linux·后端
HyggeBest1 小时前
Golang 并发原语 Sync Pool
后端·go