大纲
前言
学习资源
ShardingSphere 核心概念
接入端
Apache ShardingSphere 由 ShardingSphere-JDBC 和 ShardingSphere-Proxy 这两款既能够独立部署,又支持混合部署配合使用的产品组成。它们均提供标准化的基于数据库作为存储节点的增量功能,可适用于如 Java 同构、异构语言、云原生等各种多样化的应用场景。
ShardingSphere-JDBC
ShardingSphere-JDBC 定位为轻量级 Java 框架,在 Java 的 JDBC 层提供的额外服务。它使用客户端直连数据库,以 Jar 包形式提供服务,无需额外部署和依赖,可理解为增强版的 JDBC 驱动,完全兼容 JDBC 和各种 ORM 框架。
- 适用于任何基于 JDBC 的 ORM 框架,如:JPA、Hibernate、Mybatis、Spring JDBC Template 或直接使用 JDBC;
- 支持任何第三方的数据库连接池,如:C3P0、DBCP、Druid、BoneCP、HikariCP 等;
- 支持任意实现了 JDBC 规范的数据库,目前支持 MySQL、PostgreSQL、Oracle、SQLServer 以及任何可使用 JDBC 访问的数据库。
![]()
ShardingSphere-Proxy
ShardingSphere-Proxy 定位为透明化的 数据库代理端,提供封装了数据库二进制协议的服务端版本,用于完成对异构语言的支持。目前提供 MySQL 和 PostgreSQL 版本,它可以使用任何兼容 MySQL / PostgreSQL 协议的访问客户端(如:MySQL Command Client、MySQL Workbench、Navicat 等)操作数据,对 DBA 更加友好。
- 对应用程序完全透明,可直接当作 MySQL / PostgreSQL 使用;
- 适用于任何兼容 MySQL / PostgreSQL 协议的客户端;
- ShardingSphere-Proxy 的优势在于对异构语言的支持,以及为 DBA 提供可操作入口。
![]()
混合架构
ShardingSphere-JDBC 采用无中心化架构,与应用程序共享资源,适用于 Java 开发的高性能的轻量级 OLTP 应用;ShardingSphere-Proxy 提供静态入口以及异构语言的支持,独立于应用程序部署,适用于 OLAP 应用以及对分片数据库进行管理和运维的场景。
Apache ShardingSphere 是多接入端共同组成的生态圈。通过混合使用 ShardingSphere-JDBC 和 ShardingSphere-Proxy,并采用同一注册中心统一配置分片策略,能够灵活的搭建适用于各种场景的应用系统,使得架构师更加自由地调整适合于当前业务的最佳系统架构。
![]()
运行模式
背景说明
- Apache ShardingSphere 是一套完善的产品,使用场景非常广泛。 除生产环境的集群部署之外,还为工程师在开发和自动化测试等场景提供相应的运行模式。
- Apache ShardingSphere 提供的 3 种运行模式分别是内存模式、单机模式和集群模式。
内存模式
- 初始化配置或执行 SQL 等造成的元数据结果变更的操作,仅在当前进程中生效。
- 适用于集成测试的环境启动,方便开发人员在整合功能测试中集成 Apache ShardingSphere 而无需清理运行痕迹。
单机模式
- 能够将数据源和规则等元数据信息持久化,但无法将元数据同步至多个 Apache ShardingSphere 实例,无法在集群环境中相互感知。
- 通过某一实例更新元数据之后,会导致其他实例由于获取不到最新的元数据而产生不一致的错误。
- 适用于工程师在本地搭建 Apache ShardingSphere 环境。
集群模式
- 提供了多个 Apache ShardingSphere 实例之间的元数据共享和分布式场景下状态协调的能力。
- 在真实部署上线的生产环境中,必须使用集群模式。它能够提供计算能力水平扩展和高可用等分布式系统必备的能力。
- 集群环境需要通过独立部署的注册中心(比如 ZooKeeper、Etcd)来存储元数据和协调节点状态。
DistSQL
背景
- DistSQL(Distributed SQL)是 Apache ShardingSphere 特有的操作语言。
- 它与标准 SQL 的使用方式完全一致,用于提供增量功能的 SQL 级别操作能力。
挑战
- 灵活的规则配置和资源管控能力是 Apache ShardingSphere 的特点之一。在使用
4.x 及其之前版本时,开发者虽然可以像使用原生数据库一样操作数据,但却需要通过本地文件或注册中心配置资源和规则。然而,操作习惯变更,对于运维工程师并不友好。 - DistSQL 让用户可以像操作数据库一样操作 Apache ShardingSphere,使其从面向开发人员的框架和中间件转变为面向运维人员的数据库产品。
- DistSQL 细分为 RDL、RQL 和 RAL 三种类型。
- RDL(Resource & Rule Definition Language)负责资源和规则的创建、修改和删除;
- RQL(Resource & Rule Query Language)负责资源和规则的查询和展现;
- RAL(Resource & Rule Administration Language)负责 Hint、事务类型切换、分片执行计划查询等管理功能。
目标
- 打破中间件和数据库之间的界限,让开发者像使用数据库一样使用 Apache ShardingSphere,是 DistSQL 的设计目标。
可插拔架构
背景
- 在 Apache ShardingSphere 中,很多功能实现类的加载方式是通过 SPI(Service Provider Interface)注入的方式完成的。
- SPI 是一种为了被第三方实现或扩展的 API,它可以用于实现框架扩展或组件替换。
挑战
- 可插拔架构对程序架构设计的要求非常高,需要将各个模块相互独立,互不感知,并且通过一个可插拔内核,以叠加的方式将各种功能组合使用。设计一套将功能开发完全隔离的架构体系,既可以最大限度的将开源社区的活力激发出来,也能够保障项目的质量。
- Apache ShardingSphere
5.x 版本开始致力于可插拔架构,项目的功能组件能够灵活的以可插拔的方式进行扩展。目前,数据分片、读写分离、数据加密、影子库压测等功能,以及对 MySQL、PostgreSQL、SQLServer、Oracle 等 SQL 与协议的支持,均通过插件的方式织入项目。Apache ShardingSphere 目前已提供数十个 SPI 作为系统的扩展点,而且仍在不断增加中。
目标
- 让开发者能够像使用积木一样定制属于自己的独特系统,是 Apache ShardingSphere 可插拔架构的设计目标。
![]()
实现
- Apache ShardingSphere 的可插拔架构划分为三层,它们分别是:L1 内核层、L2 功能层、L3 生态层。
- L1 内核层:
- 是数据库基本能力的抽象,其所有组件均必须存在,但具体实现方式可通过可插拔的方式更换。
- 主要包括查询优化器、分布式事务引擎、分布式执行引擎、权限引擎和调度引擎等。
- L2 功能层:
- 用于提供增量能力,其所有组件均是可选的,可以包含零至多个组件。组件之间完全隔离,互无感知,多组件可通过叠加的方式相互配合使用。
- 主要包括数据分片、读写分离、数据加密、影子库等。用户自定义功能可完全面向 Apache ShardingSphere 定义的顶层接口进行定制化扩展,而无需改动内核代码。
- L3 生态层:
- 用于对接和融入现有数据库生态,包括数据库协议、SQL 解析器和存储适配器,分别对应于 Apache ShardingSphere 以数据库协议提供服务的方式、SQL 方言操作数据的方式以及对接存储节点的数据库类型。
ShardingSphere-JDBC 使用
读写分离使用案例
提示
本节将演示 SpringBoot + MyBatis Plus 如何整合 ShardingSphere-JDBC,并实现 读写分离,整体架构如下图所示。完整的案例代码可以直接从 GitHub 下载对应章节 sharding-sphere-lesson-01。
![]()
准备工作
版本说明
本案例所使用的组件版本如下表所示:
| 组件 | 版本说明 |
|---|
| JDK | 11 |
| MySQL | 8.0.29 |
| SpringBoot | 2.7.18 |
| ShardingSphere-JDBC | 5.1.1 |
数据库规划
本案例是在 MySQL 一主二从的架构上实现的,数据库的规划如下图所示:
![]()
| 数据库服务器 | 主从角色 | IP | 端口 |
|---|
| 主服务器 | 主库(master) | 192.168.2.191 | 3306 |
| 从服务器一 | 从库(slave1) | 192.168.2.191 | 3307 |
| 从服务器二 | 从库(slave2) | 192.168.2.191 | 3308 |
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);
|
案例代码
添加依赖
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 57 58 59 60 61 62 63
| <properties> <spring-boot.version>2.7.18</spring-boot.version> <shardingsphere.version>5.1.1</shardingsphere.version> <mysql-connector.version>8.2.0</mysql-connector.version> <mybatis-plus.version>3.3.1</mybatis-plus.version> </properties>
<dependencyManagement> <dependencies> <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> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <version>${mysql-connector.version}</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>${mybatis-plus.version}</version> </dependency> <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId> <version>${shardingsphere.version}</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <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 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
| spring.application.name=sharging-jdbc-demo
spring.profiles.active=dev
spring.shardingsphere.mode.type=Memory
spring.shardingsphere.datasource.names=master,slave1,slave2
spring.shardingsphere.datasource.master.type=com.zaxxer.hikari.HikariDataSource spring.shardingsphere.datasource.master.driver-class-name=com.mysql.jdbc.Driver spring.shardingsphere.datasource.master.jdbc-url=jdbc:mysql://192.168.2.191:3306/db_user spring.shardingsphere.datasource.master.username=root spring.shardingsphere.datasource.master.password=123456
spring.shardingsphere.datasource.slave1.type=com.zaxxer.hikari.HikariDataSource spring.shardingsphere.datasource.slave1.driver-class-name=com.mysql.jdbc.Driver spring.shardingsphere.datasource.slave1.jdbc-url=jdbc:mysql://192.168.2.191:3307/db_user spring.shardingsphere.datasource.slave1.username=root spring.shardingsphere.datasource.slave1.password=123456
spring.shardingsphere.datasource.slave2.type=com.zaxxer.hikari.HikariDataSource spring.shardingsphere.datasource.slave2.driver-class-name=com.mysql.jdbc.Driver spring.shardingsphere.datasource.slave2.jdbc-url=jdbc:mysql://192.168.2.191:3308/db_user spring.shardingsphere.datasource.slave2.username=root spring.shardingsphere.datasource.slave2.password=123456
spring.shardingsphere.rules.readwrite-splitting.data-sources.myds.type=Static
spring.shardingsphere.rules.readwrite-splitting.data-sources.myds.props.write-data-source-name=master
spring.shardingsphere.rules.readwrite-splitting.data-sources.myds.props.read-data-source-names=slave1,slave2
spring.shardingsphere.rules.readwrite-splitting.data-sources.myds.load-balancer-name=alg_round
spring.shardingsphere.rules.readwrite-splitting.load-balancers.alg_round.type=ROUND_ROBIN spring.shardingsphere.rules.readwrite-splitting.load-balancers.alg_random.type=RANDOM spring.shardingsphere.rules.readwrite-splitting.load-balancers.alg_weight.type=WEIGHT spring.shardingsphere.rules.readwrite-splitting.load-balancers.alg_weight.props.slave1=1 spring.shardingsphere.rules.readwrite-splitting.load-balancers.alg_weight.props.slave2=1
spring.shardingsphere.props.sql-show=true
|
- ShardingSphere-JDBC 默认提供了以下三种负载均衡算法
| 负载均衡算法 | 工作原理 | 适用场景 |
|---|
| ROUND_ROBIN(轮询) | 多个从库按顺序依次分配请求,循环使用 | 从库性能相近,需要均衡分配请求 |
| RANDOM(随机) | 每次随机选择一个从库处理请求 | 请求量较小或对负载均衡精度要求不高 |
| WEIGHT(加权) | 按配置的权重比例分配请求,需要额外配置权重(性能高的从库可设更大权重) | 从库性能不一致,需要根据性能灵活分配流量 |
测试代码
读写分离测试
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); }
}
|
测试代码运行后的输出结果如下:
1 2 3 4 5 6
| 2025-09-18 22:02:39.266 INFO 65403 --- [ main] ShardingSphere-SQL : Logic SQL: INSERT INTO t_user ( uname ) VALUES ( ? ) 2025-09-18 22:02:39.267 INFO 65403 --- [ main] ShardingSphere-SQL : SQLStatement: MySQLInsertStatement(setAssignment=Optional.empty, onDuplicateKeyColumns=Optional.empty) 2025-09-18 22:02:39.268 INFO 65403 --- [ main] ShardingSphere-SQL : Actual SQL: master ::: INSERT INTO t_user ( uname ) VALUES ( ? ) ::: [张三丰] 2025-09-18 22:02:39.486 INFO 65403 --- [ main] ShardingSphere-SQL : Logic SQL: SELECT id,uname FROM t_user 2025-09-18 22:02:39.487 INFO 65403 --- [ main] ShardingSphere-SQL : SQLStatement: MySQLSelectStatement(table=Optional.empty, limit=Optional.empty, lock=Optional.empty, window=Optional.empty) 2025-09-18 22:02:39.487 INFO 65403 --- [ main] ShardingSphere-SQL : Actual SQL: slave1 ::: SELECT id,uname FROM t_user
|
事务一致性测试
为了保证主从库间的事务一致性,避免跨库的分布式事务, 在 ShardingSphere-JDBC 的主从模型中,事务内的数据读写均使用主库。
- 不添加
@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;
@Test @Transactional public void testTransaction() { User user = new User(); user.setUname("李思思"); userMapper.insert(user);
List<User> list = userMapper.selectList(null); }
}
|
测试代码运行后的输出结果如下:
1 2 3 4 5 6
| 2022-09-18 22:08:56.139 INFO 62988 --- [ main] ShardingSphere-SQL : Logic SQL: INSERT INTO t_user ( uname ) VALUES ( ? ) 2022-09-18 22:08:56.139 INFO 62988 --- [ main] ShardingSphere-SQL : SQLStatement: MySQLInsertStatement(setAssignment=Optional.empty, onDuplicateKeyColumns=Optional.empty) 2022-09-18 22:08:56.139 INFO 62988 --- [ main] ShardingSphere-SQL : Actual SQL: master ::: INSERT INTO t_user ( uname ) VALUES ( ? ) ::: [李思思] 2022-09-18 22:08:56.274 INFO 62988 --- [ main] ShardingSphere-SQL : Logic SQL: SELECT id,uname FROM t_user 2022-09-18 22:08:56.274 INFO 62988 --- [ main] ShardingSphere-SQL : SQLStatement: MySQLSelectStatement(table=Optional.empty, limit=Optional.empty, lock=Optional.empty, window=Optional.empty) 2022-09-18 22:08:56.274 INFO 62988 --- [ main] ShardingSphere-SQL : Actual SQL: master ::: 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); List<User> users2 = userMapper.selectList(null); }
}
|
1 2 3 4 5 6
| 2022-09-18 22:24:08.781 INFO 66672 --- [ main] ShardingSphere-SQL : Logic SQL: SELECT id,uname FROM t_user 2022-09-18 22:24:08.781 INFO 66672 --- [ main] ShardingSphere-SQL : SQLStatement: MySQLSelectStatement(table=Optional.empty, limit=Optional.empty, lock=Optional.empty, window=Optional.empty) 2022-09-18 22:24:08.781 INFO 66672 --- [ main] ShardingSphere-SQL : Actual SQL: slave1 ::: SELECT id,uname FROM t_user 2022-09-18 22:24:08.853 INFO 66672 --- [ main] ShardingSphere-SQL : Logic SQL: SELECT id,uname FROM t_user 2022-09-18 22:24:08.853 INFO 66672 --- [ main] ShardingSphere-SQL : SQLStatement: MySQLSelectStatement(table=Optional.empty, limit=Optional.empty, lock=Optional.empty, window=Optional.empty) 2022-09-18 22:24:08.853 INFO 66672 --- [ main] ShardingSphere-SQL : Actual SQL: slave2 ::: SELECT id,uname FROM t_user
|