一、分库分表技术
当单表的数据量非常大,已经达到了数据库的IO瓶颈时,即使是尝试了加索引,读写分离等方案之后,单表的查询速度还是非常慢。这时候就可以考虑使用水平分表的方案,即通过把单表的数据拆分存放到多个表里面,以达到降低单表数据量从而提高查询速度的目的。当单库的连接数过多,单库已经无法支持更多的连接数时,这时候可以考虑分库方案,通过把单一数据库拆分为多个数据库,使得连接可以去到不同的数据库,以达到降低单库的连接数过高的目的。
例如mysql数据库的性能瓶颈一般来说单表最多的数据量是500w左右,如果表里面的字段比较少时也有可能是1000w才到数据库的性能瓶颈,这时候就可以考虑使用分库分表方案了。但是oracle在1000w的数据量的时候也没达到数据库的性能瓶颈,一般来说像oracle这种性能比较好的数据库都用不上分库分表的方案,除非数据量达到撑不住。如果一开始评估了系统的数据量是远远大于mysql的单表数据量500w,公司也不差钱可以直接用DB2,oracle等数据库,毕竟项目架构越简单出现问题的可能性就越少。水平分库分表的方案也不是万能的,对于负责的sql,如group by, having count,case when等需要分组的语句,对于复杂的连表查询(超过3个表)来说,会存在一定的问题,水平分库分表方案比较适合于单表的操作。
二、解决方案:
一种是以客户端程序代码封装的解决方案,以sharding-JDBC(可能还有些公司是自己封装了一些方案,但总体思路都是解析逻辑表然后重新拼接好真实表的sql进行执行)为代表,可以直接在业务代码里面使用, 无需引入额外的中间件,性能比中间件proxy代理的方案高,这种方案有个缺点就是只支持java,如果公司有多种开发语言就显得不是很适用了。
另外一种是以中间件proxy代理的解决方案,以MyCat为代表(当然还包括sharding-Proxy啦),支持多种开发语言,不同的开发语言都可以通过中间件来访问数据库。但是性能较客户端方式的方案低,毕竟多走了一层。
三、shardingSphere及sharding-JDBC介绍
1. 可能有很多人都听说过ShardingJDBC,ShardingJDBC是当当网捐献出来的开源分项目,后来捐给Apache组织了, 后续就发展成了ShardingShere。 ShardingSphere 已于2020年4月16日成为 Apache 软件基金会的顶级项目。
Apache ShardingSphere 是一套开源的分布式数据库中间件解决方案组成的生态圈,它由 JDBC、Proxy 和 Sidecar(规划中)这 3 款相互独立,却又能够混合部署配合使用的产品组成。 它们均提供标准化的数据分片、分布式事务和数据库治理功能,可适用于如 Java 同构、异构语言、云原生等各种多样化的应用场景。
Apache ShardingSphere 5.x 版本开始致力于可插拔架构,项目的功能组件能够灵活的以可插拔的方式进行扩展。 目前,数据分片、读写分离、多数据副本、数据加密、影子库压测等功能,以及 MySQL、PostgreSQL、SQLServer、Oracle 等 SQL 与协议的支持,均通过插件的方式织入项目。 开发者能够像使用积木一样定制属于自己的独特系统。Apache ShardingSphere 目前已提供数十个 SPI 作为系统的扩展点,仍在不断增加中。
2.sharding-JDCB
定位为轻量级Java框架,在Java的JDBC层提供的额外服务。 它使用客户端直连数据库,以jar包形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架。
适用于任何基于JDBC的ORM框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template或直接使用JDBC。
支持任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP等。
支持任意实现JDBC规范的数据库。目前支持MySQL,Oracle,SQLServer,PostgreSQL以及任何遵循SQL92标准的数据库。
四、ShardingSphere-JDBC5.2整合mybatisPlus3.X
本文先演示shardingJdbc5.2版本的垂直分表功能,垂直分表的配置算是最简单的入门了,shardingJdbc的核心功能应该是水平分片,毕竟多数据源工具不单单只有shardingJdbc提供了这种功能。下面所示代码是垂直分片代码示例,至于水平分片的入门和分片规则演示会另外再写文章记录。
1.父pom依赖,实际上这个依赖可以不用管,主要是springboot, springCloud Alibaba的依赖:
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.xmc</groupId>
<artifactId>springcloud-alibaba</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<!--这里的依赖实际上可以不管,是我在测试时建的模块-->
<module>shop-common</module>
<module>core</module>
<module>sharding</module>
</modules>
<!--父工程-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
</parent>
<!-- 依赖版本-->
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-cloud.version>Greenwich.RELEASE</spring-cloud.version>
<spring-cloud-alibaba.version>2.1.0.RELEASE</spring-cloud-alibaba.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
子模块的pom依赖
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springcloud-alibaba</artifactId>
<groupId>com.xmc</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>sharding</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</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>
</dependency>
<!--核心依赖shardingjdbc5.2-->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
<version>5.2.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--mybatisPlus依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
</project>
PO层代码如下:
t_shop_order逻辑表
package com.xmc.po;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.math.BigDecimal;
@Data
@TableName("t_shop_order")
public class TShopOrder {
@TableId
private String id;
private int number;
private int oid;
private String pname;
private BigDecimal pprice;
private String uid;
private String username;
}
t_shop_user逻辑表
package com.xmc.po;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@Data
@TableName("t_shop_user")
public class TShopUser {
@TableId
private String id;
private String username;
private String password;
private String telephone;
}
Mapper接口如下:
package com.xmc.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.xmc.po.TShopOrder;
public interface TShopOrderMapper extends BaseMapper<TShopOrder> {
}
package com.xmc.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.xmc.po.TShopUser;
public interface TShopUserMapper extends BaseMapper<TShopUser> {
}
service层实现类代码如下,service接口就不写出来了:
package com.xmc.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xmc.mapper.TShopOrderMapper;
import com.xmc.po.TShopOrder;
import com.xmc.service.TShopOrderService;
import org.springframework.stereotype.Service;
@Service
public class TShopOrderServiceImpl extends ServiceImpl<TShopOrderMapper, TShopOrder> implements TShopOrderService {
@Override
public TShopOrder selectById(String userId) {
return baseMapper.selectById(userId);
}
@Override
public int insert(TShopOrder vo) {
return baseMapper.insert(vo);
}
@Override
public int update(TShopOrder vo) {
return baseMapper.updateById(vo);
}
@Override
public int delete(String userId) {
return baseMapper.deleteById(userId);
}
}
package com.xmc.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xmc.mapper.TShopUserMapper;
import com.xmc.po.TShopUser;
import com.xmc.service.TShopUserService;
import org.springframework.stereotype.Service;
@Service
public class TShopUserServiceImpl extends ServiceImpl<TShopUserMapper, TShopUser> implements TShopUserService {
@Override
public TShopUser selectById(String userId) {
Integer integer = Integer.valueOf(userId);
return baseMapper.selectById(integer);
}
@Override
public int insert(TShopUser vo) {
return baseMapper.insert(vo);
}
@Override
public int update(TShopUser vo) {
return baseMapper.updateById(vo);
}
@Override
public int delete(String userId) {
return baseMapper.deleteById(userId);
}
}
controller层代码如下:
package com.xmc.controller;
import com.xmc.po.TShopOrder;
import com.xmc.service.TShopOrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/test/shop_order")
public class TShopOrderController {
@Autowired
private TShopOrderService tShopOrderService;
@GetMapping("/get")
public TShopOrder get(@RequestParam(name = "userId", required = false)String userId){
return tShopOrderService.selectById(userId);
}
@PostMapping("/insert")
public int insert(@RequestBody TShopOrder vo) {
return tShopOrderService.insert(vo);
}
@PostMapping("/update")
public int update(@RequestBody TShopOrder vo) {
return tShopOrderService.update(vo);
}
@PostMapping("/delete")
public int delete(@RequestBody TShopOrder vo) {
return tShopOrderService.delete(vo.getId());
}
}
package com.xmc.controller;
import com.xmc.po.TShopUser;
import com.xmc.service.TShopUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/test/shop_user")
public class TShopUserController {
@Autowired
private TShopUserService tShopUserService;
@GetMapping("/get")
public TShopUser get(@RequestParam(name = "userId", required = false)String userId){
return tShopUserService.selectById(userId);
}
@PostMapping("/insert")
public int insert(@RequestBody TShopUser vo) {
return tShopUserService.insert(vo);
}
@PostMapping("/update")
public int update(@RequestBody TShopUser vo) {
return tShopUserService.update(vo);
}
@PostMapping("/delete")
public int delete(@RequestBody TShopUser vo) {
return tShopUserService.delete(vo.getId());
}
}
项目启动类如下:
package com.xmc;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan(basePackages="com.xmc.mapper")
public class ShardingApplication {
public static void main(String[] args) {
SpringApplication.run(ShardingApplication.class, args);
}
}
application.properties如下,该文件已经包含了垂直分片的规则配置,配置了哪些逻辑表是怎么映射到哪个数据库的哪张表。
# 垂直分片,也可以认为是多数据源;垂直分片是把一个表的数据拆分为多个库的多张表或者在同一库的多张表
server.port= 8991
spring.application.name = service-sharding
# 在控制台打印SQL
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
## shardingjdbc 分片策略
## 配置数据源,给数据源起名称
spring.shardingsphere.datasource.names=d1,d2
#spring.shardingsphere.mode.type=Standalone
## 一个实体类对应多张表,覆盖
spring.main.allow-bean-definition-overriding=true
#
##配置数据源d1连接到d_order数据库 具体内容,包含连接池,驱动,地址,用户名和密码
spring.shardingsphere.datasource.d1.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.d1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.d1.jdbc-url=jdbc:mysql://127.0.0.1:3306/d_order?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
spring.shardingsphere.datasource.d1.username=root
spring.shardingsphere.datasource.d1.password=abcd1234
#配置数据源d2连接到d_user数据库 具体内容,包含连接池,驱动,地址,用户名和密码
spring.shardingsphere.datasource.d2.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.d2.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.d2.jdbc-url=jdbc:mysql://127.0.0.1:3306/d_user?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
spring.shardingsphere.datasource.d2.username=root
spring.shardingsphere.datasource.d2.password=abcd1234
#指定 逻辑表 t_shop_order 表分布情况,配置表在哪个数据库里面,表名称都是什么,这样配置之后如果是操作t_shop_order表实际上就是操作d_order数据库的shop_order表
spring.shardingsphere.rules.sharding.tables.t_shop_order.actual-data-nodes=d1.shop_order
#指定 逻辑表 t_shop_user 表分布情况,配置表在哪个数据库里面,表名称都是什么,这样配置之后如果是操作t_shop_user表实际上就是操作d_user数据库的shop_user表
spring.shardingsphere.rules.sharding.tables.t_shop_user.actual-data-nodes=d2.shop_user
# 打开 sql 输出日志
spring.shardingsphere.props.sql-show=true
测试:当我们调用 /test/shop_order/insert 接口时,通过日志和数据发现,insert语句落在了d_order数据库里面。
2022-10-16 20:53:38.292 INFO 76888 --- [nio-8991-exec-5] ShardingSphere-SQL : Actual SQL: d1 ::: INSERT INTO shop_order ( id,
number,
oid,
pname,
uid,
username ) VALUES (?, ?, ?, ?, ?, ?) ::: [1, 123, 123, 赤兔, 123, 曹操]
<== Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@66cac6cb]
当我们调用 /test/shop_user/insert接口时,通过日志和数据发现,insert语句落在了d_user数据库里面。
2022-10-16 21:14:28.375 INFO 76888 --- [nio-8991-exec-8] ShardingSphere-SQL : Actual SQL: d2 ::: INSERT INTO shop_user ( id,
username,
password,
telephone ) VALUES (?, ?, ?, ?) ::: [1, 曹操, 123, 123456789]
<== Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4e8d8218]
至于其他的接口的测试结果我就不放上来了。