Sharding-Sphere官网:http://shardingsphere.apache.org/index_zh.html
数据据库数据量是不可控的,随着时间和业务发展,造成表里面数据越来越多,如果再去对数据库表curd操作时,就会有性能问题。
解决办法:
分库分表有两种方式:垂直切分和水平切分。
垂直拆字段,水平拆记录。
垂直切分的库和表结构是不同的,而水平切分的库和表是相同的。
操作数据库中某张表,把这张表中一部分字段数据存到一张新表里面,再把这张表另部分字段数据存到另外一张表里面。
垂直拆分成的两张表,每张表的数据量和原先的表的数据量相同。
Sharding-JDBC是轻量级的Java框架,是增强的JDBC驱动。
Sharding-JDBC的功能:主要做数据分片和读写分离,不是做分库分表,分库分表由我们自己做。
主要的目的:简化分库分表后数据的相关操作。
技术:SpringBoot2.2.1 + MybatisPlus + Sharding-JDBC + Druid连接池
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druid-spring-boot-starterartifactId>
<version>1.1.20version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.18version>
dependency>
<dependency>
<groupId>org.apache.shardingspheregroupId>
<artifactId>sharding-jdbc-spring-boot-starterartifactId>
<version>4.0.0-RC1version>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.0.5version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
create database course_db;
use course_db;
create table course_1 (
`cid` bigint(20) primary key,
`cname` varchar(50) not null,
`user_id` bigint(20) not null,
`cstatus` varchar(10) not null
);
create table course_2 (
`cid` bigint(20) primary key,
`cname` varchar(50) not null,
`user_id` bigint(20) not null,
`cstatus` varchar(10) not null
);
@Data
public class Course {
private Long cid;
private String cname;
private Long userId;
private String cstatus;
}
@Repository
public interface CourseMapper extends BaseMapper<Course> {}
@MapperScan("com.angenin.shardingjdbcdemo.mapper")
注解官网数据分片配置:https://shardingsphere.apache.org/document/current/cn/user-manual/shardingsphere-jdbc/usage/sharding/spring-boot-starter/
在 application.properties 配置文件中配置:(下面数据库密码记得写)
# shardingjdbc 水平分表策略
# 配置数据源,给数据源起别名
spring.shardingsphere.datasource.names=m1
# 一个实体类对应两张表,覆盖
spring.main.allow-bean-definition-overriding=true
# 配置数据源的具体内容,包含连接池,驱动,地址,用户名,密码
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://localhost:3306/course_db?serverTimezone=GMT%2B8
spring.shardingsphere.datasource.m1.username=root
spring.shardingsphere.datasource.m1.password=
# 指定course表分布的情况,配置表在哪个数据库里,表的名称都是什么 m1.course_1,m1.course_2
spring.shardingsphere.sharding.tables.course.actual-data-nodes=m1.course_$->{1..2}
# 指定 course 表里面主键 cid 的生成策略 SNOWFLAKE
spring.shardingsphere.sharding.tables.course.key-generator.column=cid
spring.shardingsphere.sharding.tables.course.key-generator.type=SNOWFLAKE
# 配置分表策略 约定 cid 值偶数添加到 course_1 表,如果 cid 是奇数添加到 course_2 表
spring.shardingsphere.sharding.tables.course.table-strategy.inline.shardingcolumn=cid
spring.shardingsphere.sharding.tables.course.table-strategy.inline.algorithmexpression=course_$->{cid % 2 + 1}
# 打开 sql 输出日志
spring.shardingsphere.props.sql.show=true
在test包中的测试类中进行测试
@RunWith(SpringRunner.class)
@SpringBootTest
public class ShardingjdbcdemoApplicationTests {
@Autowired
private CourseMapper courseMapper;
//添加课程
@Test
public void addCourse(){
Course course = new Course();
//cid由我们设置的策略,雪花算法进行生成(至少70年内生成的id不会重复)
course.setCname("java");
course.setUserId(100L);
course.setCstatus("Normal");
courseMapper.insert(course);
}
//查询课程
@Test
public void findCourse(){
QueryWrapper<Course> wrapper = new QueryWrapper<>();
wrapper.eq("cid", 509755853058867201L);
courseMapper.selectOne(wrapper);
}
}
addCourse方法执行结果:
findCourse方法执行结果:
create database edu_db_1;
create database edu_db_2;
use edu_db_1;
create table course_1 (
`cid` bigint(20) primary key,
`cname` varchar(50) not null,
`user_id` bigint(20) not null,
`cstatus` varchar(10) not null
);
create table course_2 (
`cid` bigint(20) primary key,
`cname` varchar(50) not null,
`user_id` bigint(20) not null,
`cstatus` varchar(10) not null
);
use edu_db_2;
create table course_1 (
`cid` bigint(20) primary key,
`cname` varchar(50) not null,
`user_id` bigint(20) not null,
`cstatus` varchar(10) not null
);
create table course_2 (
`cid` bigint(20) primary key,
`cname` varchar(50) not null,
`user_id` bigint(20) not null,
`cstatus` varchar(10) not null
);
下面数据库的密码填自己的。
# shardingjdbc 水平分库分表策略
# 配置数据源,给数据源起别名
# 水平分库需要配置多个数据库
spring.shardingsphere.datasource.names=m1,m2
# 一个实体类对应两张表,覆盖
spring.main.allow-bean-definition-overriding=true
# 配置第一个数据源的具体内容,包含连接池,驱动,地址,用户名,密码
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://localhost:3306/edu_db_1?serverTimezone=GMT%2B8
spring.shardingsphere.datasource.m1.username=root
spring.shardingsphere.datasource.m1.password=
# 配置第二个数据源的具体内容,包含连接池,驱动,地址,用户名,密码
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://localhost:3306/edu_db_2?serverTimezone=GMT%2B8
spring.shardingsphere.datasource.m2.username=root
spring.shardingsphere.datasource.m2.password=
# 指定数据库分布的情况和数据表分布的情况
# m1 m2 course_1 course_2
spring.shardingsphere.sharding.tables.course.actual-data-nodes=m$->{1..2}.course_$->{1..2}
# 指定 course 表里面主键 cid 的生成策略 SNOWFLAKE
spring.shardingsphere.sharding.tables.course.key-generator.column=cid
spring.shardingsphere.sharding.tables.course.key-generator.type=SNOWFLAKE
# 指定分库策略 约定 user_id 值偶数添加到 m1 库,如果 user_id 是奇数添加到 m2 库
# 默认写法(所有的表的user_id)
#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}
# 指定只有course表的user_id
spring.shardingsphere.sharding.tables.course.database-strategy.inline.sharding-column=user_id
spring.shardingsphere.sharding.tables.course.database-strategy.inline.algorithm-expression=m$->{user_id % 2 + 1}
# 指定分表策略 约定 cid 值偶数添加到 course_1 表,如果 cid 是奇数添加到 course_2 表
spring.shardingsphere.sharding.tables.course.table-strategy.inline.sharding-column=cid
spring.shardingsphere.sharding.tables.course.table-strategy.inline.algorithm-expression=course_$->{cid % 2 + 1}
# 打开 sql 输出日志
spring.shardingsphere.props.sql.show=true
//添加课程
@Test
public void addCourseDb(){
Course course = new Course();
//cid由我们设置的策略,雪花算法进行生成(至少70年内生成的id不会重复)
course.setCname("javademo");
//分库根据user_id
course.setUserId(100L);
course.setCstatus("Normal");
courseMapper.insert(course);
course.setCname("javademo2");
course.setUserId(111L);
courseMapper.insert(course);
}
//查询课程
@Test
public void findCourseDb(){
QueryWrapper<Course> wrapper = new QueryWrapper<>();
//设置user_id的值
wrapper.eq("user_id", 100L);
//设置cid的值
wrapper.eq("cid", 509771111076986881L);
Course course = courseMapper.selectOne(wrapper);
System.out.println(course);
}
addCourseDb方法执行结果:
findCourseDb方法执行结果:
create database user_db;
use user_db;
create table t_user(
`user_id` bigint(20) primary key,
`username` varchar(100) not null,
`ustatus` varchar(50) not null
);
创建user实体类和对应的mapper
@Data
@TableName("t_user") //指定对应的表名
public class User {
private Long userId;
private String username;
private String ustatus;
}
@Repository
public interface UserMapper extends BaseMapper<User> {}
记得写数据库密码。
# shardingjdbc 垂直分库策略
# 配置数据源,给数据源起别名
# 水平分库需要配置多个数据库
# m0为用户数据库
spring.shardingsphere.datasource.names=m1,m2,m0
# 一个实体类对应两张表,覆盖
spring.main.allow-bean-definition-overriding=true
# 配置第一个数据源的具体内容,包含连接池,驱动,地址,用户名,密码
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://localhost:3306/edu_db_1?serverTimezone=GMT%2B8
spring.shardingsphere.datasource.m1.username=root
spring.shardingsphere.datasource.m1.password=
# 配置第二个数据源的具体内容,包含连接池,驱动,地址,用户名,密码
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://localhost:3306/edu_db_2?serverTimezone=GMT%2B8
spring.shardingsphere.datasource.m2.username=root
spring.shardingsphere.datasource.m2.password=
# 配置user数据源的具体内容,包含连接池,驱动,地址,用户名,密码
spring.shardingsphere.datasource.m0.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m0.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m0.url=jdbc:mysql://localhost:3306/user_db?serverTimezone=GMT%2B8
spring.shardingsphere.datasource.m0.username=root
spring.shardingsphere.datasource.m0.password=
# 配置user_db数据库里面t_user 专库专表
spring.shardingsphere.sharding.tables.t_user.actual-data-nodes=m0.t_user
# 配置主键的生成策略
spring.shardingsphere.sharding.tables.t_user.key-generator.column=user_id
spring.shardingsphere.sharding.tables.t_user.key-generator.type=SNOWFLAKE
# 指定分表策略
spring.shardingsphere.sharding.tables.t_user.table-strategy.inline.sharding-column=user_id
spring.shardingsphere.sharding.tables.t_user.table-strategy.inline.algorithm-expression=t_user
# ... 其他配置同上
//添加用户
@Test
public void addUserDb(){
User user = new User();
user.setUsername("张三");
user.setUstatus("a");
userMapper.insert(user);
}
@Test
//查询用户
public void findUserDb(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("user_id", 509793334663839745L);
User user = userMapper.selectOne(wrapper);
System.out.println(user);
}
addUserDb方法执行结果:
findUserDb方法执行结果:
use user_db;
#use edu_db_1;
#use edu_db_2;
create table t_udict(
`dictid` bigint(20) primary key,
`ustatus` varchar(100) not null,
`uvalue` varchar(100) not null
);
# 其他配置同上
# ...
# 公共表配置
spring.shardingsphere.sharding.broadcast-tables=t_udict
# 配置主键的生成策略
spring.shardingsphere.sharding.tables.t_udict.key-generator.column=dictid
spring.shardingsphere.sharding.tables.t_udict.key-generator.type=SNOWFLAKE
# ...
@Data
@TableName(value = "t_udict")
public class Udict {
private Long dictid;
private String ustatus;
private String uvalue;
}
@Repository
public interface UdictMapper extends BaseMapper<Udict> {}
//添加
@Test
public void addDict(){
Udict udict = new Udict();
udict.setUstatus("a");
udict.setUvalue("已启用");
udictMapper.insert(udict);
}
//删除
@Test
public void deleteDict(){
QueryWrapper<Udict> wrapper = new QueryWrapper<>();
wrapper.eq("dictid", 509811689974136833L);
udictMapper.delete(wrapper);
}
addDict方法执行结果:
deleteDict方法执行结果:
Sharding-JDBC通过sql语句语义分析,当sql语句有insert、update、delete时,Sharding-JDBC就把这次操作在主数据库上执行;当sql语句有select时,就会把这次操作在从数据库上执行,从而实现读写分离过程。但Sharding-JDBC并不会做数据同步,数据同步是配置MySQL后由MySQL自己完成的。
我采用的是docker来实现的,单独写在这篇文章里:https://blog.csdn.net/qq_36903261/article/details/108457759
使用docker后,需要在主服务器上新建原先的数据库数据表。
因为记录的文件名以及位点每次重启或刷新都会改变,所以以下命令放在这里,方便查看。
主mysql:
#确认位点 记录下文件名以及位点(重启或者刷新都会改变)
show master status;
从mysql:
#先停止同步
STOP SLAVE;
#修改从库指向到主库,使用上一步记录的文件名以及位点
# master_host docker容器linux的ip地址
# master_port 主mysql暴露的端口
# master_user 主mysql的用户名
# master_password 主mysql的密码
#(最后两项修改成刚刚从主mysql查到的,主mysql每次刷新权限或者重启时,这两个值都会改变,所以每次都需要查看是否相同)
CHANGE MASTER TO
master_host = '10.211.55.26',
master_port = 33060,
master_user = 'db_sync',
master_password = 'db_sync',
master_log_file = 'mysql-bin.000001',
master_log_pos = 823;
#启动同步
START SLAVE;
#查看Slave_IO_Runing和Slave_SQL_Runing字段值都为Yes,表示同步配置成功。
show slave status \G;
也使用docker的,复制的时候记得改一下ip地址
# 配置数据源,给数据源起别名
# m0为用户数据库
spring.shardingsphere.datasource.names=m0,s0
# 一个实体类对应两张表,覆盖
spring.main.allow-bean-definition-overriding=true
#user_db 主服务器
spring.shardingsphere.datasource.m0.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m0.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m0.url=jdbc:mysql://10.211.55.26:33060/user_db?serverTimezone=GMT%2B8
spring.shardingsphere.datasource.m0.username=root
spring.shardingsphere.datasource.m0.password=123456
#user_db 从服务器
spring.shardingsphere.datasource.s0.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.s0.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.s0.url=jdbc:mysql://10.211.55.26:33061/user_db?serverTimezone=GMT%2B8
spring.shardingsphere.datasource.s0.username=root
spring.shardingsphere.datasource.s0.password=123456
# 主库从库逻辑数据源定义 ds0 为 user_db
spring.shardingsphere.sharding.master-slave-rules.ds0.master-data-source-name=m0
spring.shardingsphere.sharding.master-slave-rules.ds0.slave-data-source-names=s0
# 配置user_db数据库里面t_user 专库专表
#spring.shardingsphere.sharding.tables.t_user.actual-data-nodes=m0.t_user
# t_user 分表策略,固定分配至 ds0 的 t_user 真实表
spring.shardingsphere.sharding.tables.t_user.actual-data-nodes=ds0.t_user
# 配置主键的生成策略
spring.shardingsphere.sharding.tables.t_user.key-generator.column=user_id
spring.shardingsphere.sharding.tables.t_user.key-generator.type=SNOWFLAKE
# 指定分表策略
spring.shardingsphere.sharding.tables.t_user.table-strategy.inline.sharding-column=user_id
spring.shardingsphere.sharding.tables.t_user.table-strategy.inline.algorithm-expression=t_user
# 打开 sql 输出日志
spring.shardingsphere.props.sql.show=true
测试代码使用垂直分库时编写的addUserDb和findUserDb方法。
addUserDb方法执行结果:
findUserDb方法执行结果:
https://shardingsphere.apache.org/document/current/cn/downloads/
下载解压后,需要把lib目录下后缀不全的jar包名补全。
进入conf目录
create database edu_1;
)schemaName: sharding_db
dataSources:
ds_0:
url: jdbc:mysql://10.211.55.26:33060/edu_1?serverTimezone=UTC&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
shardingRule:
tables:
t_order:
actualDataNodes: ds_${0}.t_order_${0..1}
tableStrategy:
inline:
shardingColumn: order_id
algorithmExpression: t_order_${order_id % 2}
keyGenerator:
type: SNOWFLAKE
column: order_id
bindingTables:
- t_order
defaultDatabaseStrategy:
inline:
shardingColumn: user_id
algorithmExpression: ds_${0}
defaultTableStrategy:
none:
./start.sh
启动服务。(空格然后加上端口号,可以指定端口,默认3307)mysql -uroot -proot -P3307
-h127.0.0.1
。mysql -uroot -proot -h127.0.0.1 -P3307
use sharding_db;
create table if not exists ds_0.t_order(`order_id` bigint primary key,`user_id` int not null,`status` varchar(50));
insert into t_order(`order_id`,`user_id`,`status`)values(11,1,'test');
Sharding-Proxy与Sharding-JDBC一样,并不会进行主从复制,主从复制依然是有MySQL自己完成。
把上面mysql主从复制的配置复制下来,方便查看:https://blog.csdn.net/qq_36903261/article/details/108457759
使用docker后,需要在主服务器上新建原先的数据库数据表。
因为记录的文件名以及位点每次重启或刷新都会改变,所以以下命令放在这里,方便查看。
主mysql:
#确认位点 记录下文件名以及位点(重启或者刷新都会改变)
show master status;
从mysql:
#先停止同步
STOP SLAVE;
#修改从库指向到主库,使用上一步记录的文件名以及位点
# master_host docker容器linux的ip地址
# master_port 主mysql暴露的端口
# master_user 主mysql的用户名
# master_password 主mysql的密码
#(最后两项修改成刚刚从主mysql查到的,主mysql每次刷新权限或者重启时,这两个值都会改变,所以每次都需要查看是否相同)
CHANGE MASTER TO
master_host = '10.211.55.26',
master_port = 33060,
master_user = 'db_sync',
master_password = 'db_sync',
master_log_file = 'mysql-bin.000001',
master_log_pos = 823;
#启动同步
START SLAVE;
#查看Slave_IO_Runing和Slave_SQL_Runing字段值都为Yes,表示同步配置成功。
show slave status \G;
老师这里只演示读写分离,并没有mysql的主从复制,我写的是主从复制,读写分离。
#主mysql
create database master_slave_order;
修改 config-master_slave.yaml 文件(此文件为读写分离的配置)
schemaName: master_slave_db
dataSources:
master_ds:
url: jdbc:mysql://10.211.55.26:33060/master_slave_order?serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
slave_ds_0:
url: jdbc:mysql://10.211.55.26:33061/master_slave_order?serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
masterSlaveRule:
name: ms_ds
masterDataSourceName: master_ds
slaveDataSourceNames:
- slave_ds_0
# - slave_ds_1
启动Sharding-Proxy 服务 ./start.sh
创建数据表
use master_slave_db;
create table if not exists master_slave_order.t_order(`order_id` bigint primary key,`user_id` int not null,`status` varchar(50));
insert into t_order(`order_id`,`user_id`,`status`)values(11,1,'test');
学习视频(p1-p23):https://www.bilibili.com/video/BV1LK411s7RX?p=1