所有的技术架构都是会因为业务场景变动的,逐步发展使用的,你没法去设计一个永久不变的技术架构。因为公司业务数据量的增加,必须对原有的表进行拆分,
之前记录了一下,Shardingsphere实现读写分离+分库分表,但还是不能满足一些需求,因为要归集以前的旧数据,对数据进行迁移、整理,所以就需要项目同时能访问原来的库以及新的分库分表。本项目是基于springcloud架构搭建的,最简单的方案是搭建两个服务模块,一个对原有数据归集处理,然后一个模块插入到新的分库分表中去,Shardingsphere实现读写分离+分库分表
模块就是负责新业务数据的插入,以及查询的,但是因为不想新增服务,考虑在这个服务内新增数据源的方式。
业务场景:
一个主数据源(原来的旧数据) + 一个分库分表的(master
) + 一个分库分表的(slave
)
设计思路就是,配置一个数据源,为主数据源,使用
dynamic-datasource
来管理数据源的切换,并且把Shardingsphere
管理的数据源加入进去。也就是对分表的SQL
使用sharding jdb
数据源,对不涉及到分表的SQL
,使用普通数据源。
官网dynamic-datasource 说明
- 本框架只做 切换数据源 这件核心的事情,并不限制你的具体操作,切换了数据源可以做任何
CRUD
。 - 配置文件所有以下划线
_
分割的数据源 首部 即为组的名称,相同组名称的数据源会放在一个组下。 - 切换数据源可以是组名,也可以是具体数据源名称。组名则切换时采用负载均衡算法切换。
- 默认的数据源名称为
master
,你可以通过spring.datasource.dynamic.primary
修改。 - 方法上的注解优先于类上注解。
-
DS
支持继承抽象类上的DS
,暂不支持继承接口上的DS
。
如果需要配置从库,直接在datasource
后面继续加就行,默认会使用master
数据源,需要使用其他数据源时在service类或方法上加上@DS("数据源名称")
注解就行。
ok 上代码,先添加关键依赖:
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.5.3
com.fanqie
sharding-dynamic-atasource
0.0.1-SNAPSHOT
sharding-dynamic-atasource
test project for Sharding And Dynamic-DataSource
Hoxton.SR8
1.2.44
8.0.16
1.1.23
4.1.1
3.2.1
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-devtools
runtime
true
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
com.alibaba
druid
${druid.version}
org.projectlombok
lombok
1.16.18
com.baomidou
mybatis-plus-boot-starter
3.4.1
mysql
mysql-connector-java
${mysql.version}
com.baomidou
dynamic-datasource-spring-boot-starter
${dynamic-datasource.version}
org.apache.shardingsphere
sharding-jdbc-spring-boot-starter
${sharding-sphere.version}
org.apache.shardingsphere
sharding-core-common
${sharding-sphere.version}
org.springframework.boot
spring-boot-maven-plugin
org.projectlombok
lombok
src/main/java
**/*.xml
src/main/resources
**/*.yml
**/*.xml
注意:
最好是按照以上版本去使用,因为我发现,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 {
private static final Logger logger = LoggerFactory.getLogger(DatePreciseShardingAlgorithm.class);
@Override
public String doSharding(Collection availableTargetNames, PreciseShardingValue 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}注解,切换数据源
*
* @DS(DataSourceConfiguration.SHARDING_DATA_SOURCE_NAME)
*
* @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有四种数据源,需要根据业务注入不同的数据源
*
* 1. 未使用分片, 脱敏的名称(默认): shardingDataSource;
*
2. 主从数据源: masterSlaveDataSource;
*
3. 脱敏数据源:encryptDataSource;
*
4. 影子数据源:shadowDataSource
*
* shardingjdbc默认就是shardingDataSource
* 如果需要设置其他的可以使用
* @Resource(value="") 设置
*/
@Lazy
@Resource
DataSource shardingDataSource;
/**
* 将shardingDataSource放到了多数据源(dataSourceMap)中
* 注意有个版本的bug,3.1.1版本 不会进入loadDataSources 方法,这样就一直造成数据源注册失败
*/
@Bean
public DynamicDataSourceProvider dynamicDataSourceProvider() {
Map datasourceMap = dynamicDataSourceProperties.getDatasource();
return new AbstractDataSourceProvider() {
@Override
public Map loadDataSources() {
Map 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);
}
}
访问Shareding
的Service
实现类
需要增加数据源的别名,@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);
}
}