mysql第十话 - mysql+springboot之ShardingSphere-JDBC分表分库实战
作为一个后端开发,用过mycat的应该不在少数吧。说说作者使用mycat遇到的问题! 需要单独的服务,对于我们根本就只有一串连接地址的概念,有时候挂了都不知道(因为是运维部署的,给我们的只是跳板机连的数据库,并不是mycat),高并发出现读延迟(强制主库解决),但总的来说运维成本比较高。 今天出一个客服端分库分表的实战,其实了解以及很久了,一直没有付出行动! 那么今天就来了!
1.ShardingSphere介绍
官网直通车:Apache ShardingSphere 产品定位为 Database Plus,旨在构建异构数据库上层的标准和生态。 它关注如何充分合理地利用数据库的计算和存储能力,而并非实现一个全新的数据库。ShardingSphere 站在数据库的上层视角,关注他们之间的协作多于数据库自身。 ShardingSphere 已于 2020 年 4 月 16 日成为 Apache 软件基金会的顶级项目。 目前有三大组件:
- ShardingSphere-JDBC 基于协议层面对JDBC接口的封装,是以jar包客户端的形式存在的
- ShardingSphere-Proxy 独立的代理服务,类似mycat
- ShardingSphere-SCALING (EXPERIMENTAL) 实现中
今天主要来分析java端的ShardingSphere-JDBC
2.ShardingSphere-JDBC
2.1 介绍
ShardingSphere-JDBC自身定义为轻量级Java框架,可在Java JDBC层提供额外的服务。客户端直接连接到数据库时,它以jar形式提供服务,并且不需要额外的部署。
2.2 项目详细
- pom.xml 本文基于springboot-2.5.6,shardingsphere-5.1.5
<!--Mysql连接-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<!--shardingsphere-jdbc-->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
<version>5.1.1</version>
</dependency>
orm我这里用的是mybatis。
-
表结构 主库:demo_test 从库:demo_test1,demo_test2 分表:shard_test0、shard_test1,字段user_id,name 主从表:user_info(user_id,name),user_mobile(user_id,mobile)
-
插入代码和sql
@GetMapping("insert")
public Object insert() {
Map<String, Object> resMap = new HashMap<>();
for (int i = 0; i < 10; i++) {
resMap.put("user_id", i);
shardMapper.insertByUserId(resMap);
}
return 1;
}
- sql
<!--分表插入-->
<insert id="insertByUserId" parameterType="Map">
insert into shard_test(`user_id`,`name`) values (#{user_id},unix_timestamp())
</insert>
<!--分表查询-->
<select id="selectByUserId" parameterType="Integer" resultType="Map">
select * from shard_test where user_id=#{userId}
</select>
<!--分库插入 || 全局表-->
<insert id="insertUserByUserId" parameterType="Map">
insert into user_info(`user_id`,`name`) values (#{user_id},unix_timestamp())
</insert>
<!--分库插入 || 绑定表-->
<insert id="insertMobileByUserId" parameterType="Map">
insert into user_mobile(`user_id`,`mobile`) values (#{user_id},unix_timestamp())
</insert>
<!--关联查询-->
<select id="selectByUserId" parameterType="Integer" resultType="Map">
select * from user_info u
left join user_mobile um on u.user_id=um.user_id
where u.user_id=#{userId}
</select>
2.3 单库分表
作用:一个库两张表,通过取模的方式数据落到不同的表中。官网直通车
- shardingsphere配置
spring:
shardingsphere:
datasource:
names: ds0 #数据源名称
ds0:
type: com.zaxxer.hikari.HikariDataSource #使用的数据池
driver-class-name: com.mysql.jdbc.Driver #驱动
jdbc-url: jdbc:mysql://192.168.0.100:3306/demo_test #连接地址
username: root #账号
password: 123456 #密码
rules: #规则
sharding:
tables:
shard_test: #表名
#分片表配置由数据源名 + 表名组成,以小数点分隔。多个表以逗号分隔,支持 inline 表达式。
actual-data-nodes: ds0.shard_test$->{0..1}
table-strategy: #分表策略
standard: #单分片键的场景
sharding-column: user_id #分片键 就是根据那个字段分片
sharding-algorithm-name: my_table #分片算法名称 详细在下面
sharding-algorithms: #分片算法
my_table: #上面自定义的算法名称
type: INLINE
props:
#分片算法计算规则
algorithm-expression: shard_test$->{user_id % 2} props:
sql-show: true #开启日志
- 测试插入和查询日志
//插入日志 根据取模分布到ds0库的shard_test0和shard_test1表了
Actual SQL: ds0 ::: insert into shard_test0(`user_id`,`name`) values (?, unix_timestamp()) ::: [8]
Logic SQL: insert into shard_test(`user_id`,`name`) values (?,unix_timestamp())
Actual SQL: ds0 ::: insert into shard_test1(`user_id`,`name`) values (?, unix_timestamp()) ::: [9]
Logic SQL: select * from shard_test where user_id=?
//user_id 1和2的查询日志
Actual SQL: ds0 ::: select * from shard_test1 where user_id=? ::: [1]
Logic SQL: select * from shard_test where user_id=?
Actual SQL: ds0 ::: select * from shard_test0 where user_id=? ::: [2]
通过日志是可以看到能够正常分片插入和分片查询的。但是这种单库分表一般不怎么推荐,毕竟服务器压力还是在
2.4 分库单表
作用:将数据分片到不同的数据库中的同一张表中 数据库ds0,ds1表user_info
spring:
shardingsphere:
datasource:
names: ds0,ds1
ds0:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.jdbc.Driver
jdbc-url: jdbc:mysql://192.168.0.100:3306/demo_test
username: root
password: 123456
ds1:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.jdbc.Driver
jdbc-url: jdbc:mysql://192.168.0.100:3306/demo_test1
username: root
password: 123456
rules:
sharding:
tables:
user_info: #表名
#分片表配置由数据源名 + 表名组成,以小数点分隔。多个表以逗号分隔,支持 inline 表达式。
actual-data-nodes: ds$->{0..1}.user_info
#分库分片键 和分表配置不同的就是这个key
database-strategy:
standard:
sharding-column: user_id
sharding-algorithm-name: my_database
sharding-algorithms:
my_database:
type: INLINE
props:
#分片键算法
algorithm-expression: ds$->{user_id % 2}
props:
sql-show: true
- 插入日志
Actual SQL: ds0 ::: insert into user_info(`user_id`,`name`) values (?, unix_timestamp()) ::: [6]
insert into user_info(`user_id`,`name`) values (?,unix_timestamp())
Actual SQL: ds1 ::: insert into user_info(`user_id`,`name`) values (?, unix_timestamp()) ::: [7]
根据user_id取模分布到不同的数据库中了
2.5 绑定表
作用:分库后的关联查询,避免出现笛卡尔积
#数据源配置省略了
rules:
sharding:
tables:
user_info: #user_info分库策略
actual-data-nodes: ds$->{0..1}.user_info
database-strategy:
standard:
sharding-column: user_id
sharding-algorithm-name: my_database
user_mobile: #user_mobile分库策略
actual-data-nodes: ds$->{0..1}.user_mobile
database-strategy:
standard:
sharding-column: user_id
sharding-algorithm-name: my_database
sharding-algorithms:
my_database: #使用的都是my_database这个分片算法
type: INLINE
props:
algorithm-expression: ds$->{user_id % 2}
#绑定表 这里是关键 如果又分表又分库 关联查询会出现笛卡尔积
binding-tables: user_info,user_mobile
- 测试日志输出
//往两个表插入数据
Actual SQL: ds1 ::: insert into user_info(`user_id`,`name`) values (?, unix_timestamp()) ::: [7]
Logic SQL: insert into user_mobile(`user_id`,`mobile`) values (?,unix_timestamp())
Actual SQL: ds1 ::: insert into user_mobile(`user_id`,`mobile`) values (?, unix_timestamp()) ::: [7]
insert into user_info(`user_id`,`name`) values (?,unix_timestamp())
Actual SQL: ds0 ::: insert into user_info(`user_id`,`name`) values (?, unix_timestamp()) ::: [8]
Logic SQL: insert into user_mobile(`user_id`,`mobile`) values (?,unix_timestamp())
Actual SQL: ds0 ::: insert into user_mobile(`user_id`,`mobile`) values (?, unix_timestamp()) ::: [8]
Logic SQL: insert into user_info(`user_id`,`name`) values (?,unix_timestamp())
//查询关联查询日志user_id 2和3
Actual SQL: ds0 ::: select * from user_info u
left join user_mobile um on u.user_id=um.user_id
where u.user_id=? ::: [2]
Actual SQL: ds1 ::: select * from user_info u
left join user_mobile um on u.user_id=um.user_id
where u.user_id=? ::: [3]
关联查询能正常路由到指定的数据库
2.6 广播表(全局表)
作用:全局唯一ID,业务配置冗余,插入的数据均分布到两个库中
#数据源配置省略
rules:
sharding:
#只需要声明表名
broadcast-tables: shard_test0
- 测试日志输出
//插入日志
Actual SQL: ds0 ::: insert into shard_test0(`user_id`,`name`) values (?, unix_timestamp()) ::: [9]
Actual SQL: ds1 ::: insert into shard_test0(`user_id`,`name`) values (?, unix_timestamp()) ::: [9]
Logic SQL: insert into shard_test0(`user_id`,`name`) values (?,unix_timestamp())
//查询日志输出
Actual SQL: ds0 ::: select * from shard_test0 where user_id=? ::: [5]
Logic SQL: select * from shard_test0 where user_id=?
//第三次查询
Actual SQL: ds1 ::: select * from shard_test0 where user_id=? ::: [5]
可以看到数据会往两个数据源都插入一份,而查询应该是基于随机算法
2.7 读写分离
作用:利用数据库的主从复制实现读写分离,提高数据库读取性能,一主多从和多主多从
- yaml配置
rules:
#读写分离
readwrite-splitting:
data-sources:
ds0: #上面定义的数据源名字 也可随便定义不重复的
type: Static #类型Static,Dynamic
props:
#写库
write-data-source-name: ds0
#读库 多个从库用逗号分隔
read-data-source-names: ds1,ds2
# ds1: #多个主库需要加这个配置 和分库配置
# type: Static
# props:
# write-data-source-name: ds1
# read-data-source-names: ds3
- 测试日志输出
//查询日志
Actual SQL: ds1 ::: select * from shard_test0 where user_id=? ::: [7]
Logic SQL: select * from shard_test0 where user_id=?
Actual SQL: ds2 ::: select * from shard_test0 where user_id=? ::: [8]
默认是轮询算法,如需配置随机算法可参考下面配置
rules:
readwrite-splitting:
data-sources:
ds0:
type: Static
props:
write-data-source-name: ds0
read-data-source-names: ds1,ds2
#复制均衡算法名称
load-balancer-name: load_name
load-balancers:
load_name: #名称
#算法 round_robin/轮询 random/随机 权重
type: random
props:
default: 0 #要有这个 不然启动报错
3.总结
总体来说用起来还是比较舒服的,完美的实现了分库分表,读写分离。
这里贴一下要注意的问题点:
1.有些配置里面的<table-name>
是一定要写对应的表名的,有些这种配置就可以随便定义
2.分片键配置的行内表达式如果有比较复杂的,可手写代码实现分片规则
3.读延迟问题
,可在读之前执行HintManager.getInstance().setWriteRouteOnly();
这个代码强制读主库,但是如果还有后续查询记得清除线程变量。
总结一下和mycat的区别:
SharingSphere-JDBC | Mycat | |
---|---|---|
工作层面 | JDBC协议,接口封装 | Mysql协议,JDBC协议 |
运行方式 | Jar包,客服端 | 独立服务,服务端 |
开发方式 | 代码,配置 | 数据量连接地址 |
运维方式 | 无 | 运维单独维护 |
性能 | 多线程并发 | 独立服务,需要看服务器配置 |
支持语言 | 仅支持Java | 支持JDBC协议的语言 |
以上就是本章的全部内容了。
上一篇:mysql第九话 - mysql主从复制集群实现 下一篇:通信框架之Netty第一话 - NIO的超神发展之路
天行健,君子以自强不息
转载自:https://juejin.cn/post/7131173578535338015