1. 基本概念
1.1 什么是ShardingSphere?
ShardingSphere是一个开源的分布式数据库中间件,提供了分布式数据库的跨节点分片和分布式事务解决方案。它由分片(Sharding)和球(Sphere)两个单词组成,分片表示数据分片,球表示这些分片组成的球形空间,象征着分布式数据库的全景视图。
ShardingSphere支持多种数据库的分片和分布式事务,包括关系型数据库(如MySQL、PostgreSQL等)、NoSQL数据库(如MongoDB、HBase等)、分布式消息队列(如Kafka)、分布式文件系统(如HDFS)等。它提供了完善的分片策略和事务管理机制,可以实现水平扩展和高可用性的分布式数据库架构。
ShardingSphere具有以下主要特点:
- 分片:支持水平分片和垂直分片,将数据分散存储在不同的节点上,提高了数据库的水平扩展性和性能。
- 分布式事务:提供了分布式事务解决方案,保证了跨分片的事务一致性和隔离性。
- 弹性伸缩:支持动态增加或减少节点,根据业务需求自由调整数据库规模。
- 高可用性:通过数据复制和故障转移等机制,保证了数据库的高可用性和容错性。
- 智能路由:根据分片策略和数据分布情况,智能地路由查询请求到相应的节点上,提高了查询效率。
总的来说,ShardingSphere是一个功能强大的分布式数据库中间件,为应用程序提供了灵活、高效和可靠的数据存储和访问解决方案。
1.2 分库分表
分库分表是一种数据库水平拆分和垂直拆分的策略,用于解决单一数据库容量和性能瓶颈的问题。它将一个大型的数据库按照一定的规则或者业务逻辑拆分成多个小型数据库或表,从而提高了数据库的性能、扩展性和可用性。
1.2.1 水平分表
水平分表是指将单个表中的数据按照某种规则拆分存储到多个物理表中,每个物理表只存储部分数据。
例如,可以按照用户ID、时间范围等将数据分散存储到不同的表中。水平分表的优点是可以有效减少单个表的数据量,提高了查询性能和并发处理能力。但是,需要对查询进行路由到正确的物理表,管理和维护成本相对较高。
1.2.2 水平分库
水平分库是指将一个数据库中的数据按照某种规则拆分存储到多个数据库实例中。每个数据库实例可以部署在不同的物理服务器上,实现数据的水平扩展。
例如,可以按照用户ID的哈希值或者区间将用户数据分散存储到不同的数据库中。水平分库的优点是可以有效提高数据库的扩展性和并发处理能力,降低单个数据库的负载压力。但是,需要解决跨库事务、数据一致性和查询路由等问题。
1.2.3 垂直分表
垂直分表是指将单个表中的列按照某种逻辑划分成多个表,每个表包含部分列。这种分表方式通常根据列的访问频率、数据关联性等因素进行划分。
例如,将一个课程表中的基本信息和描述信息拆分成两个表,分别存储课程的基本信息和描述信息。垂直分表的优点是可以减少单个表的列数,降低表的宽度,提高查询效率。但是,会增加表之间的关联查询成本,并且可能导致额外的数据一致性维护工作。
1.2.4 垂直分库
垂直分库是指将一个大型的数据库中的表按照某种逻辑划分成多个数据库,每个数据库包含部分表。这种分库方式通常根据业务模块或者数据访问模式进行划分。
例如,将课程相关的表存储在一个数据库中,将订单相关的表存储在另一个数据库中。垂直分库的优点是可以降低单个数据库的复杂度,提高数据库的并发处理能力。但是,可能会增加跨库查询的成本,并且需要考虑数据一致性和跨库事务的处理。
1.2.5 分库分表的应用和问题
应用:
- 在数据库设计的时候考虑垂直分库和垂直分表
- 随着数据库数据量的增加,不要马上考虑做水平切分,首先考虑缓存处理、读写分离、使用索引等方式,如果这些方式不等你根本解决问题,再考虑做水平分库和水平分表
问题:
- 跨节点连接查询问题(分页、排序)
- 多数据源管理问题
2. Sharding-JDBC 分库分表操作
2.1 什么是Sharding-JDBC?
Sharding-JDBC 是当当网开源的适用于微服务的分布式数据访问基础类库,完整的实现了分库分表,读写分离和分布式主键功能,并初步实现了柔性事务。
从 2016 年开源至今,在经历了整体架构的数次精炼以及稳定性打磨后,如今它已积累了足够的底蕴。
2.2 水平分表
2.2.1 环境搭建
1. 创建SpringBoot工程
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.gzb</groupId>
<artifactId>sharding-jdbc</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.2.1.RELEASE</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.20</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.0.0-RC1</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</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>
<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>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<mainClass>com.gzb.shardingjdbc.ShardingJdbcApplication</mainClass>
<skip>true</skip>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
2. 创建数据库表
创建数据库 course_db
创建数据库表course_1、course_2
CREATE TABLE course_1 (
cid BIGINT(20) PRIMARY KEY,
cname VARCHAR(50) NOT NULL,
user_id BIGINT(20) NOT NULL,
cstatus VARCHAR(10) NOT NULL
)
3. 创建实体类和Mapper
Course
import lombok.Data;
@Data
public class Course {
private Long cid;
private String cname;
private Long userId;
private String cstatus;
}
CourseMapper
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.gzb.shardingjdbc.entity.Course;
public interface CourseMapper extends BaseMapper<Course> {
}
启动类上加上注解:@MapperScan(basePackages = "com.gzb.shardingjdbc.mapper")
2.2.2 测试
1. application.properties配置文件
#这个配置没加可能会报错
spring.main.allow-bean-definition-overriding=true
# 配置真实数据源
spring.shardingsphere.datasource.names=m1
# 配置第 1 个数据源
spring.shardingsphere.datasource.m1.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m1.url=jdbc:mysql://localhost:3306/course_db?serverTimezone=GMT%2B8&useSSL=false
spring.shardingsphere.datasource.m1.username=root
spring.shardingsphere.datasource.m1.password=root
# 标准分片表配置
# 由数据源名 + 表名组成,以小数点分隔。多个表以逗号分隔,支持 inline 表达式。缺省表示使用已知数据源与逻辑表名称生成数据节点,用于广播表(即每个库中都需要一个同样的表用于关联查询,多为字典表)或只分库不分表且所有库的表结构完全一致的情况
# <table-name>
spring.shardingsphere.sharding.tables.course.actual-data-nodes=m1.course_$->{1..2}
# 注意事项
# 行表达式标识符可以使用 ${...} 或 $->{...},但前者与 Spring 本身的属性文件占位符冲突,因此在 Spring 环境中使用行表达式标识符建议使用 $->{...}。
# 分布式序列策略配置: 指定course表里主键id生成策略为雪花
# 分布式序列列名称 <table-name>
spring.shardingsphere.sharding.tables.course.key-generator.column=cid
# 分布式序列算法名称 <table-name>
spring.shardingsphere.sharding.tables.course.key-generator.type=SNOWFLAKE
# 分表策略,# 分库策略,以user_id为分片键,分片策略为user_id % 2 + 1,user_id为偶数操作m1数据源,否则操作m2。
spring.shardingsphere.sharding.tables.course.table-strategy.inline.sharding-column=cid
spring.shardingsphere.sharding.tables.course.table-strategy.inline.algorithm-expression=course_$->{cid % 2 + 1}
# 打开sql输出日志
spring.shardingsphere.props.sql.show=true
2. 测试代码
@SpringBootTest
class ShardingJdbcApplicationTests {
@Autowired
private CourseMapper courseMapper;
@Test
void addCourse() {
for (int i = 1; i <= 5; i++) {
Course course = new Course();
course.setCname("Sharding-JDBC" + i);
course.setUserId(1L);
course.setCstatus("normal");
courseMapper.insert(course);
}
}
@Test
void queryCourse() {
QueryWrapper<Course> queryWrapper = new QueryWrapper<Course>()
.in("cid", 965232595870679041L, 965232596210417664L);
List<Course> courses = courseMapper.selectList(queryWrapper);
courses.forEach(System.out::println);
}
}
2.3 水平分库
2.3.1 环境搭建
1. 创建数据库
原来的course表结构不变
CREATE TABLE course_1 (
cid BIGINT(20) PRIMARY KEY,
cname VARCHAR(50) NOT NULL,
user_id BIGINT(20) NOT NULL,
cstatus VARCHAR(10) NOT NULL
)
数据库规则:user_id 为偶数添加到edu_db_1
数据库,为奇数添加到edu_db_2
数据库
表规则:cid 为偶数添加到course_1
表,为奇数添加到course_2
表
2.3.2 测试
1. application.properties文件
#这个配置没加可能会报错
spring.main.allow-bean-definition-overriding=true
# 配置真实数据源
spring.shardingsphere.datasource.names=m1,m2
# 配置第 1 个数据源
spring.shardingsphere.datasource.m1.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m1.url=jdbc:mysql://localhost:3306/edu_db_1?serverTimezone=GMT%2B8&useSSL=false
spring.shardingsphere.datasource.m1.username=root
spring.shardingsphere.datasource.m1.password=root
# 配置第 2 个数据源
spring.shardingsphere.datasource.m2.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m2.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m2.url=jdbc:mysql://localhost:3306/edu_db_2?serverTimezone=GMT%2B8&useSSL=false
spring.shardingsphere.datasource.m2.username=root
spring.shardingsphere.datasource.m2.password=root
# 标准分片表配置
# 由数据源名 + 表名组成,以小数点分隔。多个表以逗号分隔,支持 inline 表达式。缺省表示使用已知数据源与逻辑表名称生成数据节点,用于广播表(即每个库中都需要一个同样的表用于关联查询,多为字典表)或只分库不分表且所有库的表结构完全一致的情况
# <table-name>
spring.shardingsphere.sharding.tables.course.actual-data-nodes=m$->{1..2}.course_$->{1..2}
# 注意事项
# 行表达式标识符可以使用 ${...} 或 $->{...},但前者与 Spring 本身的属性文件占位符冲突,因此在 Spring 环境中使用行表达式标识符建议使用 $->{...}。
# 分布式序列策略配置: 指定course表里主键id生成策略为雪花
# 分布式序列列名称 <table-name>
spring.shardingsphere.sharding.tables.course.key-generator.column=cid
# 分布式序列算法名称 <table-name>
spring.shardingsphere.sharding.tables.course.key-generator.type=SNOWFLAKE
# 分表策略以cid为分片键,分片策略为cid % 2 + 1,user_id为偶数操作course1数据源,否则操作course2
spring.shardingsphere.sharding.tables.course.table-strategy.inline.sharding-column=cid
spring.shardingsphere.sharding.tables.course.table-strategy.inline.algorithm-expression=course_$->{cid % 2 + 1}
#指定表分库
spring.shardingsphere.sharding.tables.course.database-strategy.inline.sharding-column=user_id
spring.shardingsphere.sharding.tables.course.database-strategy.inline.algorithm-expression=m$->{user_id % 2 + 1}
# 打开sql输出日志
spring.shardingsphere.props.sql.show=true
2. 测试代码
// ========================测试水平分库===============================
@Test
void insertCourse() {
Course course = new Course();
course.setCname("Sharding-JDBC1");
course.setUserId(2L);
course.setCstatus("normal");
courseMapper.insert(course);
}
@Test
void selectCourse() {
QueryWrapper<Course> queryWrapper = new QueryWrapper<Course>()
.in("cid", 965245460681850881L, 965245329144283137L);
List<Course> courses = courseMapper.selectList(queryWrapper);
courses.forEach(System.out::println);
}
2.4 垂直切分
2.4.1 搭建环境
1. 创建数据库
t_user
CREATE TABLE `t_user` (
`user_id` bigint(20) NOT NULL,
`username` varchar(50) NOT NULL,
`ustatus` varchar(50) NOT NULL,
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
单独把用户信息表拆分出来作为一个新的数据库,查询用户信息的时候,查询user_db
的t_user
表
2. 创建实体类和Mapper
@Data
@TableName("t_user")
public class User {
private Long userId;
private String username;
private String ustatus;
}
public interface UserMapper extends BaseMapper<User> {
}
2.4.2 测试
1. application.properties
#这个配置没加可能会报错
spring.main.allow-bean-definition-overriding=true
# 配置真实数据源
spring.shardingsphere.datasource.names=m1,m2,m0
# 配置第 1 个数据源
spring.shardingsphere.datasource.m1.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m1.url=jdbc:mysql://localhost:3306/edu_db_1?serverTimezone=GMT%2B8&useSSL=false
spring.shardingsphere.datasource.m1.username=root
spring.shardingsphere.datasource.m1.password=root
# 配置第 2 个数据源
spring.shardingsphere.datasource.m2.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m2.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m2.url=jdbc:mysql://localhost:3306/edu_db_2?serverTimezone=GMT%2B8&useSSL=false
spring.shardingsphere.datasource.m2.username=root
spring.shardingsphere.datasource.m2.password=root
# 配置第 3 个数据源
spring.shardingsphere.datasource.m0.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m0.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m0.url=jdbc:mysql://localhost:3306/user_db?serverTimezone=GMT%2B8&useSSL=false
spring.shardingsphere.datasource.m0.username=root
spring.shardingsphere.datasource.m0.password=root
# 用户信息数据库配置
spring.shardingsphere.sharding.tables.t_user.actual-data-nodes=m0.t_user
spring.shardingsphere.sharding.tables.t_user.key-generator.column=user_id
spring.shardingsphere.sharding.tables.t_user.key-generator.type=SNOWFLAKE
spring.shardingsphere.sharding.tables.t_user.table-strategy.inline.sharding-column=user_id
spring.shardingsphere.sharding.tables.t_user.table-strategy.inline.algorithm-expression=t_user
# 标准分片表配置
# 由数据源名 + 表名组成,以小数点分隔。多个表以逗号分隔,支持 inline 表达式。缺省表示使用已知数据源与逻辑表名称生成数据节点,用于广播表(即每个库中都需要一个同样的表用于关联查询,多为字典表)或只分库不分表且所有库的表结构完全一致的情况
# <table-name>
spring.shardingsphere.sharding.tables.course.actual-data-nodes=m$->{1..2}.course_$->{1..2}
# 注意事项
# 行表达式标识符可以使用 ${...} 或 $->{...},但前者与 Spring 本身的属性文件占位符冲突,因此在 Spring 环境中使用行表达式标识符建议使用 $->{...}。
# 分布式序列策略配置: 指定course表里主键id生成策略为雪花
# 分布式序列列名称 <table-name>
spring.shardingsphere.sharding.tables.course.key-generator.column=cid
# 分布式序列算法名称 <table-name>
spring.shardingsphere.sharding.tables.course.key-generator.type=SNOWFLAKE
# 分表策略以cid为分片键,分片策略为cid % 2 + 1,user_id为偶数操作course1数据源,否则操作course2
spring.shardingsphere.sharding.tables.course.table-strategy.inline.sharding-column=cid
spring.shardingsphere.sharding.tables.course.table-strategy.inline.algorithm-expression=course_$->{cid % 2 + 1}
#指定表分库
spring.shardingsphere.sharding.tables.course.database-strategy.inline.sharding-column=user_id
spring.shardingsphere.sharding.tables.course.database-strategy.inline.algorithm-expression=m$->{user_id % 2 + 1}
# 打开sql输出日志
spring.shardingsphere.props.sql.show=true
2. 测试代码
// ========================测试垂直切分===============================
@Test
void insertUser() {
User user = new User();
user.setUsername("Jack");
user.setUstatus("dead");
userMapper.insert(user);
}
@Test
void selectUser() {
QueryWrapper<User> queryWrapper = new QueryWrapper<User>()
.in("user_id", 965289965497876481L, 965245329144283137L);
List<User> userList = userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
}
2.5 公共表
2.5.1 搭建环境
概念:
- 存储固定数据的表,表数据很少发生变化,查询时经常进行关联
- 在每个数据表中创建出相同结构公共表
1. 建表
在edu_db_1
、edu_db_2
、user_db
中创建公共表 t_udict
t_udict
CREATE TABLE `t_udict` (
`dictid` bigint(20) NOT NULL,
`ustatus` varchar(100) NOT NULL,
`uvalue` varchar(100) NOT NULL,
PRIMARY KEY (`dictid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2. 创建实体类和Mapper
@TableName("t_udict")
@Data
public class Udict {
private Long dictid;
private String ustatus;
private String uvalue;
}
public interface UdictMapper extends BaseMapper<Udict> {
}
2.5.2 测试
1. application.properties
在 2.4.1 配置文件的基础上加入以下配置
# 配置公共表
spring.shardingsphere.sharding.broadcast-tables=t_udict
spring.shardingsphere.sharding.tables.t_udict.key-generator.column=dictid
spring.shardingsphere.sharding.tables.t_udict.key-generator.type=SNOWFLAKE
2. 测试代码
@Test
void insertPublic() {
Udict udict = new Udict();
udict.setUstatus("dead");
udict.setUvalue("寄了");
// 当插入公共表数据时,会在三个库中都插入
udictMapper.insert(udict);
}
@Test
void deletePublic() {
QueryWrapper<Udict> queryWrapper = new QueryWrapper<Udict>().eq("dictid",
965298608326836225L);
// 删除公共表数据,会将三个表的数据都删除
udictMapper.delete(queryWrapper);
}
2.6 读写分离
1. 搭建MySQL主从架构
(略)
2. 添加修改配置
# 主库从库逻辑数据源定义 ds0 为 user_db
spring.shardingsphere.sharding.master-slave-rules.ds0.master-data-sourcename=m0
spring.shardingsphere.sharding.master-slave-rules.ds0.slave-data-sourcenames=s0
# 配置 user_db 数据库里面 t_user 专库专表
#spring.shardingsphere.sharding.tables.t_user.actual-data-nodes=m$->{0}.t_user
# t_user 分表策略,固定分配至 ds0 的 t_user 真实表
spring.shardingsphere.sharding.tables.t_user.actual-data-nodes=ds0.t_user