美文网首页
Shardingsphere与dynamic-datasourc

Shardingsphere与dynamic-datasourc

作者: 54番茄 | 来源:发表于2021-08-18 17:07 被阅读0次

        所有的技术架构都是会因为业务场景变动的,逐步发展使用的,你没法去设计一个永久不变的技术架构。因为公司业务数据量的增加,必须对原有的表进行拆分,
        之前记录了一下,Shardingsphere实现读写分离+分库分表,但还是不能满足一些需求,因为要归集以前的旧数据,对数据进行迁移、整理,所以就需要项目同时能访问原来的库以及新的分库分表。本项目是基于springcloud架构搭建的,最简单的方案是搭建两个服务模块,一个对原有数据归集处理,然后一个模块插入到新的分库分表中去,Shardingsphere实现读写分离+分库分表
    模块就是负责新业务数据的插入,以及查询的,但是因为不想新增服务,考虑在这个服务内新增数据源的方式。

    业务场景:
    一个主数据源(原来的旧数据) + 一个分库分表的(master) + 一个分库分表的(slave)

    设计思路就是,配置一个数据源,为主数据源,使用dynamic-datasource来管理数据源的切换,并且把Shardingsphere管理的数据源加入进去。也就是对分表的SQL使用sharding jdb数据源,对不涉及到分表的SQL,使用普通数据源。

    官网dynamic-datasource 说明
    1. 本框架只做 切换数据源 这件核心的事情,并不限制你的具体操作,切换了数据源可以做任何CRUD
    2. 配置文件所有以下划线 _ 分割的数据源 首部 即为组的名称,相同组名称的数据源会放在一个组下。
    3. 切换数据源可以是组名,也可以是具体数据源名称。组名则切换时采用负载均衡算法切换。
    4. 默认的数据源名称为 master ,你可以通过 spring.datasource.dynamic.primary 修改。
    5. 方法上的注解优先于类上注解。
    6. DS支持继承抽象类上的DS,暂不支持继承接口上的DS
      如果需要配置从库,直接在datasource后面继续加就行,默认会使用master数据源,需要使用其他数据源时在service类或方法上加上@DS("数据源名称")注解就行。
      项目结构

    ok 上代码,先添加关键依赖:

    <?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.5.3</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.fanqie</groupId>
        <artifactId>sharding-dynamic-atasource</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>sharding-dynamic-atasource</name>
        <description>test project for Sharding And Dynamic-DataSource</description>
        <properties>
            <spring-cloud.version>Hoxton.SR8</spring-cloud.version>
            <fastjson.version>1.2.44</fastjson.version>
            <!-- 数据库 -->
            <mysql.version>8.0.16</mysql.version>
            <druid.version>1.1.23</druid.version>
            <sharding-sphere.version>4.1.1</sharding-sphere.version>
            <dynamic-datasource.version>3.2.1</dynamic-datasource.version>
        </properties>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-devtools</artifactId>
                <scope>runtime</scope>
                <optional>true</optional>
            </dependency>
            <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>
    
            <!--数据库连接池 Druid引入 -->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>${druid.version}</version>
            </dependency>
    
            <!-- lombok注解,需要安装lombok插件 -->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.16.18</version>
            </dependency>
    
            <!--mybatis-plus依赖-->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>3.4.1</version>
            </dependency>
            <!--数据源驱动-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>${mysql.version}</version>
            </dependency>
    
            <!--依赖dynamic-datasource-->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
                <version>${dynamic-datasource.version}</version>
            </dependency>
            <!--依赖sharding-->
            <dependency>
                <groupId>org.apache.shardingsphere</groupId>
                <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
                <version>${sharding-sphere.version}</version>
            </dependency>
            <dependency>
                <groupId>org.apache.shardingsphere</groupId>
                <artifactId>sharding-core-common</artifactId>
                <version>${sharding-sphere.version}</version>
            </dependency>
    
    
        </dependencies>
    
        <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>
    
            <resources>
                <resource>
                    <!--设置在java目录下的xml文件,能打进入包,也就是mapper.xml-->
                    <directory>src/main/java</directory>
                    <includes>
                        <include>**/*.xml</include>
                    </includes>
                </resource>
                <resource>
                    <!--设置resources中properties、xml、yml 能打进入包-->
                    <directory>src/main/resources</directory>
                    <includes>
                        <include>**/*.yml</include>
                        <include>**/*.xml</include>
                    </includes>
                </resource>
            </resources>
        </build>
    
    </project>
    
    

    注意:
    最好是按照以上版本去使用,因为我发现,3.1.1版本有个bug, 不会进入loadDataSources方法,这样就一直造成数据源注册失败,暂时没去追踪源码是什么原因造成的,换了版本就可以了。

    yml配置类

    server:
      port: 8900
    spring:
      main:
        allow-bean-definition-overriding: true
      datasource:
        dynamic:
          strict: true #设置严格模式,默认false不启动. 启动后在未匹配到指定数据源时候回抛出异常,不启动会使用默认数据源.
          datasource:
            master:
              driverClassName: com.mysql.cj.jdbc.Driver
              type: com.alibaba.druid.pool.DruidDataSource
              url: jdbc:mysql://192.168.200.36:3306/seal_ma?useUnicode=true&allowMultiQuerie=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false
              username: root
              password: 123456
      #shardingjdbc默认接管了所有的数据源,如果我们有多个非分表的库时,则最多只能设置一个为默认数据库,其他的非分表数据库不能访问
      shardingsphere:
        # 参数配置,显示sql
        props:
          sql.show: true
        # 配置数据源
        datasource:
          # 给每个数据源取别名,record*
          names: record1,record2
          # 给master-record1每个数据源配置数据库连接信息
          record1:
            # 配置druid数据源
            type: com.alibaba.druid.pool.DruidDataSource
            driver-class-name: com.mysql.cj.jdbc.Driver
            url: jdbc:mysql://192.168.200.5:3306/seal_sign_record?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false
            username: root
            password: 123456
            maxPoolSize: 100
            minPoolSize: 5
          # 配置record2-slave
          record2:
            type: com.alibaba.druid.pool.DruidDataSource
            driver-class-name: com.mysql.cj.jdbc.Driver
            url: jdbc:mysql://192.168.200.20:3306/seal_sign_record?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false
            username: root
            password: 123456
            maxPoolSize: 100
            minPoolSize: 5
        # 配置默认数据源ds1
        sharding:
          #       配置数据源的读写分离,但是数据库一定要做主从复制
          master-slave-rules:
            # 配置主从名称,可以任意取名字
            ms:
              # 配置主库master,负责数据的写入
              master-data-source-name: record1
              # 配置从库slave节点
              slave-data-source-names: record2
              # 配置slave节点的负载均衡均衡策略,采用轮询机制
              load-balance-algorithm-type: round_robin
          # 默认数据源,主要用于写,注意一定要配置读写分离 ,注意:如果不配置,那么就会把三个节点都当做从slave节点,新增,修改和删除会出错。
          default-data-source-name: ms
          #       配置分表的规则
          tables:
            ps_seal_log:
              actual-data-nodes: ms.ps_seal_log_$->{2020..2021}${(1..12).collect{t ->t.toString().padLeft(2,'0')} }
              table-strategy:
                standard:
                  shardingColumn: action_time
                  preciseAlgorithmClassName: com.fanqie.sd.config.DatePreciseShardingAlgorithm
    
    
    # 整合mybatis的配置XXXXX
    mybatis-plus:
      mapper-locations: classpath*:**/dao/*.xml
      type-aliases-package: com.fanqie.sd.entity
    

    Shardeing的分库分表策略类

    package com.fanqie.sd.config;
    
    import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;
    import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import java.text.NumberFormat;
    import java.util.Calendar;
    import java.util.Collection;
    import java.util.Date;
    
    /**
     * @author fanqie
     * @ClassName  shaerding  分库分表策略配置:按照年月 如ps_seal_log202108
     * @date 2021/7/28 下午4:40
     **/
    
    public class DatePreciseShardingAlgorithm implements PreciseShardingAlgorithm<Date>  {
    
    
        private static final Logger logger = LoggerFactory.getLogger(DatePreciseShardingAlgorithm.class);
        @Override
        public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Date> preciseShardingValue) {
            Date date = preciseShardingValue.getValue();
            logger.info("Sharding input:" + preciseShardingValue.getValue());
            String suffix = getSuffixByYearMonth(date);
            for (String tableName : availableTargetNames) {
                logger.info("suffix:" + suffix + ", 表明:{}" + tableName);
                if (tableName.endsWith(suffix)) {
                    return tableName;
                }
            }
            throw new IllegalArgumentException("未找到匹配的数据表");
    
        }
    
        private static String getSuffixByYearMonth(Date date) {
            NumberFormat nf = NumberFormat.getInstance();
            nf.setMinimumIntegerDigits(2);
            Calendar calendar = Calendar.getInstance();
            calendar.setTime(date);
            return calendar.get(Calendar.YEAR)  +"" +  nf.format((calendar.get(Calendar.MONTH) + 1));
        }
    }
    

    Dynamic-datasource的动态数据源配置类

    package com.fanqie.sd.config;
    
    import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
    import com.baomidou.dynamic.datasource.provider.AbstractDataSourceProvider;
    import com.baomidou.dynamic.datasource.provider.DynamicDataSourceProvider;
    import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
    import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceAutoConfiguration;
    import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.SpringBootConfiguration;
    import org.springframework.boot.autoconfigure.AutoConfigureBefore;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Lazy;
    import org.springframework.context.annotation.Primary;
    
    import javax.annotation.Resource;
    import javax.sql.DataSource;
    import java.util.Map;
    
    /**
     * 动态数据源配置:
     *
     * 使用{@link com.baomidou.dynamic.datasource.annotation.DS}注解,切换数据源
     *
     * <code>@DS(DataSourceConfiguration.SHARDING_DATA_SOURCE_NAME)</code>
     *
     * @author fanqie
     * @date 2021/8/16 下午12:36
     */
    @Configuration
    @AutoConfigureBefore({DynamicDataSourceAutoConfiguration.class, SpringBootConfiguration.class})
    public class DataSourceConfiguration {
        /**
         * 分表数据源名称
         */
        public static final String SHARDING_DATA_SOURCE_NAME = "sharding";
        /**
         * 动态数据源配置项
         */
        @Autowired
        private DynamicDataSourceProperties dynamicDataSourceProperties;
    
        /**
         * shardingjdbc有四种数据源,需要根据业务注入不同的数据源
         *
         * <p>1. 未使用分片, 脱敏的名称(默认): shardingDataSource;
         * <p>2. 主从数据源: masterSlaveDataSource;
         * <p>3. 脱敏数据源:encryptDataSource;
         * <p>4. 影子数据源:shadowDataSource
         *
         * shardingjdbc默认就是shardingDataSource
         *  如果需要设置其他的可以使用
         * @Resource(value="") 设置
         */
        @Lazy
        @Resource
        DataSource shardingDataSource;
    
    /**
         * 将shardingDataSource放到了多数据源(dataSourceMap)中
         * 注意有个版本的bug,3.1.1版本 不会进入loadDataSources 方法,这样就一直造成数据源注册失败
         */
        @Bean
        public DynamicDataSourceProvider dynamicDataSourceProvider() {
            Map<String, DataSourceProperty> datasourceMap = dynamicDataSourceProperties.getDatasource();
            return new AbstractDataSourceProvider() {
                @Override
                public Map<String, DataSource> loadDataSources() {
                    Map<String, DataSource> dataSourceMap = createDataSourceMap(datasourceMap);
                    // 将 shardingjdbc 管理的数据源也交给动态数据源管理
                    dataSourceMap.put(SHARDING_DATA_SOURCE_NAME, shardingDataSource);
                    return dataSourceMap;
                }
            };
        }
    
        /**
         * 将动态数据源设置为首选的
         * 当spring存在多个数据源时, 自动注入的是首选的对象
         * 设置为主要的数据源之后,就可以支持shardingjdbc原生的配置方式了
         *
         * @return
         */
        @Primary
        @Bean
        public DataSource dataSource(DynamicDataSourceProvider dynamicDataSourceProvider) {
            DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource();
            dataSource.setPrimary(dynamicDataSourceProperties.getPrimary());
            dataSource.setStrict(dynamicDataSourceProperties.getStrict());
            dataSource.setStrategy(dynamicDataSourceProperties.getStrategy());
            dataSource.setProvider(dynamicDataSourceProvider);
            dataSource.setP6spy(dynamicDataSourceProperties.getP6spy());
            dataSource.setSeata(dynamicDataSourceProperties.getSeata());
            return dataSource;
        }
    }
    **说明:**
    
    

    访问主数据源的Service实现类

    因为在yml中配置为默认数据源,所以可以忽略@DS注解

    package com.fanqie.sd.service.impl;
    
    import com.fanqie.sd.dao.dynamic.PsSealMapper;
    import com.fanqie.sd.entity.PsSeal;
    import com.fanqie.sd.service.PsSealService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    /**
     * @author fanqie
     * @ClassName PsSealServiceImpl
     * @date 2021/8/18 下午3:10
     **/
    @Service
    public class PsSealServiceImpl implements PsSealService {
    
        @Autowired
        private PsSealMapper sealMapper;
    
    
        @Override
        public PsSeal getSealWithValidDept(Long sealId) {
            return sealMapper.getSealWithValidDept(sealId);
        }
    }
    

    访问SharedingService实现类

    需要增加数据源的别名,@DS("sharding"),这个名字是在DynamicDataSourceProperties中设置的,你可以任意取,但是必须是你注册的。

    package com.fanqie.sd.service.impl;
    
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.fanqie.sd.dao.shareding.SealLogMapper;
    import com.fanqie.sd.entity.SealLog;
    import com.fanqie.sd.service.SealLogService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    /**
     * @author fanqie
     * @ClassName SealLogServiceImpl
     * @date 2021/8/18 下午3:11
     **/
    @Service
    @DS("sharding")
    public class SealLogServiceImpl implements SealLogService {
    
        @Autowired
        SealLogMapper sealLogMapper;
    
        @Override
        public int insert(SealLog record) {
            return sealLogMapper.insert(record);
        }
    
        @Override
        public SealLog selectByPrimaryKey(long id) {
            return sealLogMapper.selectByPrimaryKey(id);
        }
    }
    

    测试类

    package com.fanqie.sd;
    
    import com.fanqie.sd.entity.PsSeal;
    import com.fanqie.sd.entity.SealLog;
    import com.fanqie.sd.service.PsSealService;
    import com.fanqie.sd.service.SealLogService;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    
    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.List;
    
    @SpringBootTest
    class ShardingDynamicAtasourceApplicationTests {
    
    
        /**
         *
         * 从主数据源中读取数据,也就是原项目中的大表
         */
        @Autowired
        PsSealService psSealService;
    
        @Test
        void selectPsSealByEsId() {
            PsSeal seal= psSealService.getSealWithValidDept(2278L);
            System.out.println(seal.toString());
        }
    
    
    
    
        /**
         *
         * 从shareding 配置数据源中读写数据,并且shareding做了读写分离 + 分库分表
         */
        @Autowired
        SealLogService sealLogService;
    
        @Test
        void insert() throws ParseException {
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String dateStr = "2021-08-03 10:59:27";
            Date date = simpleDateFormat.parse(dateStr);
    
    
            SealLog psSealLog = new SealLog();
            psSealLog.setActionTime(date);
            psSealLog.setBusinessType(1);
            psSealLog.setSealId(9L);
            psSealLog.setEsId("11010100007955");
            psSealLog.setSealDeptId(2L);
            psSealLog.setSealName("1027测试");
            int flag =  sealLogService.insert(psSealLog);
            System.out.println("flag=" + flag);
    
            SealLog psSealLog2 = new SealLog();
            psSealLog2.setActionTime(new Date());
            psSealLog2.setBusinessType(3);
            psSealLog2.setSealId(10L);
            psSealLog2.setEsId("44030800000093");
            psSealLog2.setSealDeptId(10L);
            psSealLog2.setSealName("测试");
            int flag2 =  sealLogService.insert(psSealLog2);
            System.out.println("flag2=" + flag2);
    
    
    
            SealLog psSealLog3 = new SealLog();
            psSealLog3.setActionTime(new Date());
            psSealLog3.setBusinessType(2);
            psSealLog3.setSealId(11L);
            psSealLog3.setEsId("13030600000131");
            psSealLog3.setSealDeptId(11L);
            psSealLog3.setSealName("恒大");
            int flag3 =  sealLogService.insert(psSealLog3);
            System.out.println("flag3=" + flag3);
    
    
            String dateStr2 = "2021-09-03 10:59:27";
            Date date2 = simpleDateFormat.parse(dateStr2);
    
            SealLog psSealLog4 = new SealLog();
            psSealLog4.setActionTime(date2);
            psSealLog4.setBusinessType(2);
            psSealLog4.setSealId(12L);
            psSealLog4.setEsId("51030300002297");
            psSealLog4.setSealDeptId(12L);
            psSealLog4.setSealName("深圳");
            int flag4 =  sealLogService.insert(psSealLog4);
    
            System.out.println("flag4=" + flag4);
        }
    
    
        @Test
        void search() throws ParseException {
            SealLog sealLog =  sealLogService.selectByPrimaryKey(10L);
            System.out.println("flag5=" + sealLog);
        }
    }
    
    

    相关文章

      网友评论

          本文标题:Shardingsphere与dynamic-datasourc

          本文链接:https://www.haomeiwen.com/subject/tezwbltx.html