Apache ShardingSphere 由 JDBC、Proxy 和 Sidecar(规划中)这 3 款既能够独立部署,又支持混合部署配合使用的产品组成。 它们均提供标准化的基于数据库作为存储节点的增量功能,可适用于如 Java 同构、异构语言、云原生等各种多样化的应用场景。
关系型数据库当今依然占有巨大市场份额,是企业核心系统的基石,未来也难于撼动,我们更加注重在原有基础上提供增量,而非颠覆。
Sharding-JDBC
定位为轻量级 Java 框架,在 Java 的 JDBC 层提供的额外服务。 它使用客户端直连数据库,以 jar 包形式提供服务,无需额外部署和依赖,可理解为增强版的 JDBC 驱动,完全兼容 JDBC 和各种 ORM 框架。
- 适用于任何基于 JDBC 的 ORM 框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template 或直接使用 JDBC;
- 支持任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, HikariCP 等;
- 支持任意实现 JDBC 规范的数据库,目前支持 MySQL,PostgreSQL,Oracle,SQLServer 以及任何可使用 JDBC 访问的数据库。
ShardingSphere-JDBC 主要做两个功能:
- 数据分片
- 读写分离
整合Spring Boot
项目采用Spring Boot + MybatisPlus + Sharding-JDBC +Druid 连接池
依赖
<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>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.8</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.19</version>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.0.0-RC1</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
水平拆分
- 创建数据库 test_jdbc
- 在数据库创建两张表 test_standard_1,test_standard_2
- 约定规则:test_standard_2存储ID为奇数的数据,test_standard_1存入ID为偶数的数据
- 配置 Sharding-JDBC 分片策略,官网地址
注意一点,sharding-jdbc 版本不一样,配置也是有很大区别的,我这里是 4.0.0-RC1,大家要看 4.0的文档
建表语句
CREATE TABLE `test_standard_1` (
`id` bigint NOT NULL,
`name` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
application.properties配置
我见有的人说不支持 yml 格式,所以注意下
# 配置 sharding-jdbc 分片策略
# 配置数据源,起一个别名,可以有多个数据源用 都好分割
spring.shardingsphere.datasource.names=m1
# 配置数据源的驱动\连接池\账号密码等
spring.shardingsphere.datasource.m1.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m1.url=jdbc:mysql://192.168.81.104:3306/test_jdbc?serverTimezone=GMT%2B8
spring.shardingsphere.datasource.m1.username=dev_fqr
spring.shardingsphere.datasource.m1.password=Dev@fqr2021
# 指定 standard 表分布情况,配置表在哪个数据源(数据库)里面,表名称都是什么,即 m1.standard_1 m1.standard_2
spring.shardingsphere.sharding.tables.test_standard.actual-data-nodes=m1.test_standard_$->{1..2}
# 分布式序列策略配置
# 指定 standard 这张表里面主键的生成策略
spring.shardingsphere.sharding.tables.test_standard.key-generator.column=id
# 指定算法名称
spring.shardingsphere.sharding.tables.test_standard.key-generator.type=SNOWFLAKE
# 指定分片策略 约定ID值 奇数添加到standard_2 偶数添加到standard_1
spring.shardingsphere.sharding.tables.test_standard.table-strategy.inline.sharding-column=id
# 会拿雪花ID % 服务数量 ,但是总会得到 0 | 1,所以会找 test_standard_0 或 test_standard_1,+1 解决这样的问题
spring.shardingsphere.sharding.tables.test_standard.table-strategy.inline.algorithm-expression=test_standard_$->{id % 2 + 1}
# 打开SQL输出日志
spring.shardingsphere.props.sql.show=true
spring.main.allow-bean-definition-overriding=true
测试类
这里 就不写 TestStandardMapper 和 TestStandard 实体类了
@SpringBootTest
class ShardingJdbcApplicationTests {
@Resource
public TestStandardMapper testStandardMapper;
@Test
void insert() {
for(int i=0;i<10;i++) {
TestStandard testStandard = new TestStandard();
testStandard.setName("张三"+i);
testStandardMapper.insert(testStandard);
}
}
@Test
void selectByID() {
QueryWrapper<TestStandard> wrapper = new QueryWrapper<>();
// 你的ID是什么类型这里就一定要是什么类型,否则会报错
wrapper.eq("id",1469257890131025922L);
TestStandard testStandard = testStandardMapper.selectOne(wrapper);
System.out.println(testStandard);
}
@Test
void selectByName() {
QueryWrapper<TestStandard> wrapper = new QueryWrapper<>();
// 查询别的字段回去两个表都查
wrapper.eq("name","张三3");
TestStandard testStandard = testStandardMapper.selectOne(wrapper);
System.out.println(testStandard);
}
@Test
void selectInID() {
QueryWrapper<TestStandard> wrapper = new QueryWrapper<>();
// 会对所有ID值进行取余得到具体去哪个表查
wrapper.in("id",1469263053449306114L,1469263055173165058L);
List<TestStandard> testStandard = testStandardMapper.selectList(wrapper);
System.out.println(testStandard);
}
}
水平分库并水平分表
- 创建两个数据库 test_jdbc1,test_jdbc2
- 在这两个数据库都创建 test_standard_1,test_standard_2两张表
- 约定规则:
3.1. user_id 为偶数则添加到 test_jdbc1 库中,为奇数添加到 test_jdbc2 库中
3.2. id 为偶数添加到 test_standard_1 表,为奇数添加到 test_standard_2 表
表结构
CREATE TABLE `test_standard_1` (
`id` BIGINT NOT NULL,
`user_id` BIGINT DEFAULT NULL,
`name` VARCHAR(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
配置
# 配置 sharding-jdbc 分片策略
# 配置数据源,起一个别名,可以有多个数据源用 都好分割
spring.shardingsphere.datasource.names=m1,m2
# 配置数据源的驱动\连接池\账号密码等
spring.shardingsphere.datasource.m1.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m1.url=jdbc:mysql://192.168.81.104:3306/test_jdbc1?serverTimezone=GMT%2B8
spring.shardingsphere.datasource.m1.username=dev_fqr
spring.shardingsphere.datasource.m1.password=Dev@fqr2021
# 配置数据源的驱动\连接池\账号密码等
spring.shardingsphere.datasource.m2.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m2.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m2.url=jdbc:mysql://192.168.81.104:3306/test_jdbc2?serverTimezone=GMT%2B8
spring.shardingsphere.datasource.m2.username=dev_fqr
spring.shardingsphere.datasource.m2.password=Dev@fqr2021
# 指定 数据库的分布情况 以及 表的分布情况
spring.shardingsphere.sharding.tables.test_standard.actual-data-nodes=m$->{1..2}.test_standard_$->{1..2}
# 分布式序列策略配置
# 指定 standard 这张表里面主键的生成策略
spring.shardingsphere.sharding.tables.test_standard.key-generator.column=id
# 指定算法名称
spring.shardingsphere.sharding.tables.test_standard.key-generator.type=SNOWFLAKE
# 指定表分片策略 约定ID值 奇数添加到standard_1 偶数添加到standard_2
spring.shardingsphere.sharding.tables.test_standard.table-strategy.inline.sharding-column=id
# 会拿雪花ID % 服务数量 ,但是总会得到 0 | 1,所以会找 test_standard_0 或 test_standard_1
spring.shardingsphere.sharding.tables.test_standard.table-strategy.inline.algorithm-expression=test_standard_$->{id % 2 + 1}
# 指定数据库分片策略,根据user_id做判断,偶数添加到m1,奇数添加到m2,default-database-strategy=默认所有表都按这样的规则
#spring.shardingsphere.sharding.default-database-strategy.inline.sharding-column=user_id
#spring.shardingsphere.sharding.default-database-strategy.inline.algorithm-expression=m$->{user_id % 2 + 1}
# 对指定表做分库规则
spring.shardingsphere.sharding.tables.test_standard.database-strategy.inline.sharding-column=user_id
spring.shardingsphere.sharding.tables.test_standard.database-strategy.inline.algorithm-expression=m$->{user_id % 2 + 1}
# 打开SQL输出日志
spring.shardingsphere.props.sql.show=true
spring.main.allow-bean-definition-overriding=true
测试
@SpringBootTest
class ShardingJdbcApplicationTests {
@Resource
public TestStandardMapper testStandardMapper;
@Test
void insert() {
for(int i=0;i<10;i++) {
TestStandard testStandard = new TestStandard();
testStandard.setName("张三"+i);
testStandard.setUserId(Math.round(Math.random()*100));
testStandardMapper.insert(testStandard);
}
}
@Test
void selectByID() {
QueryWrapper<TestStandard> wrapper = new QueryWrapper<>();
// 会查询两个数据源并直接命中到跟ID取余的表,找到数据
wrapper.eq("id",1470229287665823746L);
TestStandard testStandard = testStandardMapper.selectOne(wrapper);
System.out.println(testStandard);
}
@Test
void selectByUserID() {
QueryWrapper<TestStandard> wrapper = new QueryWrapper<>();
// 会查询跟user_id取余的库,并把库中要找的表都查出来,找数据在哪里
wrapper.eq("user_id",66);
TestStandard testStandard = testStandardMapper.selectOne(wrapper);
System.out.println(testStandard);
}
@Test
void selectByName() {
QueryWrapper<TestStandard> wrapper = new QueryWrapper<>();
// 会查询两个数据源,并且查询所有的表,找到张三3这个用户
wrapper.eq("name","张三3");
TestStandard testStandard = testStandardMapper.selectOne(wrapper);
System.out.println(testStandard);
}
@Test
void selectInID() {
QueryWrapper<TestStandard> wrapper = new QueryWrapper<>();
// 会对所有ID值进行取余得到具体去哪个表查
wrapper.in("id",1469263053449306114L,1469263055173165058L);
List<TestStandard> testStandard = testStandardMapper.selectList(wrapper);
System.out.println(testStandard);
}
}
垂直分库
- 创建两个不同的数据库 user_db,order_db
- 在user_db库里面创建 user_info,order_db 库里面创建order_info表
垂直分库一般适用于微服务,一个业务连一个库,我这里有点多数据源的意思。
配置
# 配置 sharding-jdbc 分片策略
# 配置数据源,起一个别名,可以有多个数据源用 都好分割
spring.shardingsphere.datasource.names=m1,m2
# 配置数据源的驱动\连接池\账号密码等
spring.shardingsphere.datasource.m1.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m1.url=jdbc:mysql://192.168.81.104:3306/user_db?serverTimezone=GMT%2B8
spring.shardingsphere.datasource.m1.username=dev_fqr
spring.shardingsphere.datasource.m1.password=Dev@fqr2021
# 配置数据源的驱动\连接池\账号密码等
spring.shardingsphere.datasource.m2.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m2.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m2.url=jdbc:mysql://192.168.81.104:3306/order_db?serverTimezone=GMT%2B8
spring.shardingsphere.datasource.m2.username=dev_fqr
spring.shardingsphere.datasource.m2.password=Dev@fqr2021
# 针对 user_db 库的某些表进行特殊处理
# 指定 数据库的分布情况 以及 表的分布情况
spring.shardingsphere.sharding.tables.user_info.actual-data-nodes=m$->{1}.user_info
# 分布式序列策略配置
# 指定 standard 这张表里面主键的生成策略
spring.shardingsphere.sharding.tables.user_info.key-generator.column=id
# 指定算法名称
spring.shardingsphere.sharding.tables.user_info.key-generator.type=SNOWFLAKE
# 指定表分片策略 约定ID值
spring.shardingsphere.sharding.tables.user_info.table-strategy.inline.sharding-column=id
spring.shardingsphere.sharding.tables.user_info.table-strategy.inline.algorithm-expression=user_info
# 针对order_db 库的某些表进行特殊处理
# 指定 数据库的分布情况 以及 表的分布情况
spring.shardingsphere.sharding.tables.order_info.actual-data-nodes=m$->{2}.order_info
# 分布式序列策略配置
# 指定 standard 这张表里面主键的生成策略
spring.shardingsphere.sharding.tables.order_info.key-generator.column=id
# 指定算法名称
spring.shardingsphere.sharding.tables.order_info.key-generator.type=SNOWFLAKE
# 指定表分片策略 约定ID值
spring.shardingsphere.sharding.tables.order_info.table-strategy.inline.sharding-column=id
spring.shardingsphere.sharding.tables.order_info.table-strategy.inline.algorithm-expression=order_info
# 打开SQL输出日志
spring.shardingsphere.props.sql.show=true
spring.main.allow-bean-definition-overriding=true
测试
注意,这里记得要给实体类上加 @Table 注明实体类与哪张表有关系,否则插入报错
@SpringBootTest
class ShardingJdbcApplicationTests {
@Resource
public UserInfoMapper userInfoMapper;
@Resource
public OrderInfoMapper orderInfoMapper;
@Test
void insert() {
for(int i=0;i<10;i++) {
UserInfo userInfo = new UserInfo();
userInfo.setName("用户"+i);
OrderInfo orderInfo = new OrderInfo();
orderInfo.setName("用户"+i);
orderInfoMapper.insert(orderInfo);
userInfoMapper.insert(userInfo);
}
}
}
全局表(公共表)
- 存储固定数据的表,表数据很少发生变化,查询时候经常进行关联
- 在每个数据库中创建出相同结构的公共表
- 公共表的数据要添加都添加,要删除都删除
- 在 user_db,order_db中创建公共表,public_info
配置
# 配置 sharding-jdbc 分片策略
# 配置数据源,起一个别名,可以有多个数据源用 都好分割
spring.shardingsphere.datasource.names=m1,m2
# 配置数据源的驱动\连接池\账号密码等
spring.shardingsphere.datasource.m1.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m1.url=jdbc:mysql://192.168.81.104:3306/user_db?serverTimezone=GMT%2B8
spring.shardingsphere.datasource.m1.username=dev_fqr
spring.shardingsphere.datasource.m1.password=Dev@fqr2021
# 配置数据源的驱动\连接池\账号密码等
spring.shardingsphere.datasource.m2.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m2.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m2.url=jdbc:mysql://192.168.81.104:3306/order_db?serverTimezone=GMT%2B8
spring.shardingsphere.datasource.m2.username=dev_fqr
spring.shardingsphere.datasource.m2.password=Dev@fqr2021
# 针对 user_db 库的某些表进行特殊处理
# 指定 数据库的分布情况 以及 表的分布情况
spring.shardingsphere.sharding.tables.user_info.actual-data-nodes=m$->{1}.user_info
# 分布式序列策略配置
# 指定 standard 这张表里面主键的生成策略
spring.shardingsphere.sharding.tables.user_info.key-generator.column=id
# 指定算法名称
spring.shardingsphere.sharding.tables.user_info.key-generator.type=SNOWFLAKE
# 指定表分片策略 约定ID值
spring.shardingsphere.sharding.tables.user_info.table-strategy.inline.sharding-column=id
spring.shardingsphere.sharding.tables.user_info.table-strategy.inline.algorithm-expression=user_info
# 针对order_db 库的某些表进行特殊处理
# 指定 数据库的分布情况 以及 表的分布情况
spring.shardingsphere.sharding.tables.order_info.actual-data-nodes=m$->{2}.order_info
# 分布式序列策略配置
# 指定 standard 这张表里面主键的生成策略
spring.shardingsphere.sharding.tables.order_info.key-generator.column=id
# 指定算法名称
spring.shardingsphere.sharding.tables.order_info.key-generator.type=SNOWFLAKE
# 指定表分片策略 约定ID值
spring.shardingsphere.sharding.tables.order_info.table-strategy.inline.sharding-column=id
spring.shardingsphere.sharding.tables.order_info.table-strategy.inline.algorithm-expression=order_info
# 配置公共表
spring.shardingsphere.sharding.broadcast-tables=public_info
# 指定 standard 这张表里面主键的生成策略
spring.shardingsphere.sharding.tables.public_info.key-generator.column=id
# 指定算法名称
spring.shardingsphere.sharding.tables.public_info.key-generator.type=SNOWFLAKE
# 打开SQL输出日志
spring.shardingsphere.props.sql.show=true
spring.main.allow-bean-definition-overriding=true
测试
@SpringBootTest
class ShardingJdbcApplicationTests {
@Resource
public PublicInfoMapper publicInfoMapper;
@Test
void insert() {
// 会往不同数据源相同表插入数据
for(int i=0;i<10;i++) {
PublicInfo publicInfo = new PublicInfo();
publicInfo.setName("公共数据"+i);
publicInfo.setForeignKey((long)i);
publicInfoMapper.insert(publicInfo);
}
}
@Test
void select() {
QueryWrapper<PublicInfo> wrapper = new QueryWrapper<>();
// 会随机从m1,m2 中读取数据
for(int i=0;i<10;i++) {
wrapper.eq("id", 1470262655996567553L);
PublicInfo publicInfo = publicInfoMapper.selectOne(wrapper);
System.out.println(publicInfo);
}
}
}
读写分离配置
Sharding-JDBC 会根据语义的不同,来区分主从的读写分离,如 update、insert 等走主库,select 走从库
# 配置 sharding-jdbc 分片策略
# 配置数据源,起一个别名,可以有多个数据源用 都好分割
spring.shardingsphere.datasource.names=m1,s1
# 配置数据源的驱动\连接池\账号密码等
spring.shardingsphere.datasource.m1.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m1.url=jdbc:mysql://192.168.81.104:3306/user_db?serverTimezone=GMT%2B8
spring.shardingsphere.datasource.m1.username=dev_fqr
spring.shardingsphere.datasource.m1.password=Dev@fqr2021
# 配置数据源的驱动\连接池\账号密码等
spring.shardingsphere.datasource.s1.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.s1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.s1.url=jdbc:mysql://192.168.81.104:3306/user_db?serverTimezone=GMT%2B8
spring.shardingsphere.datasource.s1.username=dev_fqr
spring.shardingsphere.datasource.s1.password=Dev@fqr2021
# 主从主要配置,多个从可以以','分割,ds0 是一个逻辑库,统一主从主体信息
spring.shardingsphere.sharding.master-slave-rules.ds0.master-data-source-name=m1
spring.shardingsphere.sharding.master-slave-rules.ds0.slave-data-source-names=s1
# 指定 表的分布情况
spring.shardingsphere.sharding.tables.user_info.actual-data-nodes=ds0.user_info
# 打开SQL输出日志
spring.shardingsphere.props.sql.show=true
spring.main.allow-bean-definition-overriding=true