ShardingSphere-Proxy 入门教程之二

大纲

前言

学习资源

ShardingSphere-Proxy 使用

垂直分库使用案例

提示

本节将演示 SpringBoot + MyBatis Plus 如何整合 ShardingSphere-JDBC,并实现 垂直分库,即按照业务拆分数据库,核心理念是专库专用。在拆分之前,一个数据库由多个数据表构成,每个表对应着不同的业务。而拆分之后,则是按照业务将表进行归类,分布到不同的数据库中,从而将读写压力分散至不同的数据库(如下图所示)。完整的案例代码可以直接从 GitHub 下载对应章节 shardingsphere-proxy-lesson-02

准备工作

版本说明

本案例所使用的组件版本如下表所示:

组件版本说明
JDK11
MySQL8.0.29
SpringBoot2.7.18
ShardingSphere-Proxy5.1.1
数据库规划

本案例是在两个 MySQL 数据库上实现的,数据库的规划如下图所示:

数据库服务器 IP 端口库的名称表的名称
用户数据库服务器(server-user192.168.2.1913301db_usert_user
订单数据库服务器(server-order192.168.2.1913302db_ordert_order
ShardingSphere-Proxy 服务器 192.168.2.1913309sharding_db(逻辑库)t_user(逻辑表)、t_order(逻辑表)
数据库部署
  • 部署用户数据库服务器(server-user
1
2
3
4
5
6
7
8
9
10
# 创建并启动 MySQL 容器(Docker 会自动在宿主机上创建不存在的目录)
docker run -d \
-p 3301:3306 \
-v /data/server/user/conf:/etc/mysql/conf.d \
-v /data/server/user/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123456 \
-e TZ=Asia/Shanghai \
--name server-user \
--restart always \
mysql:8.0.29
  • 更改用户数据库服务器(server-user)的默认密码校验方式
1
2
3
4
5
6
7
8
# 进入容器内,其中环境变量 "env LANG=C.UTF-8" 用于避免容器内显示中文乱码的问题
docker exec -it server-user env LANG=C.UTF-8 /bin/bash

# 在容器内执行 MySQL 命令行
mysql -uroot -p

# 修改 MySQL 超级管理员用户的默认密码校验方式
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';

  • 部署订单数据库服务器(server-order
1
2
3
4
5
6
7
8
9
10
# 创建并启动 MySQL 容器(Docker 会自动在宿主机上创建不存在的目录)
docker run -d \
-p 3302:3306 \
-v /data/server/order/conf:/etc/mysql/conf.d \
-v /data/server/order/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123456 \
-e TZ=Asia/Shanghai \
--name server-order \
--restart always \
mysql:8.0.29
  • 更改订单数据库服务器(server-order)的默认密码校验方式
1
2
3
4
5
6
7
8
# 进入容器内,其中环境变量 "env LANG=C.UTF-8" 用于避免容器内显示中文乱码的问题
docker exec -it server-order env LANG=C.UTF-8 /bin/bash

# 在容器内执行 MySQL 命令行
mysql -uroot -p

# 修改 MySQL 超级管理员用户的默认密码校验方式
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
数据库初始化
  • 在用户数据库服务器(server-user)中,执行以下 SQL 语句:
1
2
3
4
5
6
7
8
9
10
11
12
-- 创建数据库
CREATE DATABASE db_user;

-- 切换数据库
USE db_user;

-- 创建表
CREATE TABLE t_user (
id BIGINT AUTO_INCREMENT,
uname VARCHAR(30),
PRIMARY KEY (id)
);
  • 在订单数据库服务器(server-order)中,执行以下 SQL 语句:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- 创建数据库
CREATE DATABASE db_order;

-- 切换数据库
USE db_order;

-- 创建表
CREATE TABLE t_order (
id BIGINT AUTO_INCREMENT,
order_no VARCHAR(30),
user_id BIGINT,
amount DECIMAL(10,2),
PRIMARY KEY(id)
);

ShardingSphere-Proxy 配置

垂直分库配置

ShardingSphere-Proxy 支持两种配置模式,包括 YAML 和 DistSQL,任意选择一种即可。

YAML 模式
  • 创建或编辑 ShardingSphere-Proxy 的 conf/config-sharding.yaml 配置文件,添加以下垂直分库的配置内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 逻辑数据库(Schema)的名称
schemaName: sharding_db

# 配置数据源
dataSources:
# 配置第一个数据源(用户库)
ds_0:
url: jdbc:mysql://192.168.2.191:3301/db_user?serverTimezone=UTC&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 1
# 配置第一个数据源(订单库)
ds_1:
url: jdbc:mysql://192.168.2.191:3302/db_order?serverTimezone=UTC&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 1

# 配置数据分片规则
rules:
- !SHARDING
tables:
t_user:
actualDataNodes: ds_0.t_user
t_order:
actualDataNodes: ds_1.t_order
  • ShardingSphere-Proxy 垂直分库的配置内容添加完成后,需要重启 ShardingSphere-Proxy 服务(假设这里是通过 Docker 部署 ShardingSphere-Proxy 服务),否则配置不会生效
1
2
# 重启 Docker 容器
docker restart server-proxy
  • 查看 ShardingSphere-Proxy 中的配置规则(ShardingSphere-Proxy 启动时会从 YAML 配置文件中加载)
1
2
# 远程连接 ShardingSphere-Proxy
mysql -h192.168.2.191 -P3309 -uroot -p
1
2
3
4
5
6
7
8
-- 切换到逻辑数据库(Schema)
USE sharding_db;

-- 查询所有数据源
SHOW SCHEMA RESOURCES;

-- 查询所有数据分片规则
SHOW SHARDING TABLE RULES;
DistSQL 模式
  • 远程连接 ShardingSphere-Proxy
1
mysql -h192.168.2.191 -P3309 -uroot -p
  • 创建逻辑数据库(Schema)
1
2
3
4
5
-- 创建逻辑数据库(Schema)
CREATE DATABASE sharding_db;

-- 切换到逻辑数据库(Schema)
USE sharding_db;
  • 添加数据源
1
2
3
4
5
6
7
8
9
10
11
12
13
-- 添加第一个数据源(用户库)
ADD RESOURCE ds_0 (
URL="jdbc:mysql://192.168.2.191:3301/db_user?serverTimezone=UTC&useSSL=false",
USER=root,
PASSWORD=123456,
PROPERTIES(
"connectionTimeoutMilliseconds"="30000",
"idleTimeoutMilliseconds"="60000",
"maxLifetimeMilliseconds"="1800000",
"maxPoolSize"="50",
"minPoolSize"="1"
)
);
1
2
3
4
5
6
7
8
9
10
11
12
13
-- 添加第二个数据源(订单库)
ADD RESOURCE ds_1 (
URL="jdbc:mysql://192.168.2.191:3302/db_order?serverTimezone=UTC&useSSL=false",
USER=root,
PASSWORD=123456,
PROPERTIES(
"connectionTimeoutMilliseconds"="30000",
"idleTimeoutMilliseconds"="60000",
"maxLifetimeMilliseconds"="1800000",
"maxPoolSize"="50",
"minPoolSize"="1"
)
);
  • 创建数据分片规则
1
2
3
4
-- 创建数据分片规则(用户表)
CREATE SHARDING TABLE RULE t_user (
DATANODES('ds_0.t_user')
);
1
2
3
4
-- 创建数据分片规则(订单表)
CREATE SHARDING TABLE RULE t_order (
DATANODES('ds_1.t_order')
);
  • 查看配置规则(使用 DistSQL 定义数据源和数据分片规则后,不需要重启 ShardingSphere-Proxy 服务,默认会自动生效)
1
2
3
4
5
-- 查询所有数据源
SHOW SCHEMA RESOURCES;

-- 查询所有数据分片规则
SHOW SHARDING TABLE RULES;
查看日志信息
  • 查看 ShardingSphere-Proxy 运行的实时日志信息
1
2
3
4
5
# 进入容器内,其中环境变量 "env LANG=C.UTF-8" 用于避免容器内显示中文乱码的问题
docker exec -it server-proxy env LANG=C.UTF-8 /bin/bash

# 查看日志信息
tail -f /opt/shardingsphere-proxy/logs/stdout.log
远程访问测试
  • 远程连接 ShardingSphere-Proxy
1
mysql -h192.168.2.191 -P3309 -uroot -p
  • 查看所有数据库
1
2
3
4
5
6
7
8
9
10
mysql> show databases;
+------------------------+
| schema_name |
+------------------------+
| sharding_db |
| mysql |
| information_schema |
| performance_schema |
| sys |
+------------------------+
  • 切换数据库
1
mysql> use sharding_db;
  • 查看所有表
1
2
3
4
5
6
7
mysql> show tables;
+-----------------------+------------+
| Tables_in_sharding_db | Table_type |
+-----------------------+------------+
| t_user | BASE TABLE |
| t_order | BASE TABLE |
+-----------------------+------------+
  • 查询用户数据
1
mysql> select * from t_user;
  • 查询订单数据
1
mysql> select * from t_order;

提示

执行完上述 select 操作后,可以在 ShardingSphere-Proxy 的日志信息中,查看对应的逻辑 SQL 和真实 SQL,以此判断垂直分库是否生效。

SpringBoot 实战使用案例

添加依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<properties>
<spring-boot.version>2.7.18</spring-boot.version>
<mysql-connector.version>8.2.0</mysql-connector.version>
<mybatis-plus.version>3.3.1</mybatis-plus.version>
</properties>

<dependencyManagement>
<dependencies>
<!-- SpringBoot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<!-- Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MySQL -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>${mysql-connector.version}</version>
</dependency>
<!-- MyBatis Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
创建实体类
  • 用户实体类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

@TableName("t_user") // 逻辑表名
@Data
public class User {

@TableId(type = IdType.AUTO)
private Long id;

private String uname;

}
  • 订单实体类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.math.BigDecimal;

@TableName("t_order") // 逻辑表名
@Data
public class Order {

@TableId(type = IdType.AUTO)
private Long id;

private String orderNo;

private Long userId;

private BigDecimal amount;

}
创建 Mapper
  • 用户 Mapper
1
2
3
4
5
6
7
8
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.clay.shardingjdbc.entity.User;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper extends BaseMapper<User> {

}
  • 订单 Mapper
1
2
3
4
5
6
7
8
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.clay.shardingjdbc.entity.Order;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface OrderMapper extends BaseMapper<Order> {

}
配置数据源
  • 创建配置文件(application.properties),配置数据源
1
2
3
4
5
6
7
8
9
10
11
# 应用名称
spring.application.name=sharding-proxy-demo

# 配置数据源(这里连接的是 ShardingSphere-Proxy,而不是 MySQL)
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://192.168.2.191:3309/sharding_db?serverTimezone=GMT%2B8&useSSL=false
spring.datasource.username=root
spring.datasource.password=root

# 打印 MyBatis-Plus 日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

测试代码

插入数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@SpringBootTest
class ShardingTest {

@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("00001");
order.setUserId(user.getId());
order.setAmount(new BigDecimal(100));
orderMapper.insert(order);
}

}

测试代码运行后,ShardingSphere-Proxy 运行输出的日志信息如下:

1
2
3
4
5
6
7
21:31:51.924 [ShardingSphere-Command-10] INFO ShardingSphere-SQL - Logic SQL: INSERT INTO t_user ( uname ) VALUES ( '张三' )
21:31:51.924 [ShardingSphere-Command-10] INFO ShardingSphere-SQL - SQLStatement: MySQLInsertStatement(setAssignment=Optional.empty, onDuplicateKeyColumns=Optional.empty)
21:31:51.924 [ShardingSphere-Command-10] INFO ShardingSphere-SQL - Actual SQL: ds_0 ::: INSERT INTO t_user ( uname ) VALUES ('张三')

21:31:52.075 [ShardingSphere-Command-10] INFO ShardingSphere-SQL - Logic SQL: INSERT INTO t_order ( order_no, user_id, amount ) VALUES ( '00001', 1, 100 )
21:31:52.075 [ShardingSphere-Command-10] INFO ShardingSphere-SQL - SQLStatement: MySQLInsertStatement(setAssignment=Optional.empty, onDuplicateKeyColumns=Optional.empty)
21:31:52.076 [ShardingSphere-Command-10] INFO ShardingSphere-SQL - Actual SQL: ds_1 ::: INSERT INTO t_order ( order_no, user_id, amount ) VALUES ('00001', 1, 100)
查询数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@SpringBootTest
class ShardingTest {

@Autowired
private UserMapper userMapper;

@Autowired
private OrderMapper orderMapper;

/**
* 垂直分库:查询数据测试
*/
@Test
public void testSelectFromOrderAndUser() {
User user = userMapper.selectById(1L);
System.out.println(user);
Order order = orderMapper.selectById(1L);
System.out.println(order);
}

}

测试代码运行后,ShardingSphere-Proxy 运行输出的日志信息如下:

1
2
3
4
5
6
7
21:39:05.122 [ShardingSphere-Command-12] INFO ShardingSphere-SQL - Logic SQL: SELECT id,uname FROM t_user WHERE id=1
21:39:05.122 [ShardingSphere-Command-12] INFO ShardingSphere-SQL - SQLStatement: MySQLSelectStatement(table=Optional.empty, limit=Optional.empty, lock=Optional.empty, window=Optional.empty)
21:39:05.122 [ShardingSphere-Command-12] INFO ShardingSphere-SQL - Actual SQL: ds_0 ::: SELECT id,uname FROM t_user WHERE id=1

21:39:05.213 [ShardingSphere-Command-13] INFO ShardingSphere-SQL - Logic SQL: SELECT id,order_no,user_id,amount FROM t_order WHERE id=1
21:39:05.213 [ShardingSphere-Command-13] INFO ShardingSphere-SQL - SQLStatement: MySQLSelectStatement(table=Optional.empty, limit=Optional.empty, lock=Optional.empty, window=Optional.empty)
21:39:05.213 [ShardingSphere-Command-13] INFO ShardingSphere-SQL - Actual SQL: ds_1 ::: SELECT id,order_no,user_id,amount FROM t_order WHERE id=1