分库分表(1) --- ShardingSphere(理论)
ShardingSphere
在中小企业需要分库分表的时候用的会比较多,因为它维护成本低,不需要额外增派人手;而且目前社区也还一直在开发和维护,还算是比较活跃。
但是中大型公司一般会选择选用 Mycat 这类 proxy 层方案,因为可能大公司系统和项目非常多,团队很大,人员充足,那么最好是专门弄个人来研究和维护 Mycat,
然后大量项目直接透明使用即可。
一、ShardingSphere概念
1、概念
ShardingSphere是一套开源的分布式数据库中间件解决方案组成的生态圈,它由Sharding-JDBC、Sharding-Proxy 和 Sharding-Sidecar这3款相互独立的产品组成。
他们均提供标准化的数据分片
、分布式事务
和 数据库治理功能
,可适用于如Java同构、异构语言、云原生等各种多样化的应用场景。
如图
2、功能列表
数据分片
- 分库 & 分表
- 读写分离
- 分片策略定制化
- 无中心化分布式主键
分布式事务
- 标准化事务接口
- XA强一致事务
- 柔性事务
数据库治理
- 配置动态化
- 编排 & 治理
- 数据脱敏
- 可视化链路追踪
- 弹性伸缩(规划中)
3、项目状态
二、分库分表---结果归并
概念
将从各个数据节点获取的多数据结果集,组合成为一个结果集并正确的返回至请求客户端,称为结果归并。
我们在实现分库分表之后,遍历
、排序
、分组
、分页
和 聚合
操作变成不在一张表上进行SQL,而是多张表执行的结果进行归并。
所以我们来看下ShardingSphere实现这些操作的原理。
1、遍历归并
它是最为简单的归并方式。 只需将多个数据结果集合并为一个单向链表
即可。在遍历完成链表中当前数据结果集之后,将链表元素后移一位,继续遍历下一个数据结果集即可。
2、排序归并
由于在SQL中存在ORDER BY
语句,每个数据结果集自身是有序的,所以我们要做的就是对多个有序的数组进行排序
ShardingSphere在对排序的查询进行归并时,将每个结果集的当前数据值进行比较(通过实现Java的Comparable接口完成),并将其放入优先级队列
。
每次获取下一条数据时,只需将队列顶端结果集的游标下移,并根据新游标重新进入优先级排序队列找到自己的位置即可。
举例
下图是一个通过分数进行排序的示例图。 图中展示了3张表返回的数据结果集,每个数据结果集已经根据分数排序完毕,但是3个数据结果集之间是无序的。
将3个数据结果集的当前游标指向的数据值进行排序,并放入优先级队列
,t_score_0的第一个数据值最大,t_score_2的第一个数据值次之,t_score_1的第一个数据值最小,
因此优先级队列根据t_score_0,t_score_2和t_score_1的方式排序队列。
如图
下图则展现了进行next调用的时候,排序归并是如何进行的。 通过图中我们可以看到,当进行第一次next调用时,排在队列首位的t_score_0将会被弹出队列,并且将当前
游标指向的数据值(也就是100)返回至查询客户端,并且将游标下移一位(90)之后,重新放入优先级队列。根据当前数值,t_score_0排列在队列的最后一位。 之前队列中
排名第二的t_score_2的数据结果集则自动排在了队列首位。
在进行第二次next时,只需要将目前排列在队列首位的t_score_2弹出队列,并且将其数据结果集游标指向的值返回至客户端,并下移游标,继续加入队列排队,以此类推。
当一个结果集中已经没有数据了,则无需再次加入队列。
可以看到,ShardingSphere的排序归并,是在维护数据结果集的纵轴和横轴这两个维度的有序性。
纵轴
是指每个数据结果集本身,它是天然有序的,它通过包含ORDER BY
的SQL所获取。
横轴
是指每个数据结果集当前游标所指向的值,它需要通过优先级队列
来维护其正确顺序。 每一次数据结果集当前游标的下移都需要将该数据结果集重新放入优先级队列排序,
而只有排列在队列首位的数据结果集才可能发生游标下移的操作。
3 、分组归并
分组归并的情况最为复杂,它分为流式分组归并
和内存分组归并
。 流式分组归并要求SQL的排序项与分组项的字段以及排序类型(ASC或DESC)必须保持一致,否则只能
通过内存归并才能保证其数据的正确性。
举例
假设根据科目分片,表结构中包含考生的姓名(为了简单起见,不考虑重名的情况)和分数。通过SQL获取每位考生的总分,可通过如下SQL:
SELECT name, SUM(score) FROM t_score GROUP BY name ORDER BY name;
在分组项与排序项完全一致的情况下,取得的数据是连续的,分组所需的数据全数存在于各个数据结果集的当前游标所指向的数据值,因此可以采用流式归并。如下图所示
进行归并时,逻辑与排序归并类似。 下图展现了进行next调用的时候,流式分组归并是如何进行的。
通过图中我们可以看到,当进行第一次next调用时,排在队列首位的t_score_java将会被弹出队列,并且将分组值同为“Jetty”的其他结果集中的数据一同弹出队列。 在获取了
所有的姓名为“Jetty”的同学的分数之后,进行累加操作,那么,在第一次next调用结束后,取出的结果集是“Jetty”的分数总和。 与此同时,所有的数据结果集中的游标都将
下移至数据值“Jetty”的下一个不同的数据值,并且根据数据结果集当前游标指向的值进行重排序。 因此,包含名字顺着第二位的“John”的相关数据结果集则排在的队列的前列。
流式分组归并与排序归并的区别仅仅在于两点:
- 它会一次性的将多个数据结果集中的分组项相同的数据全数取出。
- 它需要根据聚合函数的类型进行聚合计算。
4、聚合归并
无论是流式分组归并还是内存分组归并,对聚合函数的处理都是一致的。 除了分组的SQL之外,不进行分组的SQL也可以使用聚合函数。 因此,聚合归并是在之前介绍的归并类
的之上追加的归并能力,即装饰者模式
。聚合函数可以归类为比较、累加和求平均值这3种类型。
比较类型的聚合函数是指MAX
和MIN
。它们需要对每一个同组的结果集数据进行比较,并且直接返回其最大或最小值即可。
累加类型的聚合函数是指SUM
和COUNT
。它们需要将每一个同组的结果集数据进行累加。
求平均值的聚合函数只有AVG
。它必须通过SQL改写的SUM
和COUNT
进行计算,相关内容已在SQL改写的内容中涵盖,不再赘述。
5、分页归并
上文所述的所有归并类型都可能进行分页。 分页也是追加在其他归并类型之上的装饰器,ShardingSphere通过装饰者模式
来增加对数据结果集进行分页的能力。 分页归并负责
将无需获取的数据过滤掉。
ShardingSphere的分页功能比较容易让使用者误解,用户通常认为分页归并会占用大量内存。 在分布式的场景中,将LIMIT 10000000, 10
改写为LIMIT 0, 10000010
,
才能保证其数据的正确性。 用户非常容易产生ShardingSphere会将大量无意义的数据加载至内存中,造成内存溢出风险的错觉。 其实,通过流式归并的原理可知,会将
数据全部加载到内存中的只有内存分组归并这一种情况。 而通常来说,进行OLAP的分组SQL,不会产生大量的结果数据,它更多的用于大量的计算,以及少量结果产出的场景。
除了内存分组归并这种情况之外,其他情况都通过流式归并获取数据结果集,因此ShardingSphere会通过结果集的next方法将无需取出的数据全部跳过,并不会将其存入内存。
但同时需要注意的是,由于排序的需要,大量的数据仍然需要传输到ShardingSphere的内存空间。 因此,采用LIMIT这种方式分页,并非最佳实践
。 由于LIMIT并不能通过索引
查询数据,因此如果可以保证ID的连续性,通过ID进行分页是比较好的解决方案
,例如:
SELECT * FROM t_order WHERE id > 100000 AND id <= 100010 ORDER BY id;
或通过记录上次查询结果的最后一条记录的ID进行下一页的查询,例如:
SELECT * FROM t_order WHERE id > 10000000 LIMIT 10;
6、总结
用最后一张图来总结归并引擎的整体结构划分
补充
有关ShardingSphere其它的知识概述这里就不在讲了,这篇文章也是完全根据官方文档加上个人理解写的,所以想想要更加详细的了解可以去看官网。
有关ShardingSphere概念前面写了两篇博客:
2、 分库分表(2) --- ShardingSphere(理论)
下面就这个项目做个整体简单介绍,并在文章最下方附上项目Github地址
。
一、项目概述
1、技术架构
项目总体技术选型
SpringBoot2.0.6 + shardingsphere4.0.0-RC1 + Maven3.5.4 + MySQL + lombok(插件)
2、项目说明
场景
如果实际项目中Mysql是 Master-Slave (主从)部署的,那么数据保存到Master库,Master库数据同步数据到Slave库,数据读取到Slave库,
这样可以减缓数据库的压力。
3、数据库设计
我们这个项目中Mysql服务器并没有实现主从部署,而是同一个服务器建立两个库,一个当做Master库,一个当做Slave库。所以这里是不能实现的功能就是Master库
新增数据主动同步到Slave库。这样也更有利于我们测试看效果。
Master库
Slave库
从两幅图中可以看出,我这里在同一个服务器建两个数据库来模拟主从数据库。为了方便看测试效果,这里主从数据库中的数据是不一样的
。
二、核心代码
说明
完整的代码会放到GitHub上,这里只放一些核心代码。
1、pom.xml
<properties>
<java.version>1.8</java.version>
<mybatis-spring-boot>2.0.1</mybatis-spring-boot>
<druid>1.1.16</druid>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis-spring-boot}</version>
</dependency>
<!--mybatis驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--druid数据源-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid}</version>
</dependency>
<!--shardingsphere最新版本-->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.0.0-RC1</version>
</dependency>
<!--lombok实体工具-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
2、application.properties
server.port=8088
#指定mybatis信息
mybatis.config-location=classpath:mybatis-config.xml
spring.shardingsphere.datasource.names=master,slave0
# 数据源 主库
spring.shardingsphere.datasource.master.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.master.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.master.url=jdbc:mysql://localhost:3306/master?characterEncoding=utf-8
spring.shardingsphere.datasource.master.username=root
spring.shardingsphere.datasource.master.password=123456
# 数据源 从库
spring.shardingsphere.datasource.slave0.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.slave0.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.slave0.url=jdbc:mysql://localhost:3306/slave?characterEncoding=utf-8
spring.shardingsphere.datasource.slave0.username=root
spring.shardingsphere.datasource.slave0.password=123456
# 读写分离
spring.shardingsphere.masterslave.load-balance-algorithm-type=round_robin
spring.shardingsphere.masterslave.name=ms
spring.shardingsphere.masterslave.master-data-source-name=master
spring.shardingsphere.masterslave.slave-data-source-names=slave0
#打印sql
spring.shardingsphere.props.sql.show=true
Sharding-JDBC可以通过Java
,YAML
,Spring命名空间
和Spring Boot Starter
四种方式配置,开发者可根据场景选择适合的配置方式。具体可以看官网。
3、UserController
@RestController
public class UserController {
@Autowired
private UserService userService;
/**
* @Description: 保存用户
*/
@PostMapping("save-user")
public Object saveUser() {
return userService.saveOne(new User("小小", "女", 3));
}
/**
* @Description: 获取用户列表
*/
@GetMapping("list-user")
public Object listUser() {
return userService.list();
}
}
三、测试验证
1、读数据
我们可以发现读取的数据是Slave库的数据。我们再来看控制台打印的SQL。可以看到读操作是Slave库。
2、写数据
请求
localhost:8088/save-user?name=小小&sex=女&age=3
查看Mater数据库
发现Master数据库已经多了一条数据了,再看控制台打印的SQL。
这个时候如果去看Slave库的话这条新增的数据是没有的,因为没有同步过去。
Github地址
:https://github.com/yudiandemingzi/spring-boot-sharding-sphere
有关分库分表前面写了三篇博客:
2、分库分表(2) --- ShardingSphere(理论)
3、分库分表(3) ---SpringBoot + ShardingSphere实现读写分离
这篇博客通过ShardingSphere实现分表不分库
,并在文章最下方附上项目Github地址
。
一、项目概述
1、技术架构
项目总体技术选型
SpringBoot2.0.6 + shardingsphere4.0.0-RC1 + Maven3.5.4 + MySQL + lombok(插件)
2、项目说明
场景
在实际开发中,如果表的数据过大,我们可能需要把一张表拆分成多张表,这里就是通过ShardingSphere实现分表功能,但不分库。
3、数据库设计
这里有个member库,里面的tab_user
表由一张拆分成3张,分别是tab_user0
、tab_user1
、tab_user2
。
如图
具体的创建表SQL也会放到GitHub项目里
二、核心代码
说明
完整的代码会放到GitHub上,这里只放一些核心代码。
1、application.properties
server.port=8086
#指定mybatis信息
mybatis.config-location=classpath:mybatis-config.xml
spring.shardingsphere.datasource.names=master
# 数据源 主库
spring.shardingsphere.datasource.master.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.master.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.master.url=jdbc:mysql://localhost:3306/member?characterEncoding=utf-8
spring.shardingsphere.datasource.master.username=root
spring.shardingsphere.datasource.master.password=123456
#数据分表规则
#指定所需分的表
spring.shardingsphere.sharding.tables.tab_user.actual-data-nodes=master.tab_user$->{0..2}
#指定主键
spring.shardingsphere.sharding.tables.tab_user.table-strategy.inline.sharding-column=id
#分表规则为主键除以3取模
spring.shardingsphere.sharding.tables.tab_user.table-strategy.inline.algorithm-expression=tab_user$->{id % 3}
#打印sql
spring.shardingsphere.props.sql.show=true
Sharding-JDBC可以通过Java
,YAML
,Spring命名空间
和Spring Boot Starter
四种方式配置,开发者可根据场景选择适合的配置方式。具体可以看官网。
2、UserController
@RestController
public class UserController {
@Autowired
private UserService userService;
/**
* 模拟插入数据
*/
List<User> userList = Lists.newArrayList();
/**
* 初始化插入数据
*/
@PostConstruct
private void getData() {
userList.add(new User(1L,"小小", "女", 3));
userList.add(new User(2L,"爸爸", "男", 30));
userList.add(new User(3L,"妈妈", "女", 28));
userList.add(new User(4L,"爷爷", "男", 64));
userList.add(new User(5L,"奶奶", "女", 62));
}
/**
* @Description: 批量保存用户
*/
@PostMapping("save-user")
public Object saveUser() {
return userService.insertForeach(userList);
}
/**
* @Description: 获取用户列表
*/
@GetMapping("list-user")
public Object listUser() {
return userService.list();
}
三、测试验证
1、批量插入数据
请求接口
localhost:8086/save-user
我们可以从商品接口代码中可以看出,它会批量插入5条数据。我们先看控制台输出SQL语句
我们可以从SQL语句可以看出 tab_user1 和 tab_user2 表插入了两条数据
,而 tab_user0 表中插入一条数据
。
我们再来看数据库
tab_user0
tab_user1
tab_user2
完成分表插入数据。
2、获取数据
我们来获取列表的SQL,这里对SQL做了order排序操作,具体ShardingSphere分表实现order操作的原理可以看上面一篇博客。
select * from tab_user order by id
请求接口结果
我们可以看出虽然已经分表,但依然可以将多表数据聚合在一起并可以排序。