SpringBoot结合ShardingJDBC实现分库分表

282次阅读

共计 10188 个字符,预计需要花费 26 分钟才能阅读完成。

前言:

今天来聊下 SpringBoot 集成 Sharding-JDBC 实现分库分表;为此写了一个小 Demo,这个 Demo 是基于 SpringBoot,并集成了 Mybatis、Redis、Swagger(生成在线的接口文档)、PageHelper(分页工具) 等,当然绝对也集成了 Sharding-JDBC;以及设计了 RestFul 风格的接口,添加了 单元测试

下面简单介绍下本文的主线:

①、首先介绍下 Demo 的工程目录,并且介绍下使用的基本环境,如:sql、工程的 pom.xml 等

②、然后会着重介绍 SpringBoot 集成 Sharding-JDBC 的过程,及 Sharding-JDBC 基本知识 和 注意事项。

1、项目信息描述:

完整项目在 gitHub,地址: https://github.com/leishen6/S…

如有需要请自己去 giHub 上拉取代码进行查阅,由于本人水品有限,如有问题请留言提出,谢谢!

Demo 详解:

1、工程目录:

2、工程环境:

2.1、pom.xml :
   <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.6.RELEASE</version>
        <relativePath/>
    </parent>


    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.7</java.version>

        <mybatis-spring-boot>1.2.0</mybatis-spring-boot>
        <mysql-connector>5.1.39</mysql-connector>
        <fastjson>1.2.41</fastjson>
    </properties>


    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-redis</artifactId>
            <version>1.4.7.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>


        <!-- Spring Boot Mybatis 依赖 -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.0.1</version>
        </dependency>

        <!-- MySQL 连接驱动依赖 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <!--druid 连接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.16</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>${fastjson}</version>
        </dependency>

        <!--swagger-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>

        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>

        <!-- pagehelper 分页工具 -->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>4.1.6</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
        </dependency>

        <!-- sharding-jdbc -->
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
            <version>4.0.0-RC1</version>
        </dependency>

        <!-- hutool 工具类 -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-setting</artifactId>
            <version>5.2.4</version>
        </dependency>

    </dependencies>

注意:pom.xml 的内容最好不要改动了,因为如果将里面的一些 依赖版本变动了,可能会导致依赖版本兼容性问题出现,最终导致程序运行失败。

2.2、sql 环境:

①、数据库使用的 Mysql,Demo 程序运行前需要提前创建好数据库,由于使用了分库分表,所以需要创建两个库;数据库名:springboot0、springboot1

②、在 springboot0 数据库中执行下面的 sql 语句创建表:

DROP TABLE IF EXISTS `t_role`;
CREATE TABLE `t_role` (`id` int(11) NOT NULL AUTO_INCREMENT,
  `role_name` varchar(128) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Table structure for t_user0
-- ----------------------------
DROP TABLE IF EXISTS `t_user0`;
CREATE TABLE `t_user0` (`id` int(65) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `name` varchar(10) DEFAULT NULL COMMENT '姓名',
  `age` int(2) DEFAULT NULL COMMENT '年龄',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=43 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Table structure for t_user1
-- ----------------------------
DROP TABLE IF EXISTS `t_user1`;
CREATE TABLE `t_user1` (`id` int(65) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `name` varchar(10) DEFAULT NULL COMMENT '姓名',
  `age` int(2) DEFAULT NULL COMMENT '年龄',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=35 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Table structure for t_user2
-- ----------------------------
DROP TABLE IF EXISTS `t_user2`;
CREATE TABLE `t_user2` (`id` int(65) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `name` varchar(10) DEFAULT NULL COMMENT '姓名',
  `age` int(2) DEFAULT NULL COMMENT '年龄',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=34 DEFAULT CHARSET=utf8;

③、然后在创建的 springboot1 数据库中执行 sql 语句创建表:

DROP TABLE IF EXISTS `t_user0`;
CREATE TABLE `t_user0` (`id` int(65) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `name` varchar(10) DEFAULT NULL COMMENT '姓名',
  `age` int(2) DEFAULT NULL COMMENT '年龄',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=36 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Table structure for t_user1
-- ----------------------------
DROP TABLE IF EXISTS `t_user1`;
CREATE TABLE `t_user1` (`id` int(65) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `name` varchar(10) DEFAULT NULL COMMENT '姓名',
  `age` int(2) DEFAULT NULL COMMENT '年龄',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=34 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Table structure for t_user2
-- ----------------------------
DROP TABLE IF EXISTS `t_user2`;
CREATE TABLE `t_user2` (`id` int(65) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `name` varchar(10) DEFAULT NULL COMMENT '姓名',
  `age` int(2) DEFAULT NULL COMMENT '年龄',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=36 DEFAULT CHARSET=utf8;

上面的基本信息介绍完了,接下来介绍重头戏了,Sharding-JDBC 集成之路。嘿嘿 . . . . .

Sharding-JDBC 基本知识:

首先将 Sharding-JDBC 的官网贴出来,也可以去官网进行详细了解。shardingsphere 之 Sharding-JDBC

大家如果没去官网了解过的,也可以通过下面进行了解下哟:

1、基本概念:

Sharding-JDBC 定位为轻量级 Java 框架,在 Java 的 JDBC 层 提供的额外服务,所以说它是一款属于 应用层依赖类中间件。它使用客户端直连数据库,以 jar 包形式提供服务,无需额外部署和依赖,可理解为增强版的 JDBC 驱动,完全兼容 JDBC 和各种 ORM 框架。

应用层依赖类中间件:这类分库分表中间件的特点就是 和应用强耦合,需要应用显示依赖相应的 jar 包。

2、兼容性:

  • 适用于任何基于 Java 的 ORM 框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template 或直接使用 JDBC。
  • 基于任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP 等。
  • 支持任意实现 JDBC 规范的数据库。目前支持 MySQL,Oracle,SQLServer 和 PostgreSQL。

3、架构图:

​ 图片来源:sharding-JDBC 官网

4、数据分片:

进行分库分表时,是绕不开 数据分片 的知识的。

数据分片指按照某个维度将存放在单一数据库中的数据分散地存放至多个数据库或表中以达到提升性能瓶颈以及可用性的效果。

数据分片的拆分方式又分为 垂直分片 水平分片(最为常用的方式)

4.1、垂直分片:

按照业务拆分的方式称为垂直分片,又称为纵向拆分,它的核心理念是专库专用。

例如:本来一个库由订单表和用户表构成,由于并发量和数据量太大,可以将这原本的一个库进行拆分,拆分成两个库,一个订单库,里面只有一个订单表,一个用户库,里面只有一个用户表,这样使用两个库就能支持更大的并发量,提升数据库的并发瓶颈。

缺点:

垂直分片往往需要对架构和设计进行调整。通常来讲,是来不及应对互联网业务需求快速变化的;而且,它也并无法真正的解决单点瓶颈。垂直拆分可以缓解数据量和访问量带来的问题,但无法根治。

4.2、水平分片:

水平分片又称为横向拆分。相对于垂直分片,它不再将数据根据业务逻辑分类,而是通过某个字段(或某几个字段),根据某种规则将数据分散至多个库或表中,每个分片仅包含数据的一部分。

注意:水平分片从理论上突破了单机数据量处理的瓶颈,并且扩展相对自由,是分库分表的标准解决方案。

例如,本文中实现的分库分表就是使用的 水平分片 ; 根据用户表中 name 用户名字段进行分片;

在新增用户数据时,首先根据配置的分片策略(分片策略包含分片算法)判断此用户名的数据到底新增到哪个数据库中,以及哪个表中。

5、内部执行流程:

核心由 SQL 解析 => 执行器优化 => SQL 路由 => SQL 改写 => SQL 执行 => 结果归并 的流程组成。

主要介绍下 SQL 路由、SQL 改写的概念:

SQL 路由:根据解析上下文匹配用户配置的分片策略,并生成最终的路由路径;

SQL 改写:将 SQL 改写为在 真实数据库 中可以正确执行的语句。

Sharding-JDBC 集成过程:

1、首先看下 Sharding-JDBC 的配置文件:

下面是 Sharding-JDBC 配置文件的内容;

注意:

本工程中的分库分表是分库 2 个,分表 3 个,分片键是 name 字段,分库分表都是依据 name 这个分片键,

分表只有 t_user 表进行分表;


## 分库分表 配置: (下面配置的分库数量、虚拟节点数量等主要是为了实现一致性 hash 算法进行分片)

# 分库数量
sharding.datasource.count=2

# 分库虚拟节点数量
sharding.datasource.virtual.node.count=360

# 虚拟节点映射到物理节点范围:例如本文中是根据 name 名字进行分片的, 所以使用名字的 hash 值对虚拟节点数取余;
# 得到一个 0 -359 的余数,然后按照余数所属的范围, 如果余数在 0 -179 范围则数据分片访问 springboot0 数据源,
# 如果余数在 180-359 范围,则数据被分片访问 springboot1 数据源; 下面的分表原理一样。sharding.datasource.virtual.node.count.rang=0-179,180-359

# 分表数量
sharding.table.count=3

# 分表虚拟节点数量
sharding.table.virtual.node.count=360

# 虚拟节点映射到物理节点范围
sharding.table.virtual.node.count.rang=0-119,120-249,250-359


# 实际数据源名字
spring.shardingsphere.datasource.names=springboot0,springboot1

# 数据源
spring.shardingsphere.datasource.springboot0.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.springboot0.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.springboot0.url=jdbc:mysql://localhost:3306/springboot0?characterEncoding=utf-8&useSSL=false
spring.shardingsphere.datasource.springboot0.username=root
spring.shardingsphere.datasource.springboot0.password=root

spring.shardingsphere.datasource.springboot1.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.springboot1.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.springboot1.url=jdbc:mysql://localhost:3306/springboot1?characterEncoding=utf-8&useSSL=false
spring.shardingsphere.datasource.springboot1.username=root
spring.shardingsphere.datasource.springboot1.password=root


### 分片策略使用的是: 自定义的分片算法
## 实际的数据节点,符合 groovy 语法; 这里的 {0..1} 指的是 0 到 1 及其之间的数字,数字有 0,1 两个,代表分库是两个;
## 并且拼接在 springboot 后面,就构成了上面配置的实际数据源名称了
spring.shardingsphere.sharding.tables.t_user.actualDataNodes=springboot$->{0..1}.t_user$->{0..2}

## 分片键:name 字段
spring.shardingsphere.sharding.tables.t_user.databaseStrategy.standard.shardingColumn=name
## 自定义 分库 算法
spring.shardingsphere.sharding.tables.t_user.databaseStrategy.standard.preciseAlgorithmClassName=com.lyl.algorithm.MyPreciseDBShardingAlgorithm

## 分片键:name 字段
spring.shardingsphere.sharding.tables.t_user.tableStrategy.standard.shardingColumn=name
## 自定义 分表 算法
spring.shardingsphere.sharding.tables.t_user.tableStrategy.standard.preciseAlgorithmClassName=com.lyl.algorithm.MyPreciseTableShardingAlgorithm


# 不进行分库分表的数据源指定,使用设置的默认数据源 springboot0 ; 例如,本文中的 t_role 表就不进行
# 分库分表,那关于 t_role 表的增删改差都走默认数据源 springboot0
spring.shardingsphere.sharding.default-data-source-name=springboot0
# 打印执行的数据库以及语句
spring.shardingsphere.props.sql.show=true

如果需要更改分库数量,或者分表数量的话,那么也需要对配置文件进行更改;例如:将分库数量改为 3 个;

下面这些配置文件内容需要更改:

原配置内容:

sharding.datasource.count=2

sharding.datasource.virtual.node.count.rang=0-179,180-359

spring.shardingsphere.sharding.tables.t_user.actualDataNodes=springboot$->{0..1}.t_user$->{0..2}

改为:

sharding.datasource.count=3

sharding.datasource.virtual.node.count.rang=0-119,120-249,250-359

spring.shardingsphere.sharding.tables.t_user.actualDataNodes=springboot$->{0..2}.t_user$->{0..2}

2、一致性 hash 算法:

一致性 hash 算法学习可参考:白话解析:一致性哈希算法 consistent hashing

首先解释下,sharing-JDBC 配置文件中使用的虚拟节点就是为了实现一致性 hash 算法;

为什么使用一致性 hash 算法呢?

因为,使用一致性 hash 算法是为了满足后期可能出现的数据库扩容问题;

在这里来简单介绍下,常用的分片算法(方式):hash 方式,一致性 hash(consistent hash),按照数据范围(range based)。

上面三种分片方式学习可参考:带着问题学习分布式系统之数据分片

2.1、介绍一致性 hash 算法为什么易于扩容呢?

结合下文本,本文是根据 name 字段进行分片的,使用 name 用户名的 hash 值对虚拟节点数 360取余,得到一个

0-359 的余数,然后根据余数匹配配置的虚拟节点范围进行映射实际物理节点,来得到实际的数据源节点等。

如图:(分库 2 个,实际数据源 springboot0、springboot1,虚拟节点数 360,虚拟节点范围 0-179,180-359)

进行扩容:(增加一个数据库)

分库 3 个,实际数据源 springboot0、springboot1、springboot2,虚拟节点数 360 不变,虚拟节点范围

0-119,120-249,250-359

如图,使用一致性 hash 算法,在扩容时,不会导致整体数据的不可用,只会损失一部分数据;原本分 2 个库时,应处于 springboot1 数据源中的数据,在分库 3 时,进行查询此数据时,会被分片到 springboot2 中查询,此时会出现查询不到的情况;但是,这也只是损失了一小部分数据,不会导致整体数据出现问题。

3、自定义分片算法:

3.1、自定义分库实现:

3.2、自定义分表算法:

end,本文结束 . . . . . . . . . . . . . . . .

❤不要忘记留下你学习的足迹 [点赞 + 收藏 + 评论]嘿嘿ヾ

一切看文章不点赞都是“耍流氓”,嘿嘿ヾ (◍°∇°◍)ノ゙!开个玩笑,动一动你的小手,点赞就完事了,你每个人出一份力量(点赞 + 评论) 就会让更多的学习者加入进来!非常感谢!~ω~=

正文完
 0