ShardingSphere-Proxy 入门教程之一

大纲

前言

学习资源

ShardingSphere-Proxy 安装

安装方式

目前 ShardingSphere-Proxy 官方提供了三种安装方式:

  • Docker
  • Helm(K8s)
  • 二进制发布包

安装步骤

ShardingSphere-Proxy 配置

两种配置方式

  • ShardingSphere-Proxy 支持两种规则配置与治理方式:
    • YAML 配置文件,适用于启动期静态配置。
    • DistSQL(Distributed SQL),适用于运行期动态治理。

DistSQL 的介绍

  • DistSQL 的基本定位

    • DistSQL(Distributed SQL) 是 ShardingSphere 提供的一套分布式数据库治理语言。
    • DistSQL 用于在线管理和调整 ShardingSphere-Proxy 的规则、资源及算法配置,并非用于查询或操作业务数据。
    • 当 ShardingSphere-Proxy 识别到 DistSQL 时:
      • 不会将该 SQL 转发给后端 MySQL
      • 而是由 ShardingSphere-Proxy 自身解析并执行
      • 执行结果存储在 ShardingSphere 的元数据 / 治理上下文中
  • DistSQL 出现之前的配置模式(静态配置时代)

    • 在没有 DistSQL 的情况下,ShardingSphere-Proxy 的配置方式具有明显限制:
      • 修改规则(如分片、读写分离)
        → 必须修改 YAML 配置文件
      • 调整负载均衡策略
        → 必须修改 YAML 配置文件
      • 配置变更后,需要重启 ShardingSphere-Proxy
      • 重启 ShardingSphere-Proxy 会导致:
        • 客户端连接全部断开
        • 在生产环境中存在明显风险
    • 配置调整与服务可用性强耦合
  • DistSQL 引入后的变化(在线治理能力)

    • DistSQL 的引入,使 ShardingSphere-Proxy 具备了运行期动态治理能力:
      • 新增或修改数据源
        → 不需要重启 ShardingSphere-Proxy
      • 创建、修改读写分离规则
        → 不需要重启 ShardingSphere-Proxy
      • 配置或切换负载均衡算法
        → 不需要重启 ShardingSphere-Proxy
      • 动态调整从库权重等参数
        → 不需要重启 ShardingSphere-Proxy
    • 配置变更即时生效,业务连接无感知

DistSQL 的三大语法

  • RDL(Resource Definition Language,资源定义语法)

    • 作用:
      • 定义、修改和管理数据库资源
    • 特点:
      • 面向 ShardingSphere-Proxy 运行时的资源管理
      • 对应 YAML 中的 dataSources 配置
      • 可在线添加、修改、删除资源,无需重启 ShardingSphere-Proxy
    • 用途:
      • 创建 / 删除数据源
      • 修改数据源属性(如连接池大小)
      • 创建 / 修改分片规则或读写分离规则
  • RQL(Resource Query Language,资源查询语法)

    • 作用:
      • 查询 ShardingSphere-Proxy 内部资源、规则和状态信息
    • 特点:
      • 类似 SQL 查询语法,但不操作业务数据
      • 返回 ShardingSphere-Proxy 内部元数据
      • 用于监控和验证配置是否生效
    • 用途:
      • 查询已配置的数据源
      • 查询读写分离规则
      • 查询分片规则
      • 查看负载均衡算法
  • RAL(Resource Administration Language,资源管理语法)

    • 作用:
      • 管理 ShardingSphere-Proxy 运行时状态和执行操作
    • 特点:
      • 类似运维命令
      • 可动态调整 ShardingSphere-Proxy 规则和资源
      • 不影响业务 SQL 执行,不访问后端数据
    • 用途:
      • 启用 / 禁用数据源
      • 刷新表或分片规则元数据
      • 修改读写分离规则的负载均衡算法
      • 在线调整分片表规则
语法作用核心命令示例对应 YAML 配置
RDL 语法定义 / 修改资源(数据源、分片表、规则)ADD RESOURCE / CREATE SHARDING TABLE RULEdataSources / rules
RQL 语法查询资源 / 规则状态SHOW RESOURCES / SHOW READWRITE_SPLITTING RULES无直接 YAML 对应,用于验证
RAL 语法管理运行时状态ENABLE/DISABLE RESOURCE / REFRESH TABLE METADATA对应 YAML 中规则的动态修改

DistSQL 与 YAML 的关系

  • DistSQL 与 YAML 的职责划分

    • YAML 配置
      • 适合定义:
        • 数据源基础信息
        • 初始规则结构
      • 在 ShardingSphere-Proxy 启动时加载
    • DistSQL
      • 适合运行期治理:
        • 规则变更
        • 算法切换
        • 权重调整
        • 状态查看
      • 支持不停机操作
  • DistSQL 与 YAML 不是对立的,而是分工明确。

场景推荐方式
初始启动 YAML
运行期调整 DistSQL
生产环境 YAML + DistSQL
动态治理 DistSQL

DistSQL 与普通 SQL 的区别

对比项普通 SQLDistSQL
操作对象表 / 行规则 / 数据源 / 算法
执行位置数据库 ShardingSphere-Proxy 内部
是否影响业务数据不影响不影响
是否持久化数据表 ShardingSphere-Proxy 元数据
是否需要重启不需要重启不需要重启

ShardingSphere-Proxy 使用

读写分离使用案例

提示

本节将演示 SpringBoot + MyBatis Plus 如何基于 ShardingSphere-Proxy 实现 读写分离,整体架构如下图所示。完整的案例代码可以直接从 GitHub 下载对应章节 sharding-sphere-lesson-07

准备工作

版本说明

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

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

本案例是在 MySQL 一主二从的架构(请自行搭建 MySQL 主从复制环境)上实现的,数据库的规划如下图所示:

数据库服务器主从角色 IP 端口库的名称表的名称
主服务器主库(master192.168.2.1913306db_usert_user
从服务器一从库(slave1192.168.2.1913307db_usert_user
从服务器二从库(slave2192.168.2.1913308db_usert_user
ShardingSphere-Proxy 服务器 192.168.2.1913309readwrite_splitting_dbt_user

MySQL 主从同步环境搭建

MySQL 基于日志点实现主从复制的教程可以看 这里

数据库初始化
  • 在 MySQL 主库(master)中,执行以下 SQL 语句:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-- 创建数据库
CREATE DATABASE db_user;

-- 切换数据库
USE db_user;

-- 创建表
CREATE TABLE t_user (
id BIGINT AUTO_INCREMENT,
uname VARCHAR(30),
PRIMARY KEY (id)
);

-- 插入表数据
INSERT INTO t_user(uname) VALUES('zhang3');
INSERT INTO t_user(uname) VALUES(@@hostname);

ShardingSphere-Proxy 配置

读写分离配置

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

YAML 模式
  • 创建或编辑 ShardingSphere-Proxy 的 conf/config-readwrite-splitting.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
39
40
41
42
43
44
45
# Schema 名称
schemaName: readwrite_splitting_db

# 配置数据源
dataSources:
# 配置第一个数据源(用于写)
write_ds:
url: jdbc:mysql://192.168.2.191:3306/db_user?serverTimezone=UTC&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 1
# 配置第二个数据源(用于读)
read_ds_0:
url: jdbc:mysql://192.168.2.191:3307/db_user?serverTimezone=UTC&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 1
# 配置第三个数据源(用于读)
read_ds_1:
url: jdbc:mysql://192.168.2.191:3308/db_user?serverTimezone=UTC&useSSL=false
username: root
password: 123456
connectionTimeoutMilliseconds: 30000
idleTimeoutMilliseconds: 60000
maxLifetimeMilliseconds: 1800000
maxPoolSize: 50
minPoolSize: 1

# 配置读写分离规则
rules:
- !READWRITE_SPLITTING
dataSources:
readwrite_ds:
type: Static
props:
write-data-source-name: write_ds
read-data-source-names: read_ds_0,read_ds_1
  • ShardingSphere-Proxy 读写分离的配置内容添加完成后,需要重启 ShardingSphere-Proxy 服务(假设这里是通过 Docker 部署 ShardingSphere-Proxy 服务),否则配置不会生效
1
2
# 重启 Docker 容器
docker restart server-proxy
DistSQL 模式
  • 创建 Schema
1
2
3
4
5
-- 创建 Schema(数据库)
CREATE DATABASE readwrite_splitting_db;

-- 切换 Schema(数据库)
USE readwrite_splitting_db;
  • 添加数据源
1
2
3
4
5
6
7
8
9
10
11
12
13
-- 添加写数据源
ADD RESOURCE write_ds (
URL="jdbc:mysql://192.168.2.191:3306/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 read_ds_0 (
URL="jdbc:mysql://192.168.2.191:3307/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 read_ds_1 (
URL="jdbc:mysql://192.168.2.191:3308/db_user?serverTimezone=UTC&useSSL=false",
USER=root,
PASSWORD=123456,
PROPERTIES(
"connectionTimeoutMilliseconds"="30000",
"idleTimeoutMilliseconds"="60000",
"maxLifetimeMilliseconds"="1800000",
"maxPoolSize"="50",
"minPoolSize"="1"
)
);
  • 创建读写分离规则(TYPE 用于指定负载均衡算法,可选:randomround_robinweight
1
2
3
4
5
CREATE READWRITE_SPLITTING RULE readwrite_ds (
WRITE_RESOURCE=write_ds,
READ_RESOURCES(read_ds_0, read_ds_1),
TYPE(NAME=random)
);
  • 验证配置是否生效
1
2
3
4
5
-- 查询所有数据源
SHOW SCHEMA RESOURCES;

-- 查询所有读写分离规则
SHOW READWRITE_SPLITTING RULES;
  • 使用 DistSQL 定义数据源和读写分离规则后,不需要重启 ShardingSphere-Proxy 服务,默认会自动生效
查看日志信息
  • 查看 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
11
mysql> show databases;
+------------------------+
| schema_name |
+------------------------+
| readwrite_splitting_db |
| mysql |
| information_schema |
| performance_schema |
| sys |
+------------------------+
5 rows in set (0.01 sec)
  • 切换数据库
1
mysql> use readwrite_splitting_db;
  • 查看所有表
1
2
3
4
5
6
7
mysql> show tables;
+----------------------------------+------------+
| Tables_in_readwrite_splitting_db | Table_type |
+----------------------------------+------------+
| t_user | BASE TABLE |
+----------------------------------+------------+
1 row in set (0.01 sec)
  • 插入数据
1
mysql> insert into t_user(uname) values('wang5');
  • 查询数据(第一次)
1
mysql> select * from t_user;
  • 查询数据(第二次)
1
mysql> select * from t_user;

提示

执行完上述 insertselect 操作后,可以在 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;

}
创建 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> {

}
配置数据源
  • 创建配置文件(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.jdbc.Driver
spring.datasource.url=jdbc:mysql://192.168.2.191:3309/readwrite_splitting_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
@SpringBootTest
class ReadWriteTest {

@Autowired
private UserMapper userMapper;

/**
* 读写分离测试
*/
@Test
public void testInsert() {
User user = new User();
user.setUname("张三丰");
userMapper.insert(user);

List<User> list = userMapper.selectList(null);
}

}

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

1
2
3
4
5
6
7
16:00:07.139 [ShardingSphere-Command-17] INFO ShardingSphere-SQL - Logic SQL: INSERT INTO t_user  ( uname )  VALUES  ( '张三丰' )
16:00:07.139 [ShardingSphere-Command-17] INFO ShardingSphere-SQL - SQLStatement: MySQLInsertStatement(setAssignment=Optional.empty, onDuplicateKeyColumns=Optional.empty)
16:00:07.139 [ShardingSphere-Command-17] INFO ShardingSphere-SQL - Actual SQL: write_ds ::: INSERT INTO t_user ( uname ) VALUES ( '张三丰' )

16:00:07.903 [ShardingSphere-Command-10] INFO ShardingSphere-SQL - Logic SQL: SELECT id,uname FROM t_user
16:00:07.903 [ShardingSphere-Command-10] INFO ShardingSphere-SQL - SQLStatement: MySQLSelectStatement(table=Optional.empty, limit=Optional.empty, lock=Optional.empty, window=Optional.empty)
16:00:07.903 [ShardingSphere-Command-10] INFO ShardingSphere-SQL - Actual SQL: read_ds_0 ::: SELECT id,uname FROM t_user
事务一致性测试

为了保证主从库间的事务一致性,避免跨库的分布式事务, 在 ShardingSphere-Proxy 的主从模型中,事务内的数据读写均使用主库

  • 不添加 @Transactional 注解时:insert 语句对主库操作,select 语句对从库操作
  • 添加 @Transactional 注解时:insert 语句和 select 语句均对主库操作
  • 注意: 在 JUnit 环境下使用 @Transactional 注解,默认情况下会对事务进行回滚(即使在没有使用 @Rollback 注解的情况下,也会对事务进行回滚);如果不希望回滚事务,可以额外使用 @Rollback(false)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@SpringBootTest
class ReadWriteTest {

@Autowired
private UserMapper userMapper;

/**
* 事务一致性测试 <p>
* 当使用 @Transactional 事务注解后,事务内的所有 SQL 语句(包括读和写)都是在主库(master)执行
*/
@Test
@Transactional
public void testTransaction() {
User user = new User();
user.setUname("李思思");
userMapper.insert(user);

List<User> list = userMapper.selectList(null);
}

}

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

1
2
3
4
5
6
7
15:57:23.174 [ShardingSphere-Command-3] INFO ShardingSphere-SQL - Logic SQL: INSERT INTO t_user  ( uname )  VALUES  ( '李思思' )
15:57:23.174 [ShardingSphere-Command-3] INFO ShardingSphere-SQL - SQLStatement: MySQLInsertStatement(setAssignment=Optional.empty, onDuplicateKeyColumns=Optional.empty)
15:57:23.175 [ShardingSphere-Command-3] INFO ShardingSphere-SQL - Actual SQL: write_ds ::: INSERT INTO t_user ( uname ) VALUES ( '李思思' )

15:57:23.217 [ShardingSphere-Command-3] INFO ShardingSphere-SQL - Logic SQL: SELECT id,uname FROM t_user
15:57:23.217 [ShardingSphere-Command-3] INFO ShardingSphere-SQL - SQLStatement: MySQLSelectStatement(table=Optional.empty, limit=Optional.empty, lock=Optional.empty, window=Optional.empty)
15:57:23.217 [ShardingSphere-Command-3] INFO ShardingSphere-SQL - Actual SQL: write_ds ::: SELECT id,uname FROM t_user
负载均衡测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@SpringBootTest
class ReadWriteTest {

@Autowired
private UserMapper userMapper;

/**
* 负载均衡测试
*/
@Test
public void testLoadBalance() {
List<User> users1 = userMapper.selectList(null); // 默认采用轮询负载均衡算法,执行第一次查询会路由到 slave1 库
List<User> users2 = userMapper.selectList(null); // 默认采用轮询负载均衡算法,执行第二次查询会路由到 slave2 库
}

}

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

1
2
3
4
5
6
7
16:48:01.373 [ShardingSphere-Command-2] INFO ShardingSphere-SQL - Logic SQL: SELECT  id,uname  FROM t_user
16:48:01.374 [ShardingSphere-Command-2] INFO ShardingSphere-SQL - SQLStatement: MySQLSelectStatement(table=Optional.empty, limit=Optional.empty, lock=Optional.empty, window=Optional.empty)
16:48:01.374 [ShardingSphere-Command-2] INFO ShardingSphere-SQL - Actual SQL: read_ds_0 ::: SELECT id,uname FROM t_user

16:48:01.448 [ShardingSphere-Command-3] INFO ShardingSphere-SQL - Logic SQL: SELECT id,uname FROM t_user
16:48:01.449 [ShardingSphere-Command-3] INFO ShardingSphere-SQL - SQLStatement: MySQLSelectStatement(table=Optional.empty, limit=Optional.empty, lock=Optional.empty, window=Optional.empty)
16:48:01.449 [ShardingSphere-Command-3] INFO ShardingSphere-SQL - Actual SQL: read_ds_1 ::: SELECT id,uname FROM t_user