美文网首页
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