文章目录
- 1 ShardingSphere-JDBC读写分离
- 1.1 创建SpringBoot程序
- 1.1.1、创建项目
- 1.1.2、添加依赖
- 1.1.3、创建实体类
- 1.1.4、创建Mapper
- 1.1.5、配置 Spring Boot
- 1.1.6、配置shardingsphere
- 1.2 测试
- 1.2.1 读写分离测试
- 1.2.2 负载均衡测试
- 1.2.3 事务测试
- 常见错误
- 2 ShardingSphere-JDBC垂直分片
- 2.1 准备服务器
- 2.1.1、创建server-user容器
- 2.1.2、创建server-order容器
- 2.2 程序实现
- 2.2.1、创建实体类
- 2.2.2、创建Mapper
- 2.2.3、配置垂直分片
- 2.3 测试垂直分片
- 3 ShardingSphere-JDBC水平分片
- 3.1 准备服务器
- 3.1.1、创建server-order0容器
- 3.1.2、创建server-order1容器
- 3.2 水平分片
- 3.2.1、配置一个分片节点
- 3.2.2、水平分库配置
- 3.2.3、水平分表配置
- 3.3 多表关联
- 3.3.1、创建关联表
- 3.3.2、创建实体类
- 3.3.3、创建Mapper
- 3.3.4、配置关联表
- 3.3.5、测试插入数据
1 ShardingSphere-JDBC读写分离
1.1 创建SpringBoot程序
1.1.1、创建项目
项目类型:Spring Initializr
SpringBoot脚手架:http://start.aliyun.com
项目名:sharding-jdbc-demo
SpringBoot版本:3.0.5
1.1.2、添加依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core</artifactId>
<version>5.4.0</version>
</dependency>
<!--兼容jdk17和spring boot3-->
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.33</version>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.8</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
1.1.3、创建实体类
package com.atguigu.shardingjdbcdemo.entity;
@TableName("t_user")
@Data
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String uname;
}
1.1.4、创建Mapper
package com.atguigu.shardingjdbcdemo.mapper;
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
1.1.5、配置 Spring Boot
application.properties:
# 配置 DataSource Driver
spring.datasource.driver-class-name=org.apache.shardingsphere.driver.ShardingSphereDriver
# 指定 YAML 配置文件
spring.datasource.url=jdbc:shardingsphere:classpath:shardingsphere.yaml
1.1.6、配置shardingsphere
shardingsphere.yaml
模式配置:
mode:
type: Standalone
repository:
type: JDBC
数据源配置:
dataSources:
write_ds:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://192.168.100.101:3306/db_user
username: root
password: 123456
read_ds_0:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://192.168.100.101:3307/db_user
username: root
password: 123456
read_ds_1:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://192.168.100.101:3308/db_user
username: root
password: 123456
读写分离配置:
rules:
- !READWRITE_SPLITTING
dataSources:
readwrite_ds:
writeDataSourceName: write_ds
readDataSourceNames:
- read_ds_0
- read_ds_1
transactionalReadQueryStrategy: PRIMARY # 事务内读请求的路由策略,可选值:PRIMARY(路由至主库)、FIXED(同一事务内路由至固定数据源)、DYNAMIC(同一事务内路由至非固定数据源)。默认值:DYNAMIC
loadBalancerName: random
loadBalancers:
random:
type: RANDOM
输出sql:
props:
sql-show: true
1.2 测试
1.2.1 读写分离测试
package com.atguigu.shardingjdbcdemo;
@SpringBootTest
class ShardingJdbcDemoApplicationTests {
@Autowired
private UserMapper userMapper;
/**
* 写入数据的测试
*/
@Test
public void testInsert(){
User user = new User();
user.setUname("张三丰");
userMapper.insert(user);
}
}
1.2.2 负载均衡测试
/**
* 负载均衡测试
*/
@Test
public void testSelect(){
for (int i = 0; i < 100; i++) {
User user1 = userMapper.selectById(1);
}
}
负载均衡算法配置:
rules:
- !READWRITE_SPLITTING
loadBalancers:
random:
type: RANDOM
round_robin:
type: ROUND_ROBIN
weight:
type: WEIGHT
props:
read_ds_0: 1
read_ds_1: 2
1.2.3 事务测试
transactionalReadQueryStrategy: PRIMARY
事务内读请求的路由策略,可选值:
PRIMARY(路由至主库)
FIXED(同一事务内路由至固定数据源)
DYNAMIC(同一事务内路由至非固定数据源)。默认值:DYNAMIC
测试1:
不添加@Transactional:insert对主库操作,select对从库操作
测试2:
添加@Transactional:则insert和select按照transactionalReadQueryStrategy的配置执行
/**
* 事务测试
*/
@Transactional//开启事务
@Test
public void testTrans(){
User user = new User();
user.setUname("铁锤");
userMapper.insert(user);
List<User> users = userMapper.selectList(null);
}
**注意:**在JUnit环境下的@Transactional注解,默认情况下就会对事务进行回滚(即使在没加注解@Rollback,也会对事务回滚)
常见错误
ShardingSphere-JDBC远程连接的方式默认的密码加密规则是:mysql_native_password
因此需要在服务器端修改服务器的密码加密规则,如下:
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
2 ShardingSphere-JDBC垂直分片
2.1 准备服务器
服务器规划:使用docker
方式创建如下容器
-
服务器:容器名
server-user
,端口3301
-
服务器:容器名
server-order
,端口3302
2.1.1、创建server-user容器
- step1:创建容器:
docker run -d \
-p 3318:3306 \
-v mysql-order-conf:/etc/mysql/conf.d \
-v mysql-order-data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123456 \
--name mysql-order \
mysql:8
- step2:登录MySQL服务器:
#进入容器:
docker exec -it server-user env LANG=C.UTF-8 /bin/bash
#进入容器内的mysql命令行
mysql -uroot -p
#修改默认密码插件
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
- step3:创建数据库:
CREATE DATABASE db_user;
USE db_user;
CREATE TABLE t_user (
id BIGINT AUTO_INCREMENT,
uname VARCHAR(30),
PRIMARY KEY (id)
);
2.1.2、创建server-order容器
- step1:创建容器:
docker run -d \
-p 3302:3306 \
-v /atguigu/server/order/conf:/etc/mysql/conf.d \
-v /atguigu/server/order/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123456 \
--name server-order \
mysql:8.0.29
- step2:登录MySQL服务器:
#进入容器:
docker exec -it server-order env LANG=C.UTF-8 /bin/bash
#进入容器内的mysql命令行
mysql -uroot -p
#修改默认密码插件
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
- step3:创建数据库:
CREATE DATABASE db_order;
USE db_order;
CREATE TABLE t_order (
id BIGINT AUTO_INCREMENT,
order_no VARCHAR(30),
user_id BIGINT,
PRIMARY KEY(id)
);
2.2 程序实现
2.2.1、创建实体类
package com.atguigu.shardingjdbcdemo.entity;
@TableName("t_order")
@Data
public class Order {
@TableId(type = IdType.AUTO)
private Long id;
private String orderNo;
private Long userId;
}
2.2.2、创建Mapper
package com.atguigu.shardingjdbcdemo.mapper;
@Mapper
public interface OrderMapper extends BaseMapper<Order> {
}
2.2.3、配置垂直分片
模式配置
mode:
type: Standalone
repository:
type: JDBC
数据源配置:
dataSources:
user_ds:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://192.168.100.101:3301/db_user
username: root
password: 123456
order_ds:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://192.168.100.101:3302/db_order
username: root
password: 123456
垂直分片配置:
rules:
- !SHARDING
tables:
t_user:
actualDataNodes: user_ds.t_user
t_order:
actualDataNodes: order_ds.t_order
输出sql:
props:
sql-show: true
2.3 测试垂直分片
@Autowired
private UserMapper userMapper;
@Autowired
private OrderMapper orderMapper;
/**
* 垂直分片:插入数据测试
*/
@Test
public void testInsertOrderAndUser(){
User user = new User();
user.setUname("强哥");
userMapper.insert(user);
Order order = new Order();
order.setOrderNo("ATGUIGU001");
order.setUserId(user.getId());
orderMapper.insert(order);
}
/**
* 垂直分片:查询数据测试
*/
@Test
public void testSelectFromOrderAndUser(){
User user = userMapper.selectById(1L);
Order order = orderMapper.selectById(1L);
}
3 ShardingSphere-JDBC水平分片
3.1 准备服务器
服务器规划:使用docker
方式创建如下容器
-
服务器:容器名
server-order0
,端口3310
-
服务器:容器名
server-order1
,端口3311
3.1.1、创建server-order0容器
- step1:创建容器:
docker run -d \
-p 3319:3306 \
-v /atguigu/server/order0/conf:/etc/mysql/conf.d \
-v /atguigu/server/order0/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123456 \
--name server-order2 \
mysql:8.0.29
- step2:登录MySQL服务器:
#进入容器:
docker exec -it server-order0 env LANG=C.UTF-8 /bin/bash
#进入容器内的mysql命令行
mysql -uroot -p
#修改默认密码插件
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
- step3:创建数据库:
注意:
水平分片的id需要在业务层实现,不能依赖数据库的主键自增
CREATE DATABASE db_order;
USE db_order;
CREATE TABLE t_order0 (
id BIGINT,
order_no VARCHAR(30),
user_id BIGINT,
PRIMARY KEY(id)
);
CREATE TABLE t_order1 (
id BIGINT,
order_no VARCHAR(30),
user_id BIGINT,
PRIMARY KEY(id)
);
3.1.2、创建server-order1容器
- step1:创建容器:
docker run -d \
-p 3311:3306 \
-v /atguigu/server/order1/conf:/etc/mysql/conf.d \
-v /atguigu/server/order1/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123456 \
--name server-order1 \
mysql:8.0.29
- step2:登录MySQL服务器:
#进入容器:
docker exec -it server-order1 env LANG=C.UTF-8 /bin/bash
#进入容器内的mysql命令行
mysql -uroot -p
#修改默认密码插件
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
- **step3:创建数据库:**和server-order0相同
注意:
水平分片的id需要在业务层实现,不能依赖数据库的主键自增
CREATE DATABASE db_order;
USE db_order;
CREATE TABLE t_order0 (
id BIGINT,
order_no VARCHAR(30),
user_id BIGINT,
PRIMARY KEY(id)
);
CREATE TABLE t_order1 (
id BIGINT,
order_no VARCHAR(30),
user_id BIGINT,
PRIMARY KEY(id)
);
3.2 水平分片
3.2.1、配置一个分片节点
模式配置
mode:
type: Standalone
repository:
type: JDBC
数据源配置:
dataSources:
user_ds:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://192.168.100.101:3301/db_user
username: root
password: 123456
order_ds_0:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://192.168.100.101:3310/db_order
username: root
password: 123456
order_ds_1:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://192.168.100.101:3311/db_order
username: root
password: 123456
配置一个order分片节点:
rules:
- !SHARDING
tables:
t_user:
actualDataNodes: user_ds.t_user
t_order:
actualDataNodes: order_ds_0.t_order0
输出sql:
props:
sql-show: true
修改Order实体类的主键策略:
//@TableId(type = IdType.AUTO)//依赖数据库的主键自增策略
@TableId(type = IdType.ASSIGN_ID)//分布式id
测试代码:
/**
* 水平分片:插入数据测试
*/
@Test
public void testInsertOrder(){
Order order = new Order();
order.setOrderNo("ATGUIGU001");
order.setUserId(1L);
orderMapper.insert(order);
}
3.2.2、水平分库配置
使用行表达式:核心概念 :: ShardingSphere (apache.org)
将数据 分片到order_ds_0和order_ds_1中
actualDataNodes: order_ds_${0..1}.t_order0
分片算法配置
分片规则:order表中user_id
为偶数时,数据插入server-order0服务器
,user_id
为奇数时,数据插入server-order1服务器
。这样分片的好处是,同一个用户的订单数据,一定会被插入到同一台服务器上,查询一个用户的订单时效率较高。
rules:
- !SHARDING
tables:
t_user:
actualDataNodes: user_ds.t_user
t_order:
actualDataNodes: order_ds_${0..1}.t_order0
databaseStrategy:
standard:
shardingColumn: user_id
shardingAlgorithmName: userid_inline
shardingAlgorithms:
userid_inline:
type: INLINE
props:
algorithm-expression: order_ds_${user_id % 2}
测试:
/**
* 水平分片:分库插入数据测试
*/
@Test
public void testInsertOrderDatabaseStrategy(){
for (long i = 0; i < 4; i++) {
Order order = new Order();
order.setOrderNo("ATGUIGU" + System.currentTimeMillis());
order.setUserId(i + 1);
orderMapper.insert(order);
}
}
3.2.3、水平分表配置
将数据 分片到order_ds_0和order_ds_1的t_order0和t_order1中
actualDataNodes: order_ds_${0..1}.t_order${0..1}
分片算法配置
分片规则:order表中id
为偶数时,数据插入t_order0数据库
,id
为奇数时,数据插入t_order1数据库
。
rules:
- !SHARDING
tables:
t_user:
actualDataNodes: user_ds.t_user
t_order:
actualDataNodes: order_ds_${0..1}.t_order${0..1}
databaseStrategy:
standard:
shardingColumn: user_id
shardingAlgorithmName: userid_inline
tableStrategy:
standard:
shardingColumn: id
shardingAlgorithmName: orderid_inline
shardingAlgorithms:
userid_inline:
type: INLINE
props:
algorithm-expression: order_ds_${user_id % 2}
orderid_inline:
type: INLINE
props:
algorithm-expression: t_order${id % 2}
测试:
/**
* 水平分片:分表插入数据测试
*/
@Test
public void testInsertOrderTableStrategy(){
for (long i = 0; i < 4; i++) {
Order order = new Order();
order.setOrderNo("ATGUIGU" + System.currentTimeMillis());
order.setUserId(1L);
orderMapper.insert(order);
}
for (long i = 0; i < 4; i++) {
Order order = new Order();
order.setOrderNo("ATGUIGU" + System.currentTimeMillis());
order.setUserId(2L);
orderMapper.insert(order);
}
}
3.3 多表关联
3.3.1、创建关联表
在server-order0、server-order1
服务器中分别创建两张订单详情表t_order_item0、t_order_item1
我们希望同一个用户的订单表和订单详情表中的数据都在同一个数据源中,避免跨库关联
,因此这两张表我们使用相同的分片策略。
那么在t_order_item
中我们也需要创建order_id
和user_id
这两个分片键
CREATE TABLE t_order_item0(
id BIGINT,
user_id BIGINT,
order_id BIGINT,
price DECIMAL(10,2),
`count` INT,
PRIMARY KEY(id)
);
CREATE TABLE t_order_item1(
id BIGINT,
user_id BIGINT,
order_id BIGINT,
price DECIMAL(10,2),
`count` INT,
PRIMARY KEY(id)
);
3.3.2、创建实体类
package com.atguigu.shardingjdbcdemo.entity;
@TableName("t_order_item")
@Data
public class OrderItem {
@TableId(type = IdType.ASSIGN_ID) //分布式id
private Long id;
private Long userId;
private Long orderId;
private BigDecimal price;
private Integer count;
}
3.3.3、创建Mapper
package com.atguigu.shargingjdbcdemo.mapper;
@Mapper
public interface OrderItemMapper extends BaseMapper<OrderItem> {
}
3.3.4、配置关联表
t_order_item的分片表、分片策略、分布式序列策略和t_order一致
rules:
- !SHARDING
tables:
t_user:
actualDataNodes: user_ds.t_user
t_order:
actualDataNodes: order_ds_${0..1}.t_order${0..1}
databaseStrategy:
standard:
shardingColumn: user_id
shardingAlgorithmName: userid_inline
tableStrategy:
standard:
shardingColumn: id
shardingAlgorithmName: orderid_inline
t_order_item:
actualDataNodes: order_ds_${0..1}.t_order_item${0..1}
databaseStrategy:
standard:
shardingColumn: user_id
shardingAlgorithmName: userid_inline
tableStrategy:
standard:
shardingColumn: order_id
shardingAlgorithmName: orderid_item_inline
shardingAlgorithms:
userid_inline:
type: INLINE
props:
algorithm-expression: order_ds_${user_id % 2}
orderid_inline:
type: INLINE
props:
algorithm-expression: t_order${id % 2}
orderid_item_inline:
type: INLINE
props:
algorithm-expression: t_order_item${order_id % 2}
3.3.5、测试插入数据
同一个用户的订单表和订单详情表中的数据都在同一个数据源中,避免跨库关联
/**
* 测试关联表插入
*/
@Test
public void testInsertOrderAndOrderItem(){
for (long i = 0; i < 2; i++) {
Order order = new Order();
order.setOrderNo("ATGUIGU" + System.currentTimeMillis());
order.setUserId(1L);
orderMapper.insert(order);
for (long j = 0; j < 2; j++) {
OrderItem orderItem = new OrderItem();
orderItem.setUserId(1L);
orderItem.setOrderId(order.getId());
orderItem.setPrice(new BigDecimal(10));
orderItem.setCount(2);
orderItemMapper.insert(orderItem);
}
}
for (long i = 0; i < 2; i++) {
Order order = new Order();
order.setOrderNo("ATGUIGU" + System.currentTimeMillis());
order.setUserId(2L);
orderMapper.insert(order);
for (long j = 0; j < 2; j++) {
OrderItem orderItem = new OrderItem();
orderItem.setUserId(2L);
orderItem.setOrderId(order.getId());
orderItem.setPrice(new BigDecimal(5));
orderItem.setCount(2);
orderItemMapper.insert(orderItem);
}
}
}