系列文档参考 MYSQL系列-整体架构介绍
目前市面上有很多比较成熟的分库分表中间件,可以帮助我们解决分库分表后多数据源问题目前常用的就是下面三种,接下来3个章节我们来一一介绍
- 「 Spring 动态数据源 」
- 「shardingsphere(前身 sharding-jdbc)」
- 「Mycat」
原始需求
有一个会员积分系统,承载线上业务会员积分能力,随着用户数的增长,会员积分相关表数据量急剧增长,单表存储已经不能满足业务需求,需要对积分相关表进行分库分表处理,以适应日益增长的数据量。
因积分数据量巨大,先需要对同一个表进行水平分库分表处理
为啥要分库分表处理?
- 如果仅分库不分表,那么可能需要100个库或者更多,然而数据库连接池是db实例级别的,如果每个数据库连接池最小连接数是10,那么最少也需要1000个常驻线程,会把本地线程撑爆
- 如果仅分表不分库,那么高峰时期的流程都打到同一db上,这样也是无法横向扩展的
拆分结果类似如下(示例拆成2个库,每个库2张分表):
设计思路
基于Spring boot + mybatics + druid + 动态数据源,支持读写分离,配置表落在积分库1
实现方案
数据库表脚本
新建了2个库point_shard1、point_shard2 其中point_balance在1 2库各两张分表point_balance1 point_balance2
param_config是配置表,落在1库
sql
CREATE DATABASE if not exists point_shard1 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE DATABASE if not exists point_shard2 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
use point_shard1;
DROP TABLE IF EXISTS `point_balance1`;
CREATE TABLE IF NOT EXISTS `point_balance1` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`uid` BIGINT(20) NOT NULL COMMENT 'uid',
`type` SMALLINT(5) NOT NULL COMMENT '积分类型',
`value` BIGINT(20) NOT NULL COMMENT '积分值',
`expire_time` DATE NOT NULL COMMENT '过期时间',
`modify_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
`country` VARCHAR(20) NOT NULL DEFAULT 'CN' COMMENT '国家',
PRIMARY KEY (`id`),
UNIQUE KEY idx_uid_and_expire_time(`uid`,`expire_time`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='用户余额表1';
DROP TABLE IF EXISTS `param_config`;
CREATE TABLE param_config (
`id` INT AUTO_INCREMENT NOT NULL COMMENT '自增ID',
`param_key` VARCHAR (50) NOT NULL COMMENT '配置KEY',
`param_key_desc` VARCHAR (200) DEFAULT NULL COMMENT '配置KEY说明',
`param_value` VARCHAR (1024) NOT NULL COMMENT '配置value',
`param_value_type` SMALLINT (2) NOT NULL DEFAULT 0 COMMENT '参数类型',
`create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`modify_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
`country` VARCHAR (20) NOT NULL DEFAULT 'CN' COMMENT '国家',
PRIMARY KEY (`id`),
INDEX idx_param_key_country (param_key, country)
) ENGINE = INNODB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8 COMMENT = '配置参数表' ;
maven相关引用
因为涉及spring boot+mybatics+druid,以及调试使用junit,sl4j日志打印,最终引用包如下
xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.7.9</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<version>2.7.9</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.16</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.5</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>3.2.10</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.7.9</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
<version>2.7.9</version>
</dependency>
</dependencies>
mybatics相关配置
mybatics配置信息我选择放在xml配置文件中,放在\resources\spring\spring_jdbc.xml
首先druid配置数据库连接串信息,如下
xml
<bean id="db_w_1" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="${master.jdbc.url1}"/>
<property name="username" value="${master.jdbc.username}"/>
<property name="password" value="${master.jdbc.password}"/>
<!-- Druid连接池属性配置 -->
<property name="initialSize" value="5"/>
<property name="maxActive" value="20"/>
<property name="minIdle" value="5"/>
<property name="maxWait" value="60000"/>
<!-- 添加更多的属性配置 -->
<property name="filters" value="config"/>
<property name="validationQuery" value="select 1"/>
<property name="timeBetweenEvictionRunsMillis" value="600000"/>
<property name="minEvictableIdleTimeMillis" value="600000"/>
<property name="defaultAutoCommit" value="true"/>
<property name="testOnBorrow" value="true"/>
<property name="testOnReturn" value="true"/>
<property name="testWhileIdle" value="true"/>
<property name="removeAbandoned" value="true"/>
<property name="removeAbandonedTimeout" value="1800"/>
<property name="logAbandoned" value="true"/>
</bean>
共有写库:db_w_1 db_w_2 读库:db_r_1 db_r_2
自己实现的数据源ShardingDataSource
,路由策略ContextDbRouter
,公共类(包含分库分表算法)DataSourceUtil
以及其他的一些通用配置实现,具体配置如下
xml
<bean id="shardingDataSource" class="com.toby.dynamic.data.source.db.ShardingDataSource"/>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="shardingDataSource"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager" order="2"/>
<bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="shardingDataSource"/>
<property name="configLocation" value="classpath:sqlmap-config.xml"/>
<property name="mapperLocations">
<array>
<value>classpath*:sqlmap/*.xml</value>
<value>classpath*:sqlmap/config/*.xml</value>
</array>
</property>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.toby.dynamic.data.source.db.dao"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryBean"/>
</bean>
<!-- 配置的分库分表以及读写库数据源-->
<util:map id="datasourceMap" key-type="java.lang.String" value-type="java.util.Map">
<entry key="MASTER">
<map key-type="java.lang.String" value-type="java.lang.String">
<entry key="1" value="db_w_1"/>
<entry key="2" value="db_w_2"/>
<!-- default 默认数据源写库-->
<entry key="-1" value="db_w_1"/>
</map>
</entry>
<entry key="READ">
<map>
<entry key="1" value="db_r_1"/>
<entry key="2" value="db_r_2"/>
<!-- default 默认数据源读库-->
<entry key="-1" value="db_r_1"/>
</map>
</entry>
</util:map>
<!-- 分库分表路由实现-->
<bean class="com.toby.dynamic.data.source.db.route.ContextDbRouter">
<property name="dataSourceGroup" ref="datasourceMap"/>
</bean>
分库核心实现
UML类图如下
未完待续