前言
随着产品业务的扩展以及用户量上涨,一些数据表数据量会变得特别大,且每日数据记录增量也越来越大,导致查询效率低,影响用户体验。因此需要对数据库进行分表。
数据分片
垂直分片
数据库 - 根据业务将数据表分散到不同的数据库中,如订单库和用户库
数据表 - 将表数据根据字段分散到不同的表中
- 缺点
- 无法真正解决单点瓶颈问题
- 可以缓解数据量和访问量带来的问题,但无法根治
- 表中的数据量依然超过单节点所能承载的阈值,则需要水平分片来进一步处理
水平分片
将表数据分散到一个或多个数据库的相同结构的表中
技术选择
Sharding-jdbc
- 需要新增代码和配置
- 分表分库,读写分离
- 可自定义分片策略
MyCat
- 数据库中间层
Sharding-Jdbc
Sharding-JDBC是ShardingSphere的第一个产品,也是ShardingSphere的前身。 它定位为轻量级Java框架,在Java的JDBC层提供的额外服务。它使用客户端直连数据库,以jar包形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架。
- 适用于任何基于JDBC的ORM框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template或直接使用JDBC。
- 支持任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP等。
- 支持任意实现JDBC规范的数据库。目前支持MySQL,Oracle,SQLServer,PostgreSQL以及任何遵循SQL92标准的数据库。
SpringBoot 集成 Sharding-jdbc
- 添加依赖
<!--sharding-sphere版本号-->
<properties>
<sharding-sphere.version>4.0.0-RC3</sharding-sphere.version>
</properties>
<!-- for spring boot -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>${sharding-sphere.version}</version>
</dependency>
- 基于Spring boot的规则配置
spring:
shardingsphere:
# 数据源配置
datasource:
names: ds0
ds0:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/gaofenshuo_dev?useSSL=false&useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
username: root
password: 123456
# 分片规则配置
sharding:
# 默认分库策略,由于不需要分库,所以直接设置ds0
default-database-strategy:
inline:
sharding-column: id
algorithm-expression: ds0
# 分表策略配置
tables:
uc_sys_evaluationlog:
database-strategy:
inline:
sharding-column: id
algorithm-expression: ds0
table-strategy:
standard:
sharding-column: created_at
precise-algorithm-class-name: com.gfs.shardingjdbcdemo.config.CreateDatePreciseShardingAlgorithm
range-algorithm-class-name: com.gfs.shardingjdbcdemo.config.CreateDateRangeShardingAlgorithm
# inline:
# sharding-column: id
# algorithm-expression: uc_sys_evaluationlog$->{id % 2}
# actual-data-nodes: ds0.uc_sys_evaluationlog_$->{2019..2020}$->{['00','01','02','03','04','05','06','07','08','09','10','11','12']},
actual-data-nodes: ds0.uc_sys_evaluationlog_$->{2019}$->{['01','02']},
key-generator:
column: id
type: SNOWFLAKE
props:
sql:
show: true
当项目已存在数据源情况下,需要删除原有数据源的配置,才可应用成功,否则会沿用原有数据源,不使用sharding-jdbc服务。
- 编写测试代码
- controller
@RestController
public class DemoController {
@Autowired
private DemoDao demoDao;
@GetMapping("add")
public String demo(){
demoDao.addDemo();
return "success";
}
@GetMapping("query")
public JSONArray query(){
return demoDao.queryDemo();
}
}
- demoDao
public interface DemoDao {
void addDemo();
JSONArray queryDemo();
}
- demo.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.gfs.shardingjdbcdemo.dao.DemoDao">
<insert id="addDemo">
INSERT INTO uc_sys_evaluationlog (
student_id,
created_at,
evaluation_mode,
evaluation_type,
evaluation_id,
evaluation_record_id,
content_type,
content_id,
item_order,
cloud_score,
total_score,
score,
audio_url,
cloud_json,
study_time,
cloud_type
)
VALUES
(
3145,
'2019-01-02 10:00:00',
'mode',
'sharding',
NULL,
10021,
'demo',
101,
NULL,
100,
NULL,
100.00,
NULL,
NULL,
9999,
NULL
);
</insert>
<select id="queryDemo" resultType="java.util.Map">
select * from uc_sys_evaluationlog where id = 100
</select>
</mapper>
- 精确分片策略
@Slf4j
public class CreateDatePreciseShardingAlgorithm implements PreciseShardingAlgorithm<String> {
private final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMM");
/**
* Sharding.
*
* @param availableTargetNames available data sources or tables's names
* @param shardingValue sharding value uc_sys_evaluationlog_201901
* @return sharding result for data source or table's name
*/
@SneakyThrows
@Override
public String doSharding(final Collection<String> availableTargetNames, final PreciseShardingValue<String> shardingValue) {
log.info("availableTargetNames:{}", JSON.toJSONString(availableTargetNames));
log.info("shardingValue:{}", JSON.toJSONString(shardingValue));
Date date = DateUtils.parseDate(shardingValue.getValue(),"yyyy-MM-dd HH:mm:ss");
String realDate = sdf.format(date);
final String tableName = shardingValue.getLogicTableName();
final String split = "_";
return tableName + split + realDate;
}
}
- 范围分片策略
@Slf4j
public class CreateDateRangeShardingAlgorithm implements RangeShardingAlgorithm<String> {
private final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMM");
/**
* Sharding.
*
* @param availableTargetNames available data sources or tables's names
* @param shardingValue sharding value
* @return sharding results for data sources or tables's names
*/
@SneakyThrows
@Override
public Collection<String> doSharding(Collection<String> availableTargetNames, RangeShardingValue<String> shardingValue) {
String lower = shardingValue.getValueRange().lowerEndpoint();
String upper = shardingValue.getValueRange().upperEndpoint();
Date lowerDate = DateUtils.parseDate(lower, "yyyy-MM-dd HH:mm:ss");
Date upperDate = DateUtils.parseDate(upper, "yyyy-MM-dd HH:mm:ss");
Date tempDate = lowerDate;
Collection<String> result = Lists.newArrayList();
final String tableName = shardingValue.getLogicTableName();
final String spilt = "_";
while (upperDate.after(tempDate)) {
result.add(tableName + spilt + sdf.format(tempDate));
tempDate = DateUtils.addMonths(tempDate, 1);
}
result.add(tableName + spilt + sdf.format(tempDate));
return result;
}
}
- 代码地址
https://github.com/gaozaoshun/sharding-jdbc-demo.git