项目中可以使用ShardingSphere-JDBC将数据存到不同库的表中
一、准备服务器
服务器规划:使用docker
方式创建如下容器
- 主服务器:容器名server-order0,端口3310
- 从服务器:容器名server-order1,端口3311
1. 创建server-order0容器
① 创建容器
docker run -d \
-p 3310:3306 \
-v /liush/server/order0/conf:/etc/mysql/conf.d \
-v /liush/server/order0/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123456 \
--name server-order0 \
mysql:8.0.29
② 登录MySQL服务器
#进入容器:
docker exec -it server-order0 env LANG=C.UTF-8 /bin/bash
#进入容器内的mysql命令行
mysql -uroot -proot
#修改默认密码插件
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
③ 创建数据库
注意:水平分片的id需要在业务层实现,不能依赖数据库的主键自增
CREATE DATABASE db_order;
USE db_order;
CREATE TABLE t_order0 (
id BIGINT,
order_no VARCHAR(30),
user_id BIGINT,
amount DECIMAL(10,2),
PRIMARY KEY(id)
);
CREATE TABLE t_order1 (
id BIGINT,
order_no VARCHAR(30),
user_id BIGINT,
amount DECIMAL(10,2),
PRIMARY KEY(id)
);
2. 创建server-order1容器
① 创建容器
docker run -d \
-p 3311:3306 \
-v /liush/server/order1/conf:/etc/mysql/conf.d \
-v /liush/server/order1/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123456 \
--name server-order1 \
mysql:8.0.29
② 登录MySQL服务器
#进入容器:
docker exec -it server-order1 env LANG=C.UTF-8 /bin/bash
#进入容器内的mysql命令行
mysql -uroot -proot
#修改默认密码插件
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
③ 创建数据库:和server-order0相同
注意:水平分片的id需要在业务层实现,不能依赖数据库的主键自增
CREATE DATABASE db_order;
USE db_order;
CREATE TABLE t_order0 (
id BIGINT,
order_no VARCHAR(30),
user_id BIGINT,
amount DECIMAL(10,2),
PRIMARY KEY(id)
);
CREATE TABLE t_order1 (
id BIGINT,
order_no VARCHAR(30),
user_id BIGINT,
amount DECIMAL(10,2),
PRIMARY KEY(id)
);
二、程序实现
水平分片需要关注全局序列的实现,因为不能简单的使用基于数据库的主键自增。
这里有两种方案:一种是基于MyBatisPlus的id策略;一种是ShardingSphere-JDBC的全局序列配置。
1. 修改实体类
基于MyBatisPlus的id策略:将Order类的id设置成如下形式
@TableId(type = IdType.ASSIGN_ID)
private Long id;
2. 配置全局序列
基于ShardingSphere-JDBC的全局序列配置:和前面的MyBatisPlus的策略二选一
# 分布式序列策略配置
# 分布式序列列名称
spring.shardingsphere.rules.sharding.tables.t_order.key-generate-strategy.column=id
# 分布式序列算法名称
spring.shardingsphere.rules.sharding.tables.t_order.key-generate-strategy.key-generator-name=alg_snowflake
# 分布式序列算法配置:<key-generate-algorithm-name> 分布式序列算法名称
spring.shardingsphere.rules.sharding.key-generators.alg_snowflake.type=SNOWFLAKE
3. 配置水平分片
分片规则:
- order表中id为偶数时,数据插入对应服务器的t_order0,id为奇数时,数据插入对应服务器的t_order1
- order表中user_id为偶数时,数据插入server-order0,user_id为奇数时,数据插入server-order1
这样分片的好处是,同一个用户的订单数据,一定会被插入到同一台服务器上,查询一个用户的订单时效率较高
# 应用名称
spring.application.name=sharding-jdbc-demo
# 环境设置
spring.profiles.active=dev
#=======================================数据源配置
# 配置真实数据源
spring.shardingsphere.datasource.names=server-user,server-order0,server-order1
# 配置第 1 个数据源
spring.shardingsphere.datasource.server-user.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.server-user.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.server-user.url=jdbc:mysql://192.168.100.201:3301/db_user
spring.shardingsphere.datasource.server-user.username=root
spring.shardingsphere.datasource.server-user.password=123456
# 配置第 2 个数据源
spring.shardingsphere.datasource.server-order0.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.server-order0.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.server-order0.url=jdbc:mysql://192.168.100.201:3310/db_order
spring.shardingsphere.datasource.server-order0.username=root
spring.shardingsphere.datasource.server-order0.password=123456
# 配置第 3 个数据源
spring.shardingsphere.datasource.server-order1.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.server-order1.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.server-order1.url=jdbc:mysql://192.168.100.201:3311/db_order
spring.shardingsphere.datasource.server-order1.username=root
spring.shardingsphere.datasource.server-order1.password=123456
#=======================================数据节点配置
# 标准分片表配置(数据节点)
# <table-name>逻辑表名:匹配 数据源名.真实表
spring.shardingsphere.rules.sharding.tables.t_user.actual-data-nodes=server-user.t_user
spring.shardingsphere.rules.sharding.tables.t_order.actual-data-nodes=server-order$->{0..1}.t_order$->{0..1}
#=======================================t_order表分片配置
#水平分片到同一数据库
# 分片列名称
spring.shardingsphere.rules.sharding.tables.t_order.table-strategy.standard.sharding-column=id
# 分片算法名称
spring.shardingsphere.rules.sharding.tables.t_order.table-strategy.standard.sharding-algorithm-name=alg_mod
#水平分片到不同数据库
# 分片列名称
spring.shardingsphere.rules.sharding.tables.t_order.database-strategy.standard.sharding-column=user_id
# 分片算法名称
spring.shardingsphere.rules.sharding.tables.t_order.database-strategy.standard.sharding-algorithm-name=alg_mod
#=======================================分片策略配置
# 分片算法类型:<sharding-algorithm-name> 分片算法名称
spring.shardingsphere.rules.sharding.sharding-algorithms.alg_mod.type=MOD
# 分片算法属性配置
spring.shardingsphere.rules.sharding.sharding-algorithms.alg_mod.props.sharding-count=2
#=======================================分布式序列策略配置
# 分布式序列策略配置:这里MyBatisPlus的分布式id和此处的设置二选一
# 分布式序列列名称
#spring.shardingsphere.rules.sharding.tables.t_order.key-generate-strategy.column=id
# 分布式序列算法名称
#spring.shardingsphere.rules.sharding.tables.t_order.key-generate-strategy.key-generator-name=alg_snowflake
# 分布式序列算法配置:<key-generate-algorithm-name> 分布式序列算法名称
#spring.shardingsphere.rules.sharding.key-generators.alg_snowflake.type=SNOWFLAKE
# 打印SQL
spring.shardingsphere.props.sql-show=true
三、测试水平分片和全局序列
/**
* 水平分片:插入数据
*/
@Test
public void testInsert2(){
//创建10个用户
for (int i = 0; i < 10; i++) {
User user = new User();
user.setUname("用户" + i);
userMapper.insert(user);
//为每个用户生成1-5个订单
int count = new Random().nextInt(5) + 1;
System.out.println(" count = " + count);
for (int j = 0; j < count; j++) {
Order order = new Order();
order.setOrderNo("ATGUIGU"+j);
order.setAmount(new BigDecimal(100));
order.setUserId(user.getId());
orderMapper.insert(order);
}
}
}
/**
* 水平分片:读所有订单数据
*/
@Test
public void testSelectAll(){
List<Order> orders = orderMapper.selectList(null);
orders.forEach(System.out::println);
}
/**
* 水平分片:读某个用户的订单数据
* 可以发现,统一用户的订单来源于同一数据库
*/
@Test
public void testSelectOrdersByUserId(){
QueryWrapper<Order> orderQueryWrapper = new QueryWrapper<>();
orderQueryWrapper.eq("user_id", 483L);
List<Order> orders = orderMapper.selectList(orderQueryWrapper);
orders.forEach(System.out::println);
}