mysql第十话 - Mybatis+Springboot3.2.3(2.5.6)之ShardingSphere-JDBC分表分库实战
官网直通车:Apache ShardingSphere 产品定位为 Database Plus,旨在构建异构数据库上层的标准和生态。它关注如何充分合理地利用数据库的计算和存储能力,而并非实现一个全新的数据库。ShardingSphere 站在数据库的上层视角,关注他们之间的协作多于数据库自身。ShardingSphere 已于 2020 年 4 月 16 日成为 Apache 软件基金会的顶级项目
作为一个后端开发,用过mycat的应该不在少数吧。说说作者使用mycat遇到的问题!
需要单独的服务,对于我们根本就只有一串连接地址的概念,有时候挂了都不知道(因为是运维部署的,给我们的只是跳板机连的数据库,并不是mycat),高并发出现读延迟(强制主库解决),但总的来说运维成本比较高。
今天出一个客户端分库分表的实战,其实了解以及很久了,一直没有付出行动!
那么今天就来了!
文章于2024-08-15更新,JDK21+Springboot3.2.3+ShardingSphere5.5.0,线上还有使用老版本,旧文章不做删除,放在最后 。
快捷目录
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 5.5.0
2.1 介绍
ShardingSphere-JDBC自身定义为轻量级Java框架,可在Java JDBC层提供额外的服务。客户端直接连接到数据库时,它以jar形式提供服务,并且不需要额外的部署。
2.2 项目详细
- pom.xml
本文基于JDK21,Springboot-3.2.3,Mybatis,shardingsphere-5.5.0
//主依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<!--Mysql连接-->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<!--shardingsphere-jdbc-->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc</artifactId>
<version>5.5.0</version>
</dependency>
orm我这里用的是mybatis。因为用的时候github已经有5.5.0版本但是maven仓库还没有,旧版本中间遇到一些问题,就从github拉的镜像打的5.5.0版本。后续可以先优先看官方版本。
- 表结构,基础创建sql,其他表名自己修改
CREATE TABLE `shard_test0` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
`name` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
`create_tiime` datetime(3) DEFAULT CURRENT_TIMESTAMP(3),
`update_time` datetime(3) DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
主库:shard_demo 从库:shard_demo1,shard_demo2
读写表测试:shard_test0(user_id,name)
分库测试:user_info(user_id,name),分表形同分库,文章就不贴了
全局表:user_mobile(user_id,mobile)
- 准备如下sql
<?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.example.demo.mapper.ShardMapper">
<!--分表插入-->
<insert id="insertByUserId" parameterType="Map">
insert into shard_test0(`user_id`,`name`) values (#{user_id},unix_timestamp())
</insert>
<!--分表查询-->
<select id="selectByUserId" parameterType="Integer" resultType="Map">
select * from shard_test0 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="selectUserByUserId" 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>
</mapper>
- 查询和插入的代码先贴这里
@GetMapping("select")
public Object select(@RequestParam Integer userId) {
//强制读主库
// HintManager hintManager = HintManager.getInstance();
// hintManager.setWriteRouteOnly();
// 读写分离
// Map<String, Object> objectMap = shardMapper.selectByUserId(userId);
// hintManager.close();
//分表分库 全局表
Map<String, Object> objectMap = shardMapper.selectUserByUserId(userId);
return objectMap;
}
// @Transactional
@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);
//分表分库
shardMapper.insertUserByUserId(resMap);
// 全局表
shardMapper.insertMobileByUserId(resMap);
// if (i == 2) i = 1 / 0; //@Transactional 默认多个库生效
}
return 1;
}
2.3 默认yaml配置
- 配置看的是官网的,不是同版本的需自行查看文档
server:
port: 12233
mybatis:
mapper-locations: classpath:mapper/*/*.xml
executor-type: SIMPLE
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
spring:
jackson:
default-property-inclusion: non_null
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
datasource:
#指定驱动
driver-class-name: org.apache.shardingsphere.driver.ShardingSphereDriver
#指向新的yaml配置文件
url: jdbc:shardingsphere:classpath:config-shard.yaml
- 相应的配置新建一个配置文件
config-shard.yaml
,相应配置全部写在哪个配置文件,这里贴一下通用的配置
mode:
type: Standalone
repository:
type: JDBC
#数据源逻辑名称,读写分离时需要用到
dataSources:
ds0:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://192.168.168.199:3306/shard_demo
username: root
password: 123456
hikari:
# 最小连接
minimum-idle: 5
# 最大连接数
maximum-pool-size: 30
# 获取连接超时时间
connection-timeout: 5000
# 空闲连接最大存活的时间
idle-timeout: 60000
# 连接最大存活时间
max-lifetime: 120000
# 连接有效性检测时间
validation-timeout: 60000
# 检测连接是否有效
connection-test-query: SELECT 1
ds1:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://192.168.168.199:3306/shard_demo1
username: root
password: 123456
ds2:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://192.168.168.199:3306/shard_demo2
username: root
password: 123456
rules:
- !SINGLE
tables:
- "*.*" #加载指定单表
# 默认数据源,仅在执行 CREATE TABLE 创建单表时有效。缺失值为空,表示随机单播路由。
defaultDataSource: ds0
props:
sql-show: true #开启日志
接下来写的全是rules
下的内容。
2.4 单库分表
作用:三个库中的同一张表,通过取模的方式把数据落到不同的库中。官网直通车
- shardingsphere分表分库配置
rules:
- !SHARDING
tables:
user_info:
actualDataNodes: ds$->{0..2}.user_info
# databaseStrategy: #分库策略
# standard:
# shardingColumn: user_id
# shardingAlgorithmName: database_inline
# tableStrategy: #分表策略
# standard:
# shardingColumn: user_id
# shardingAlgorithmName: user_info_inline
defaultDatabaseStrategy: # 默认库分片策略
standard:
shardingColumn: user_id
shardingAlgorithmName: default_inline
# defaultTableStrategy: # 默认表分片策略
# defaultKeyGenerateStrategy: # 默认的分布式序列策略
# defaultShardingColumn: # 默认分片列名称
shardingAlgorithms: #分片算法
default_inline:
type: INLINE
props:
algorithm-expression: ds$->{user_id % 3}
database_inline:
type: INLINE
props:
algorithm-expression: ds$->{user_id % 3}
# keyGenerators: #分布式序列算法配置
# snowflake:
# type: SNOWFLAKE
# auditors: #分片审计算法配置
# sharding_key_required_auditor:
# type: DML_SHARDING_CONDITIONS
- 分别打开查询和插入方法中这两行代码,测试插入和查询日志
//分表分库 全局表
Map<String, Object> objectMap = shardMapper.selectUserByUserId(userId);
//分表分库
shardMapper.insertUserByUserId(resMap);
- 输出日志
//插入日志 根据取模分布到三个库了
Actual SQL: ds1 ::: insert into user_info(`user_id`,`name`) values (?, unix_timestamp()) ::: [7]
Actual SQL: ds2 ::: insert into user_info(`user_id`,`name`) values (?, unix_timestamp()) ::: [8]
Actual SQL: ds0 ::: insert into user_info(`user_id`,`name`) values (?, unix_timestamp()) ::: [9]
//查询日志 分别查1,2,3
Actual SQL: ds1 ::: select * from user_info u
left join user_mobile um on u.user_id=um.user_id
where u.user_id=? ::: [1]
Actual SQL: ds2 ::: select * from user_info u
left join user_mobile um on u.user_id=um.user_id
where u.user_id=? ::: [2]
Actual SQL: ds0 ::: select * from user_info u
left join user_mobile um on u.user_id=um.user_id
where u.user_id=? ::: [3]
通过日志是可以看到能够正常分片插入和分片查询的,功能测试正常。
2.5 广播表(全局表)
作用:全局唯一ID,业务配置冗余,或者是多个库的关联表,插入的数据均分布到多个库中
#数据源配置省略
rules:
- !BROADCAST
tables: # 广播表规则列表
- user_mobile
- 打开这个代码
// 全局表
shardMapper.insertMobileByUserId(resMap);
- 测试日志输出
//插入日志
Actual SQL: ds0 ::: insert into user_mobile(`user_id`,`mobile`) values (?,unix_timestamp()) ::: [9]
Actual SQL: ds2 ::: insert into user_mobile(`user_id`,`mobile`) values (?,unix_timestamp()) ::: [9]
Actual SQL: ds1 ::: insert into user_mobile(`user_id`,`mobile`) values (?,unix_timestamp()) ::: [9]
//查询日志输出
这里不贴了,旧文章有
可以看到数据会往两个数据源都插入一份,能够正常关联查询(分片查询实测),而单表查询应该是基于随机算法。
2.6 读写分离
作用:利用数据库的主从复制实现读写分离,提高数据库读取性能,一主多从和多主多从
- yaml配置,ds0是写库,ds1、ds2是读库
rules:
- !SINGLE
tables:
- "*.*" #加载指定单表
# 默认数据源,仅在执行 CREATE TABLE 创建单表时有效。缺失值为空,表示随机单播路由。
defaultDataSource: ds0
- !READWRITE_SPLITTING #读写分离规则
dataSources:
readwrite_ds: #读写分离逻辑数据源名称
writeDataSourceName: ds0 #写库
readDataSourceNames: #从库
- ds1
- ds2
# 事务内读请求的路由策略,可选值:PRIMARY(路由至主库)、FIXED(同一事务内路由至固定数据源)、DYNAMIC(同一事务内路由至非固定数据源)。默认值:DYNAMIC
transactionalReadQueryStrategy: PRIMARY
loadBalancerName: load_name #指定均衡器名称
loadBalancers:
load_name: #负载均衡算法名称 random round_robin轮询 weight权重
type: round_robin
# props:
# ds1: 9 #权重配置
# ds2: 1
- 打开这两行测试代码
Map<String, Object> objectMap = shardMapper.selectByUserId(userId);
shardMapper.insertByUserId(resMap);
- 测试日志输出
//查询日志
Actual SQL: ds2 ::: select * from shard_test0 where user_id=? ::: [3]
Actual SQL: ds1 ::: select * from shard_test0 where user_id=? ::: [4]
//插入日志
Actual SQL: ds0 ::: insert into shard_test0(`user_id`,`name`) values (?,unix_timestamp()) ::: [8]
Actual SQL: ds0 ::: insert into shard_test0(`user_id`,`name`) values (?,unix_timestamp()) ::: [9]
读写分离实测OK,查询默认是轮询算法,如需配置随机算法可参考按需配置。
- 打开下面代码,强制路由(也支持分片,有实际用途的可查官网)
//读写分离 强制读主库
HintManager hintManager = HintManager.getInstance();
hintManager.setWriteRouteOnly();
Map<String, Object> objectMap = shardMapper.selectByUserId(userId);
hintManager.close();
- 测试日志
Actual SQL: ds0 ::: select * from shard_test0 where user_id=? ::: [5]
Actual SQL: ds0 ::: select * from shard_test0 where user_id=? ::: [6]
实测可强制读取主库,其他的还实测了分片插入的事务是正常生效的 ,其他按需就自行去测试吧。
2.7 贴下完整配置
- 默认的yaml
server:
port: 12233
mybatis:
mapper-locations: classpath:mapper/*/*.xml
executor-type: SIMPLE
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
#https://shardingsphere.apache.org/document/current/cn/user-manual/shardingsphere-jdbc/yaml-config/rules/sharding/
spring:
jackson:
default-property-inclusion: non_null
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
datasource:
driver-class-name: org.apache.shardingsphere.driver.ShardingSphereDriver
url: jdbc:shardingsphere:classpath:config-shard.yaml
#这块是测试mybatis的
# driver-class-name: com.mysql.cj.jdbc.Driver
# url: jdbc:mysql://192.168.168.199:3306/shard_demo
# username: root
# password: 123456
config-shard.yaml
mode:
type: Standalone
repository:
type: JDBC
#数据源逻辑名称
dataSources:
ds0:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://192.168.168.199:3306/shard_demo
username: root
password: 123456
hikari:
# 最小连接
minimum-idle: 5
# 最大连接数
maximum-pool-size: 30
# 获取连接超时时间
connection-timeout: 5000
# 空闲连接最大存活的时间
idle-timeout: 60000
# 连接最大存活时间
max-lifetime: 120000
# 连接有效性检测时间
validation-timeout: 60000
# 检测连接是否有效
connection-test-query: SELECT 1
ds1:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://192.168.168.199:3306/shard_demo1
username: root
password: 123456
ds2:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://192.168.168.199:3306/shard_demo2
username: root
password: 123456
rules:
- !SINGLE
tables:
- "*.*" #加载指定单表
# 默认数据源,仅在执行 CREATE TABLE 创建单表时有效。缺失值为空,表示随机单播路由。
defaultDataSource: ds0
- !READWRITE_SPLITTING #读写分离规则
dataSources:
readwrite_ds: #读写分离逻辑数据源名称
writeDataSourceName: ds0 #写库
readDataSourceNames: #从库
- ds1
- ds2
# 事务内读请求的路由策略,可选值:PRIMARY(路由至主库)、FIXED(同一事务内路由至固定数据源)、DYNAMIC(同一事务内路由至非固定数据源)。默认值:DYNAMIC
transactionalReadQueryStrategy: PRIMARY
loadBalancerName: load_name #指定均衡器名称
loadBalancers:
load_name: #负载均衡算法名称 random round_robin轮询 weight权重
type: round_robin
# props:
# ds1: 9 #权重配置
# ds2: 1
# #分表分库 https://shardingsphere.apache.org/document/current/cn/user-manual/shardingsphere-jdbc/yaml-config/rules/sharding/
# - !SHARDING
# tables:
# user_info:
# actualDataNodes: ds$->{0..2}.user_info
## databaseStrategy: #分库策略
## standard:
## shardingColumn: user_id
## shardingAlgorithmName: database_inline
## tableStrategy: #分表策略
## standard:
## shardingColumn: user_id
## shardingAlgorithmName: user_info_inline
# defaultDatabaseStrategy: # 默认库分片策略
# standard:
# shardingColumn: user_id
# shardingAlgorithmName: default_inline
## defaultTableStrategy: # 默认表分片策略
## defaultKeyGenerateStrategy: # 默认的分布式序列策略
## defaultShardingColumn: # 默认分片列名称
# shardingAlgorithms: #分片算法
# default_inline:
# type: INLINE
# props:
# algorithm-expression: ds$->{user_id % 3}
# database_inline:
# type: INLINE
# props:
# algorithm-expression: ds$->{user_id % 3}
## keyGenerators: #分布式序列算法配置
## snowflake:
## type: SNOWFLAKE
## auditors: #分片审计算法配置
## sharding_key_required_auditor:
## type: DML_SHARDING_CONDITIONS
#
# - !BROADCAST
# tables: # 广播表规则列表
# - user_mobile
props:
sql-show: true
# @Transactional 默认多个库生效 如配置其他的事务方式需要引入shardingsphere-transaction-xa-core
#transaction:
# defaultType: XA # 事务模式,可选值 LOCAL/XA/BASE
# providerType: Atomikos #XA/BASE 指定模式下的具体实现
3.总结
总体来说用起来还是比较舒服的,完美的实现了分库分表,读写分离。
这里贴一下要注意的问题点:
1.有些配置里面的<table-name>
是一定要写对应的表名的,有些这种配置就可以随便定义
2.分片键配置的行内表达式如果有比较复杂的,可手写代码实现分片规则
3.读延迟问题
,可在读之前执行HintManager.getInstance().setWriteRouteOnly();
这个代码强制读主库,但是如果还有后续查询记得清除线程变量。
总结一下和mycat的区别:
SharingSphere-JDBC | Mycat | |
---|---|---|
工作层面 | JDBC协议,接口封装 | Mysql协议,JDBC协议 |
运行方式 | Jar包,客户端 | 独立服务,服务端 |
开发方式 | 代码,配置 | 数据量连接地址 |
运维方式 | 无 | 运维单独维护 |
性能 | 多线程并发 | 独立服务,需要看服务器配置 |
支持语言 | 仅支持Java | 支持JDBC协议的语言 |
2.老版本ShardingSphere-JDBC 5.1.5
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 #要有这个 不然启动报错
以上就是本章的全部内容了。
上一篇:mysql第九话 - mysql主从复制集群实现
下一篇:通信框架之Netty第一话 - NIO的超神发展之路
天行健,君子以自强不息
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)