shardingsphere读写分离+精确分片 | 张扎瓦的博客

shardingsphere读写分离+精确分片

使用shardingsphere进行读写分离+精确分表


背景

最近在做一个项目,这个项目的数据量比较大,有几张表会每天产生几十万条数据,其中一张表数据特别大,每天一百万左右,由于前期并未预估到如此大的数据量,还是单库单表,所以导致查询特别慢,有时候还会卡死,急需进行分库分表。

shardingsphere简介

官网地址:https://shardingsphere.apache.org/index_zh.html

Apache ShardingSphere 是一套开源的分布式数据库中间件解决方案组成的生态圈,它由 JDBC、Proxy 和 Sidecar(规划中)这 3 款相互独立,却又能够混合部署配合使用的产品组成。 它们均提供标准化的数据分片、分布式事务和数据库治理功能,可适用于如 Java 同构、异构语言、云原生等各种多样化的应用场景。

Apache ShardingSphere 定位为关系型数据库中间件,旨在充分合理地在分布式的场景下利用关系型数据库的计算和存储能力,而并非实现一个全新的关系型数据库。 它通过关注不变,进而抓住事物本质。关系型数据库当今依然占有巨大市场,是各个公司核心业务的基石,未来也难于撼动,我们目前阶段更加关注在原有基础上的增量,而非颠覆。

Apache ShardingSphere 5.x 版本开始致力于可插拔架构,项目的功能组件能够灵活的以可插拔的方式进行扩展。 目前,数据分片、读写分离、多数据副本、数据加密、影子库压测等功能,以及 MySQL、PostgreSQL、SQLServer、Oracle 等 SQL 与协议的支持,均通过插件的方式织入项目。 开发者能够像使用积木一样定制属于自己的独特系统。Apache ShardingSphere 目前已提供数十个 SPI 作为系统的扩展点,仍在不断增加中。

ShardingSphere 已于2020年4月16日成为 Apache 软件基金会的顶级项目。

常见的分库分表简介

  • 垂直分表
    将一个大表的若干字段提取出来,拆分成多个小表
  • 垂直分库
    专库专表 数据库根据功能划分,如订单库,商品库
  • 水平分表
    同一个表 拆分成结构相同的多个表,减少单表数据量
  • 水平分库
    同一个库,拆分成结构相同的多个库,减少单个数据库的数据量

解决办法

因为业务不是特别复杂,也没有字段特别多的表,所以使用水平分表,根据数据量划分,每张表的数据量控制在五六百万左右。分表方式采用时间后缀的方式区分,如:order_202007,order_202008。因为数据库的写入操作比较频繁(10分钟一次),为了减少一个数据库的压力,降低由于数据更新导致的行锁,采用读写分离,即:主库负责写入操作,从库负责查询,主从通过bin-log方式进行数据同步。

开始干

MySQL配置主从复制

这一部分可以参考我的上一篇博客,地址:mysql主从配置

springboot+shardingsphere配置

maven配置

1
2
3
4
5
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.0.0-RC1</version>
</dependency>

properties 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#sharding 分表配置
#一个实体类对应多张表
spring.main.allow-bean-definition-overriding=true

#配置数据源,给数据源起名
spring.shardingsphere.datasource.names=master,slave0

#主库配置
spring.shardingsphere.datasource.master.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.master.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.master.url=jdbc:mysql://192.168.0.43:3306/sas_hkdl?useUnicode=true&allowMultiQueries=true&useSSL=false
spring.shardingsphere.datasource.master.username=root
spring.shardingsphere.datasource.master.password=zhangxu

#从库配置
spring.shardingsphere.datasource.slave0.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.slave0.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.slave0.url=jdbc:mysql://192.168.0.43:3307/sas_hkdl?useUnicode=true&allowMultiQueries=true&useSSL=false
spring.shardingsphere.datasource.slave0.username=root
spring.shardingsphere.datasource.slave0.password=zhangxu

#配置读写分离
spring.shardingsphere.sharding.master-slave-rules.ds.masterDataSourceName=master
spring.shardingsphere.sharding.master-slave-rules.ds.slaveDataSourceNames=slave0

#配置精确分表
#范围分表 -- 按月
spring.shardingsphere.sharding.tables.hkdl_upload_audio.actual-data-Nodes=ds.hkdl_upload_audio${[]}
#分片的主键
spring.shardingsphere.sharding.tables.hkdl_upload_audio.table-strategy.standard.sharding-column=start_time
#自定义精确分表的实现类
spring.shardingsphere.sharding.tables.hkdl_upload_audio.table-strategy.standard.precise-algorithm-class-name=com.huayunworld.sas.web.conf.db.algorithm.month.MonthPreciseShardingAlgorithm
#范围分表的实现类,用来查找符合查询范围内的表名
spring.shardingsphere.sharding.tables.hkdl_upload_audio.table-strategy.standard.range-algorithm-class-name=com.huayunworld.sas.web.conf.db.algorithm.month.MonthRangeShardingAlgorithm


#打印sql语句
spring.shardingsphere.props.sql.show=true

代码实现

类 PreciseShardingAlgorithm 的泛型,就是上面配置的分表主键的数据类型

MonthPreciseShardingAlgorithm 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* 分表策略,根据日期分表
*
* @author zhangxu
* @date 2020/6/28 11:45
*/
@Slf4j
public class MonthPreciseShardingAlgorithm implements PreciseShardingAlgorithm<Timestamp> {
private static final DateTimeFormatter MONTH_FORMAT = DateTimeFormatter.ofPattern("yyyyMM", Locale.CHINA);
//表名分隔符
private static final String SEPERATOR = "_";

@Override
public String doSharding(Collection<String> collection, PreciseShardingValue<Timestamp> shardingValue) {
String loginTableName = shardingValue.getLogicTableName();
Timestamp createTime = shardingValue.getValue();

try {
String timeStr = SEPERATOR + createTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDate().format(MONTH_FORMAT);
log.info("进入表:{}", loginTableName + timeStr);
return loginTableName + timeStr;
} catch (Exception e) {
log.error("解析创建时间异常,分表失败", e);
}
return loginTableName;
}
}

MonthRangeShardingAlgorithm 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* 范围查询策略
*
* @author zhangxu
* @date 2020/6/28 11:53
*/
@Slf4j
public class MonthRangeShardingAlgorithm implements RangeShardingAlgorithm<Timestamp> {
private static final DateTimeFormatter MONTH_FORMAT = DateTimeFormatter.ofPattern("yyyyMM", Locale.CHINA);
//表名分隔符
private static final String SEPERATOR = "_";

@Override
public Collection<String> doSharding(Collection<String> collection, RangeShardingValue<Timestamp> rangeShardingValue) {
Collection<String> tableSet = Sets.newConcurrentHashSet();
String logicTableName = rangeShardingValue.getLogicTableName();
Range<Timestamp> dates = rangeShardingValue.getValueRange();

// 获取开始时间
LocalDate startTime = dates.lowerEndpoint().toLocalDateTime().toLocalDate();
// 获取结束时间
LocalDate endTime = dates.upperEndpoint().toLocalDateTime().toLocalDate();

// 计算传入的月份跟当前月份的差值
Period period = Period.between(startTime, endTime);
int subMonths = period.getMonths() + 1;
for (int i = 0; i <= subMonths; i++) {
tableSet.add(logicTableName + SEPERATOR + MONTH_FORMAT.format(startTime));
startTime = startTime.plusMonths(1);
}
log.info("要查询的表集合:{}", tableSet);
return tableSet;
}
}

需要注意的一点,每次查询或者插入时,都必须传入分表字段,否则无法分表。

如果我的文章对您有所帮助,不妨打赏一杯豆浆以资鼓励(○` 3′○)