基于springboot的ShardingSphere5.X的分库分表的解决方案之分表解决方案(一)
假定我们现在已经使用spring boot编写了一套系统,随着我们的系统的不断使用,其中某些表的数据量已经突破了单表千万的数据量,这时候我们该怎么办呢?ShardingSphere就是你需要的解决方案,购买了专栏的小伙伴可以加微信:aaabbb158,加微信的时候请加上您购买专栏的当前账号名称的备注,我会为你解答你遇到相应的问题,同时为您提供当前专栏的源代码。
1、什么是ShardingSphere
Apache ShardingSphere 是一套开源的分布式数据库中间件解决方案组成的生态圈,它由 JDBC、Proxy 和 Sidecar(规划中)这 3 款相互独立,却又能够混合部署配合使用的产品组成。 它们均提供标准化的数据分片、分布式事务和数据库治理功能,可适用于如 Java 同构、异构语言、云原生等各种多样化的应用场景。
Apache ShardingSphere 定位为关系型数据库中间件,旨在充分合理地在分布式的场景下利用关系型数据库的计算和存储能力,而并非实现一个全新的关系型数据库。 它通过关注不变,进而抓住事物本质。关系型数据库当今依然占有巨大市场,是各个公司核心业务的基石,未来也难于撼动,我们目前阶段更加关注在原有基础上的增量,而非颠覆。
Apache ShardingSphere 5.x 版本开始致力于可插拔架构,项目的功能组件能够灵活的以可插拔的方式进行扩展。 目前,数据分片、读写分离、数据加密、影子库压测等功能,以及对 MySQL、PostgreSQL、SQLServer、Oracle 等 SQL 与协议的支持,均通过插件的方式织入项目。 开发者能够像使用积木一样定制属于自己的独特系统。Apache ShardingSphere 目前已提供数十个 SPI 作为系统的扩展点,而且仍在不断增加中。
以上就是关于ShardingSphere的简单的介绍,如果大家希望了解更多的关于ShardingSphere,我们可以直接访问ShardingSphere官网来更多的了解我们的ShardingSphere。
2、quick start
我们现在就可以快速开始我们的第一个分表的demo例子的构建,5.0的版本的发布时间节点为2020年11月10号,距离当前写这篇文章的日子也就一个月左右的时间,那么我们在使用这个5.0.0-alpha版本的时候铁定是一堆的坑,这些我将带着大家一点一点的填进去。
2.1、数据库设计
这是我们的第一个quick start版本,因此我们不要一下讲解的太复杂,我们需要循序渐进,带着大家一点一点的深入到整个分库分表的实践过程,那么我们这边首先使用power design设计一张MySQL数据库的t_order【订单表】字段如下所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6HziErwV-1609312079451)(E:\indea-base\sharding-sphere-example\doc\sharding-learn\images\20201212-215646-959.png)]
有些小伙伴就有疑问了,我设计的表不是t_order,咋建表语句里面是t_order0、t_order1、t_order2、t_order3这样的建表语句呢,这就是我们分表的一个策略,我们将t_order表分为了4张表,将我们原先t_order一张表的数据均匀的分配到这四张表中,假定我们原先单表数据是100W,那现在单表数据就减少为25W,那很明显我们的查询就会快上很多了,创建订单表的SQL语句如下:
drop table if exists t_order0;
/*==============================================================*/
/* Table: t_order0 */
/*==============================================================*/
create table t_order0
(
order_id bigint not null comment '订单流水ID',
user_id bigint comment '用户流水ID',
order_no varchar(100) comment '订单编号',
create_time date comment '创建时间',
primary key (order_id)
);
alter table t_order0 comment '订单表';
drop table if exists t_order1;
/*==============================================================*/
/* Table: t_order1 */
/*==============================================================*/
create table t_order1
(
order_id bigint not null comment '订单流水ID',
user_id bigint comment '用户流水ID',
order_no varchar(100) comment '订单编号',
create_time date comment '创建时间',
primary key (order_id)
);
alter table t_order1 comment '订单表';
drop table if exists t_order2;
/*==============================================================*/
/* Table: t_order2 */
/*==============================================================*/
create table t_order2
(
order_id bigint not null comment '订单流水ID',
user_id bigint comment '用户流水ID',
order_no varchar(100) comment '订单编号',
create_time date comment '创建时间',
primary key (order_id)
);
alter table t_order2 comment '订单表';
drop table if exists t_order3;
/*==============================================================*/
/* Table: t_order3 */
/*==============================================================*/
create table t_order3
(
order_id bigint not null comment '订单流水ID',
user_id bigint comment '用户流水ID',
order_no varchar(100) comment '订单编号',
create_time date comment '创建时间',
primary key (order_id)
);
alter table t_order3 comment '订单表';
2.2、创建数据库
打开我们的Navicat for MySQL的数据库管理工具,然后我们连上一个属于我们自己的数据库,然后创建一个测试的数据库如下所示:
2.3、执行SQL脚本
然后我们直接在Navicat for MySQL的数据库管理工具中执行我们2.1中的建表的SQL脚本,如下所示
脚本执行完成以后,我们会在我们的数据库管理工具中看到如下所示的结果:
那么我们的数据库阶段就到此结束了,接下来就转入到我们的程序开发步骤了。
2.4、创建spring boot工程
直接打开我们的idea,然后创建我们的spring boot工程,步骤如下所示:
2.4.1、步骤一:创建spring boot项目
2.4.2、步骤二:填写spring项目的信息
2.4.3、步骤三:选择当前工程的基础依赖
2.4.4、步骤四:引入ShardingSphere依赖
工程创建完成以后我们打开我们的pom.xml文件,引入我们的ShardingSphere的相关依赖,如下所示:
<?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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.mysql.sharding</groupId>
<artifactId>sharding-sphere-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>sharding-sphere-demo</name>
<description>这是一个ShardingSphere的例子</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- 引入swagger2依赖 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.6.1</version>
<exclusions>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.6.1</version>
</dependency>
<!-- 阿里数据源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.3</version>
</dependency>
<!--通用 Mapper-->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>2.0.4</version>
</dependency>
<!-- 引入mysql数据库依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<!--分页插件-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.4</version>
</dependency>
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
<version>5.0.0-alpha</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
这里有两个坑大家一定要注意,首先第一个坑是:
2.4.4.1、swagger2依赖的包冲突导致启动失败
由于我们的swagger2和ShardingSphere依赖guava这个maven包,这会导致在我们引入swagger2接口测试依赖的时候启动项目报错,解决方式相当的简单直接排除我们的swagger2的guava的依赖包如下所示:
<!-- 引入swagger2依赖 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.6.1</version>
<exclusions>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
</exclusions>
</dependency>
2.4.4.2、下载shardingsphere-jdbc-core-spring-boot-starter包失败
可能是由于这是最新的starter的原因,作者还没上传到国外的maven中央仓库,如果大家没有使用阿里云的maven仓库,就会导致无法下载我们的这个starter,后续国外的仓库就会有了,要是最近接触的人可能会遇到无法下载这个依赖,那这时候大家记得将自己的maven的中央仓库切换到阿里的maven仓库。
2.5、编写代码
2.5.1、编写实体
直接在我们的com.mysql.sharding.demo包底下创建一个entity包,然后在里面创建一个Order的实体类如下所示:
package com.mysql.sharding.demo.entity;
import javax.persistence.Column;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Date;
/**
* @author linzef
* @since 2020-12-12
* 类描述: 订单表
*/
@Table(name = "t_order")
public class Order {
/**
* 订单流水ID
*/
@Id
@Column(name = "order_id")
private Long orderId;
/**
* 用户流水ID
*/
@Column(name = "user_Id")
private Long userId;
/**
* 订单编号
*/
@Column(name = "order_no")
private String orderNo;
/**
* 订单创建时间
*/
@Column(name = "create_time")
private Date createTime;
public Long getOrderId() {
return orderId;
}
public void setOrderId(Long orderId) {
this.orderId = orderId;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getOrderNo() {
return orderNo;
}
public void setOrderNo(String orderNo) {
this.orderNo = orderNo;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
}
2.5.2、数据库层的实现
直接在我们的包底下创建一个dao的包,然后在里面创建一个OrderDao的实体类如下所示:
package com.mysql.sharding.demo.dao;
import com.mysql.sharding.demo.entity.Order;
import tk.mybatis.mapper.common.Mapper;
/**
* @author linzef
* @since 2020-12-12
* 类描述: 订单的dao
*/
public interface OrderDao extends Mapper<Order> {
}
当前项目目录结果如下所示:
2.5.3、编写dao的xml
然后在我们的resource目录底下创建一个mybatis/mapper文件夹,然后在mybatis文件夹底下创建mybatis-config.xml如下所示:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<typeAlias alias="Integer" type="java.lang.Integer" />
<typeAlias alias="Long" type="java.lang.Long" />
<typeAlias alias="HashMap" type="java.util.HashMap" />
<typeAlias alias="LinkedHashMap" type="java.util.LinkedHashMap" />
<typeAlias alias="ArrayList" type="java.util.ArrayList" />
<typeAlias alias="LinkedList" type="java.util.LinkedList" />
</typeAliases>
</configuration>
接着在mapper文件夹底下创建OrderDao.xml代码如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mysql.sharding.demo.dao.OrderDao">
<resultMap id="BaseResultMap" type="com.mysql.sharding.demo.entity.Order">
<id column="order_id" jdbcType="VARCHAR" property="orderId"/>
<result column="order_no" jdbcType="VARCHAR" property="orderNo"/>
<result column="user_Id" jdbcType="VARCHAR" property="userId"/>
<result column="create_time" jdbcType="DATE" property="createTime"/>
</resultMap>
</mapper>
当前项目目录结果如下所示:
2.5.4、配置spring boot
打开我们的application.yml文件,若你的是application.properties,请将其改为yml即可,然后在里面配置我们的数据库相关信息,如下所示:
# mybatis的配置
mybatis:
config-location: classpath:mybatis/mybatis-config.xml
mapper-locations: classpath:mybatis/mapper/*.xml
# 分页插件的配置
pagehelper:
helperDialect: mysql
reasonable: true
# 当前工程端口的配置
server:
port: 8798
spring:
shardingsphere:
# 展示修改以后的sql语句
props:
sql-show: true
datasource:
common:
driver-class-name: com.mysql.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
names: db0
db0:
url: jdbc:mysql://127.0.0.1:3306/db0?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2b8
username: root
password: '123456'
rules:
sharding:
key-generators:
# 此处必须要配置,否则会导致报错,因为shardingsphere-jdbc-core-spring-boot-starter需要加载此项配置,官网的demo例子有错
snowflake:
type: SNOWFLAKE
props:
worker-id: 123
sharding-algorithms:
table-inline:
type: INLINE
props:
# 此处写法必须是t_order$->{order_id % 4}不能写为t_order${order_id % 4},yml不认这种写法
algorithm-expression: t_order$->{order_id % 4}
tables:
t_order:
# 配置t_order的分表的规则
actual-data-nodes: db0.t_order$->{0..3}
table-strategy:
standard:
sharding-column: order_id
sharding-algorithm-name: table-inline
enabled: true
接着打开我们的ShardingSphereDemoApplication.java文件,代码如下所示:
package com.mysql.sharding.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import tk.mybatis.spring.annotation.MapperScan;
@SpringBootApplication
@MapperScan("com.mysql.sharding.demo.dao")
public class ShardingSphereDemoApplication {
public static void main(String[] args) {
SpringApplication.run(ShardingSphereDemoApplication.class, args);
}
}
2.5.5、编写service
在我们的com.mysql.sharding.demo包底下创建一个service包,然后在里面我们创建一个插入数据和查询数据的方法,代码如下所示:
package com.mysql.sharding.demo.service;
import com.mysql.sharding.demo.dao.OrderDao;
import com.mysql.sharding.demo.entity.Order;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* @author linzef
* @since 2020-12-12
* 类描述: 订单的service
*/
@Service
@Transactional(rollbackFor = {Exception.class})
public class OrderService {
@Autowired
private OrderDao orderDao;
/**
* 功能描述: 实现新增订单
* @param order 订单的实体
*/
public void saveOrder(Order order){
orderDao.insertSelective(order);
}
/**
* 功能描述: 根据订单ID来获取订单数据
* @param orderId 订单流水ID
* @return 返回查询结果
*/
public Order getOrderByOrderId( Long orderId){
return orderDao.selectByPrimaryKey(orderId);
}
}
2.5.6、编写controller
在我们的com.mysql.sharding.demo包底下创建一个controller包,然后在里面创建一个OrderController的类,代码如下所示:
package com.mysql.sharding.demo.controller;
import com.mysql.sharding.demo.entity.Order;
import com.mysql.sharding.demo.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
/**
* @author linzef
* @since 2020-12-12
* 类描述: 订单的controller
*/
@RestController
public class OrderController {
@Autowired
private OrderService orderService;
/**
* 功能描述: 实现新增订单
* @param order 订单的实体
*/
@PostMapping("saveOrder")
public void saveOrder(@RequestBody Order order){
orderService.saveOrder(order);
}
/**
* 功能描述: 根据订单ID来获取订单数据
* @param orderId 订单流水ID
* @return 返回查询结果
*/
@PostMapping("getOrderByOrderId")
public Order getOrderByOrderId( Long orderId){
return orderService.getOrderByOrderId(orderId);
}
}
当前项目目录结果如下所示:
2.5.7、配置swagger2
理论上到了此处我们就可以直接测试我们的例子了,但是使用postman感觉太麻烦了,我们是否有一种更简单的测试方案呢,这边我们直接使用swagger2的集成来测试我们的接口,直接在我们的com.mysql.sharding.demo包底下创建一个config包,创建一个配置类Swagger2Config.java,代码如下所示:
package com.mysql.sharding.demo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
/**
* @author linzf
* @since 2020/12/12
* 类描述:
*/
@Configuration
@EnableSwagger2
public class Swagger2Config {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.useDefaultResponseMessages(false)
.select()
.apis((input)->{
Class<?> declaringClass = input.declaringClass();
if(declaringClass.isAnnotationPresent(RestController.class)){
return true;
}
if(input.isAnnotatedWith(ResponseBody.class)){
return true;
}
return false;
})
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
//大标题
.title("sharding-jdbc测试页面!")
//版本
.version("1.0")
.build();
}
}
当前项目目录结果如下所示:
2.5.8、验证我们的分表方案
直接启动我们当前的工程,然后访问http://127.0.0.1:8798/swagger-ui.html,这时候我们会看到如下的页面,然后我们就可以开始我们的测试了。
点开我们的orde-controller,会出现如下的页面:
然后点击我们的saveOrder方法,输入如下所示的json数据:
{
"createTime": "2020-12-12T13:42:52.558Z",
"orderId": 1,
"orderNo": "string1",
"userId": 1
}
最后点击Try it out按钮,然后我们看我们的后台的日志如下所示:
这时候我们可以看到我们的数据指向了t_order1这张表,那我们直接打开我们的t_order1这张表,可以看到如下数据:
那这时候我们如果将order_id的值设置为2那将会插入那张表呢,很明显是插入了t_order2这张表,因为我们在application.yml已经配置好了规则就是根据order_id%4的值来进行分表的。
大家可以直接在swagger2中修改下order_id的值来验证自己的猜想,同理我们的getOrderByOrderId这个方法也会根据我们的order_id进行导航,那么到此处我们就完成了我们的ShardingSphere5.X版本的第一个quick start了。