为什么是sharding-jdbc
目前市面上的竞品主要是阿里开源的mycat,网上很多的表格对比图,大同小异,这里我们只讨论它们的不同点:
- sharding-jdbc
- 以jar包提供服务。
- 无需单独部署,更加可靠,运维成本低,但对代码有侵入性。
- 社区活跃度更高,有问题可以及时反馈。
- mycat
- 以中间件的方式提供服务。
- 需要单独部署,可靠性较低,运维成本高,但对代码友好,无侵入性。
- 社区活跃度较低。
综合公司资源,开发成本,我们选择了更可靠、活跃度更高的sharding-jdbc,虽然对代码有一定侵入性,但使用到数据分片的表毕竟在少数,所以侵入性在可控范围内。
sharding-jdbc分表不分库,读写分离实践
PS:大多数场景下,我们使用分表不分库基本就能满足我们的需求,所以本文仅讨论下分表不分库利用sharding-jdbc实现读写分离最佳实践。
- 首先我们创建分表如下,即t_order_info_$->{0..9},一库十表的设计,当然在生产环境中我们还需要根据我们的实际情况,例如存量数据的大小以及数据增量的速度,我们分表之后至少要保证五年内甚至更长时间无需再次处理数据分片。
create table t_order_info_0
(
order_id bigint not null comment '订单号'
primary key,
uid bigint not null comment '用户ID',
product_id bigint not null comment '商品ID'
);
create table t_order_info_1
(
order_id bigint not null comment '订单号'
primary key,
uid bigint not null comment '用户ID',
product_id bigint not null comment '商品ID'
);
# 因为只是表名递增,此处省略表2-8.....
create table t_order_info_9
(
order_id bigint not null comment '订单号'
primary key,
uid bigint not null comment '用户ID',
product_id bigint not null comment '商品ID'
);
- 创建maven工程,并在pom.xml中引入如下依赖
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.github</groupId>
<artifactId>sharding-jdbc</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--Mybatis-Plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.1.1</version>
</dependency>
<!--shardingsphere start-->
<!-- for spring boot -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.0.1</version>
</dependency>
<!-- for spring namespace -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-namespace</artifactId>
<version>4.0.1</version>
</dependency>
<!--shardingsphere end-->
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.23</version>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>utf8</encoding>
<compilerArgument>-XDignore.symbol.file=true -Xlint</compilerArgument>
<testCompilerArgument>-XDignore.symbol.file=true -Xlint</testCompilerArgument>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.1.0.RELEASE</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
- 配置yml文件如下
spring:
shardingsphere:
datasource:
names: master,slave
master:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/coffe?connectTimeout=1000&useUnicode=true&useSSL=false&serverTimezone=GMT%2b8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&characterEncoding=utf8
username: your_database_username
password: your_database_password
initialSize: 5
minIdle: 80
maxActive: 200
# 连接池中的等待连接的超时时间,单位ms
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
# 打开PSCache,并且指定每个连接上PSCache的大小
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
filters: stat,wall,slf4j
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
# 合并多个DruidDataSource的监控数据
use-global-data-source-stat: true
slave:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/coffe?connectTimeout=1000&useUnicode=true&useSSL=false&serverTimezone=GMT%2b8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&characterEncoding=utf8
username: your_database_username
password: your_database_password
initialSize: 5
minIdle: 80
maxActive: 200
# 连接池中的等待连接的超时时间,单位ms
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
# 打开PSCache,并且指定每个连接上PSCache的大小
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
filters: stat,wall,slf4j
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
# 合并多个DruidDataSource的监控数据
use-global-data-source-stat: true
sharding:
tables:
t_order_info:
actual-data-nodes: ms_ds.t_order_info_$->{0..9}
table-strategy:
inline:
sharding-column: order_id
algorithm-expression: t_order_info_$->{order_id % 10}
master-slave-rules:
ms_ds:
master-data-source-name: master
slave-data-source-names:
- slave
- 创建单元测试,运行单元测试后我们可以看到sharding-jdbc与mybatis-plus自动配置,并识别数据分片,按照我们的路由规则自动识别到t_order_info_1并正确查出数据。
public class OrderInfoDaoTest extends BaseTest {
@Autowired
private OrderInfoDao orderInfoDao;
@Test
public void queryOrderInfo() {
System.out.println(orderInfoDao.lambdaQuery().eq(OrderInfo::getOrderId, 1).one());
}
}
有 一个细节不知道各位同学有没有发现,我们这里数据库连接池依然是采用阿里开源druid,那sharding-jdbc整合mybatis-plus中,druid的属性又是如何配置的呢,细心的同学已经发现了,直接在数据源中继续配置就行,那这是如何实现的呢。我们看如下代码:
public static DataSource getDataSource(final String dataSourceClassName, final Map<String, Object> dataSourceProperties) throws ReflectiveOperationException {
DataSource result = (DataSource) Class.forName(dataSourceClassName).newInstance();
for (Entry<String, Object> entry : dataSourceProperties.entrySet()) {
callSetterMethod(result, getSetterMethodName(entry.getKey()), null == entry.getValue() ? null : entry.getValue().toString());
}
return result;
}
private static void callSetterMethod(final DataSource dataSource, final String methodName, final String setterValue) {
for (Class<?> each : GENERAL_CLASS_TYPE) {
try {
Method method = dataSource.getClass().getMethod(methodName, each);
if (boolean.class == each || Boolean.class == each) {
method.invoke(dataSource, Boolean.valueOf(setterValue));
} else if (int.class == each || Integer.class == each) {
method.invoke(dataSource, Integer.parseInt(setterValue));
} else if (long.class == each || Long.class == each) {
method.invoke(dataSource, Long.parseLong(setterValue));
} else {
method.invoke(dataSource, setterValue);
}
return;
} catch (final ReflectiveOperationException ignored) {
}
}
}
可以发现sharding-jdbc是通过反射的方式去实例化druid,并将各位同学配置的属性通过反射的方式一一赋值,从而使得druid配置生效。设置失败则直接忽略。
sharding-jdbc强制查主库
sharding-jdbc如果配置了主从分离的话,默认主写从读,要看到底查询走的从库还是主库,我们需要把sql打印的配置打开,配置如下:
spring:
shardingsphere:
props:
sql:
show: true
启动以上配置后我们再次执行单元测试,sharding-jdbc就会详细的打印执行的sql如下:
我们关注实际sql就能清晰的看到,走的slave(从库)查询,并且自动路由到t_order_info_1,因为我们查询的order_id=1。
但是有些场景下,因为主从延迟的问题,我们必须要查询主库才能使业务代码正常运转,那sharding-jdbc中我们怎么走主库查询呢,其实非常简单,在需要执行的查询语句前添加如下语句即可走主库查询:
HintManager.getInstance().setMasterRouteOnly();
但是需要注意的是,执行该语句的后续sql将全部走主库查询,那如果我们只想执行该语句后的第一个查询语句走主库查询,那我们应该怎么做呢,我们发现HintManager实现了AutoCloseable,那各位同学不难想到,只要将HintManager关闭,就能切换到从库查询了,于是我们就可以这样写:
try (HintManager hintManager = HintManager.getInstance()) {
hintManager.setMasterRouteOnly();
// your master query sql
}
// your others query sql
我们来验证下,编写如下单元测试:
@Test
public void queryOrderInfo() {
try (HintManager hintManager = HintManager.getInstance()) {
hintManager.setMasterRouteOnly();
System.out.println(orderInfoDao.lambdaQuery().eq(OrderInfo::getOrderId, 1).one());
}
System.out.println(orderInfoDao.lambdaQuery().eq(OrderInfo::getOrderId, 2).one());
}
执行结果如下:
我们可以看到第一个查询走了主库查询,第二个则正常走从库查询了。
sharding-jdbc分表后如何使用事务
对于我们的只分表不分库来说,该场景并不涉及分布式事务,所以使用事务时直接添加Spring的@Transactional(rollbackFor = Exception.class)即可。
对于涉及分布式事务的场景,我们还需要添加一个注解
@ShardingTransactionType(TransactionType.LOCAL)
sharding-jdbc会根据注解中配置的事务类型使用不同的事务管理器来实现分布式事务,分布式事务我们在此不做展开,后续我们单独讨论分布式事务。
总结
sharding-jdbc是一款开箱即用的数据库分片框架,只需简单配置,即可实现各种复杂的数据分片操作,与Spring,Mybatis-plus整合的也非常好,在现有项目上引入sharding-jdbc也变的非常容易。有需求的同学快上手实践吧。如果各位同学觉得对你有所帮助,请关注、点赞、评论、收藏来支持我,手头宽裕的话也可以赞赏来表达各位的认可,各位同学的支持是对我最大的鼓励。未来为大家带来更好的创作。
以上所有代码已经上传到GitHub:
https://github.com/NotExistUserName/sharding-jdbc.git
分享一句非常喜欢的话:把根牢牢扎深,再等春风一来,便会春暖花开。
版权声明:以上引用信息以及图片均来自网络公开信息,如有侵权,请留言或联系
504401503@qq.com,立马删除。