ShardingSphere-Proxy 入门教程之六

大纲

前言

学习资源

ShardingSphere-Proxy 使用

广播表使用案例

提示

本节将演示 SpringBoot + MyBatis-Plus 如何使用 ShardingSphere-Proxy 的广播表。完整的案例代码可以直接从 GitHub 下载对应章节 shardingsphere-proxy-lesson-06

核心概念

  • 广播表的简介:

    • 广播表是指所有的分片数据源中都存在的表,且表结构及其表数据(包括主键值)在每个数据库中均完全一致。
    • 适用于数据量不大,且需要与海量数据的表进行关联查询(JOIN)的场景,例如:字典表。
  • 广播表的特性:

    • 数据变更会同步到所有分片节点
      • 对广播表执行 INSERTUPDATEDELETE 操作时,ShardingSphere-Proxy 会将变更操作同时广播到所有数据源,确保各节点的数据完全一致。
    • 查询操作可在任意节点完成
      • 广播表的数据在所有节点中一致,因此查询时可从任意一个数据源获取结果(通常基于负载均衡策略选择节点)。
    • 可与任意表进行 JOIN
      • 广播表在所有节点都有一份完整数据,不涉及跨节点聚合,因此可以安全、方便地与任意分片表或非分片表进行 JOIN 操作。

准备工作

版本说明

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

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

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

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

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

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

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

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

# 修改 MySQL 超级管理员用户的默认密码校验方式
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';
数据库初始化
  • 在订单数据库服务器一(server_order0)中,执行以下 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_dict(
id BIGINT,
dict_type VARCHAR(200),
dict_code VARCHAR(100),
dict_value VARCHAR(200),
PRIMARY KEY(id)
);
  • 在订单数据库服务器二(server_order1)中,执行以下 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_dict(
id BIGINT,
dict_type VARCHAR(200),
dict_code VARCHAR(100),
dict_value VARCHAR(200),
PRIMARY KEY(id)
);

ShardingSphere-Proxy 配置

特别注意

无论 ShardingSphere-Proxy 是使用 YAML 配置 还是 DistSQL 配置,都必须注意 ShardingSphere-Proxy 的版本差异。不同版本之间支持的 DistSQL 命令、YAML 配置语法以及参数格式可能存在较大差异,同一份配置在不同版本下可能无法直接执行或行为不一致,因此在编写和迁移配置时必须结合 ShardingSphere-Proxy 具体版本进行验证。

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
35
36
37
38
# 逻辑数据库(Schema)名称
schemaName: sharding_db

# 配置数据源
dataSources:
# 配置第 1 个数据源
server_order0:
url: jdbc:mysql://192.168.2.191:3310/db_order?serverTimezone=UTC&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 1

# 配置第 2 个数据源
server_order1:
url: jdbc:mysql://192.168.2.191:3311/db_order?serverTimezone=UTC&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 1

# 配置数据分片规则
rules:
- !SHARDING
# 配置分片表规则
tables:
t_dict:
actualDataNodes: server_order$->{0..1}.t_dict

# 配置广播表
broadcastTables:
- t_dict
  • 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
9
10
11
12
13
14
-- 查询所有数据库
SHOW DATABASES;

-- 切换到逻辑数据库(Schema)
USE sharding_db;

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

-- 查询所有数据分片规则
SHOW SHARDING TABLE RULES;

-- 查询所有广播表规则
SHOW SHARDING BROADCAST TABLE RULES;
DistSQL 配置
  • 创建逻辑数据库(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 server_order0 (
URL="jdbc:mysql://192.168.2.191:3310/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
5
6
7
8
9
10
11
12
13
-- 添加第二个数据源
ADD RESOURCE server_order1 (
URL="jdbc:mysql://192.168.2.191:3311/db_order?serverTimezone=UTC&useSSL=false",
USER=root,
PASSWORD=123456,
PROPERTIES(
"connectionTimeoutMilliseconds"="30000",
"idleTimeoutMilliseconds"="60000",
"maxLifetimeMilliseconds"="1800000",
"maxPoolSize"="50",
"minPoolSize"="1"
)
);
  • 创建分片规则
1
2
3
CREATE SHARDING TABLE RULE t_dict (
DATANODES("server_order$->{0..1}.t_dict")
);
  • 创建广播表规则
1
CREATE SHARDING BROADCAST TABLE RULES (t_dict);
  • 验证配置规则是否生效(使用 DistSQL 定义数据源和数据分片规则后,不需要重启 ShardingSphere-Proxy 服务,默认会自动生效)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- 查询所有数据库
SHOW DATABASES;

-- 切换到逻辑数据库(Schema)
USE sharding_db;

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

-- 查询所有数据分片规则
SHOW SHARDING TABLE RULES;

-- 查询所有广播表规则
SHOW SHARDING BROADCAST 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 输出详细的日志信息,需要在其 conf/server.yml 配置文件中添加 sql-show: true 配置项。

远程访问测试
  • 远程连接 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
mysql> show tables;
+-----------------------+------------+
| Tables_in_sharding_db | Table_type |
+-----------------------+------------+
| t_dict | BASE TABLE |
+-----------------------+------------+

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>
创建实体类
  • 字典实体类(特别注意:由于广播表的表结构及其表数据(包括主键值)在每个数据库中均完全一致,因此可以使用 MyBatis-Plus 内置的 ASSIGN_ID(基于雪花算法)主键生成策略来生成 ID
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
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

@TableName("t_dict") // 真实表名
@Data
public class Dict {

/**
* 使用 MyBatis-Plus 的雪花算法来生成 ID <br>
* 由于广播表的表结构及其表数据(包括主键值)在每个数据库中均完全一致,因此可以使用 MyBatis-Plus 的 ASSIGN_ID(基于雪花算法)主键生成策略
*/
@TableId(type = IdType.ASSIGN_ID)
private Long id;

/**
* 字典类型 <br>
* 例如: gender / order_status
*/
private String dictType;

/**
* 字典编码 <br>
* 例如: male / pending
*/
private String dictCode;

/**
* 字典显示值 <br>
* 例如: 男 / 待支付
*/
private String dictValue;

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

@Mapper
public interface DictMapper extends BaseMapper<Dict> {

}
配置数据源
  • 创建配置文件(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
@SpringBootTest
class ShardingTest {

@Autowired
DictMapper dictMapper;

/**
* 广播表:插入数据<br>
* 每个数据库服务器中的 t_dict 表会同时插入数据
*/
@Test
public void testInsertBroadcast() {
Dict dict = new Dict();
dict.setDictType("gender");
dict.setDictCode("male");
dict.setDictValue("男");
dictMapper.insert(dict);
}

}

测试代码运行后,ShardingSphere-Proxy 运行输出的日志信息如下(可以发现,往不同数据库服务器中的 t_dict 表插入数据时,该数据的主键(ID)值都是相同的):

1
2
3
4
22:45:31.425 [ShardingSphere-Command-3] INFO ShardingSphere-SQL - Logic SQL: INSERT INTO t_dict ( id, dict_type, dict_code, dict_value ) VALUES ( 2002057716207280130, 'gender', 'male', '男' )
22:45:31.426 [ShardingSphere-Command-3] INFO ShardingSphere-SQL - SQLStatement: MySQLInsertStatement(setAssignment=Optional.empty, onDuplicateKeyColumns=Optional.empty)
22:45:31.426 [ShardingSphere-Command-3] INFO ShardingSphere-SQL - Actual SQL: server_order0 ::: INSERT INTO t_dict ( id, dict_type, dict_code, dict_value ) VALUES (2002057716207280130, 'gender', 'male', '男')
22:45:31.426 [ShardingSphere-Command-3] INFO ShardingSphere-SQL - Actual SQL: server_order1 ::: INSERT INTO t_dict ( id, dict_type, dict_code, dict_value ) VALUES (2002057716207280130, 'gender', 'male', '男')
查询数据测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@SpringBootTest
class ShardingTest {

@Autowired
DictMapper dictMapper;

/**
* 广播表:查询数据<br>
* 只会从任意一个节点的 t_dict 表中查询数据(基于随机负载均衡规则)
*/
@Test
public void testSelectBroadcast() {
List<Dict> dicts = dictMapper.selectList(null);
}

}

测试代码运行后,ShardingSphere-Proxy 运行输出的日志信息如下(可以发现,最终只会从任意一个节点的 t_dict 表查询数据,默认是基于随机负载均衡规则):

1
2
3
22:51:40.491 [ShardingSphere-Command-8] INFO ShardingSphere-SQL - Logic SQL: SELECT  id,dict_type,dict_code,dict_value  FROM t_dict
22:51:40.491 [ShardingSphere-Command-8] INFO ShardingSphere-SQL - SQLStatement: MySQLSelectStatement(table=Optional.empty, limit=Optional.empty, lock=Optional.empty, window=Optional.empty)
22:51:40.491 [ShardingSphere-Command-8] INFO ShardingSphere-SQL - Actual SQL: server_order0 ::: SELECT id,dict_type,dict_code,dict_value FROM t_dict