转载

SpringBoot结合Sharding-JDBC实现分库分表

前言:

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

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

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

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

1、项目信息描述:

SpringBoot结合Sharding-JDBC实现分库分表

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

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

Demo详解:

1、工程目录:

SpringBoot结合Sharding-JDBC实现分库分表

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、架构图:

SpringBoot结合Sharding-JDBC实现分库分表

​ 图片来源: sharding-JDBC官网

4、数据分片:

进行分库分表时,是绕不开 数据分片

的知识的。

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

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

4.1、垂直分片:

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

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

缺点:

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

4.2、水平分片:

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

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

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

用户名字段进行分片;

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

5、内部执行流程:

核心由 SQL解析 => 执行器优化 => SQL路由 => SQL改写 => SQL执行 => 结果归并

的流程组成。

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

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

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

SpringBoot结合Sharding-JDBC实现分库分表

Sharding-JDBC 集成过程:

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

SpringBoot结合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)

SpringBoot结合Sharding-JDBC实现分库分表

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

0-119,120-249,250-359

SpringBoot结合Sharding-JDBC实现分库分表

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

3、自定义分片算法:

3.1、自定义分库实现:

/**
 * @PACKAGE_NAME: com.lyl.algorithm
 * @ClassName: MyPreciseDBShardingAlgorithm
 * @Description:  自定义数据库的精确分片算法,根据用户名进行分片
 * @Date: 2020-06-18 17:28
 **/
public class MyPreciseDBShardingAlgorithm  implements PreciseShardingAlgorithm<String> {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    // 分库虚拟节点数量
    public static int virtualNodeCount;

    // 分库虚拟节点范围
    public static String virtualNodeCountRang;


    public MyPreciseDBShardingAlgorithm(){

    };


    /**
     *  数据分片
     * @param dbNames 实际数据源集合; springboot0、springboot1
     * @param preciseShardingValue 分片键 name 用户名值
     * @return
     */
    @Override
    public String doSharding(Collection<String> dbNames, PreciseShardingValue<String> preciseShardingValue) {

        Props props = new Props("application-shardingJDBC.properties");
        // 初始化分库的虚拟节点数量和范围
        virtualNodeCount = props.getInt(SystemConstant.SHARDING_DATASOURCE_VIRTUAL_NODE_COUNT);
        virtualNodeCountRang = props.getStr(SystemConstant.SHARDING_DATASOURCE_VIRTUAL_NODE_COUNT_RANG);

        // 根据用户名的hash值对《virtualNodeCount》进行取余后,得到余数,余数一定在0,《virtualNodeCount》之间的
        Integer mod = preciseShardingValue.getValue().hashCode() % virtualNodeCount;

        // 由于获取的字符串的hash值可能存在负数,所以需要需要取其绝对值
        mod = ShardingUtils.getAbsoluteValue(mod);

        // 虚拟节点范围映射到实际物理节点
        Integer shardingValue = ShardingUtils.getPhysicNodeByVisualNode(mod, virtualNodeCountRang);

        for (String each : dbNames) {
            // 将余数与配置的实际数据库名进行匹配
            if (each.endsWith(String.valueOf(shardingValue))) {
                logger.info("logic DB : "+ each);
                return each;
            }
        }

        throw new UnsupportedOperationException();
    }
}

3.2、自定义分表算法:

/**
 * @PACKAGE_NAME: com.lyl.algorithm
 * @ClassName: MyPreciseTableShardingAlgorithm
 * @Description:  自定义表的精确分片算法,根据用户名进行分片
 * @Date: 2020-06-18 17:11
 **/
public class MyPreciseTableShardingAlgorithm implements PreciseShardingAlgorithm<String> {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    // 分表虚拟节点数量
    public static int virtualNodeCount;

    // 分表虚拟节点范围
    public static String virtualNodeCountRang;


    public MyPreciseTableShardingAlgorithm(){

    };


    /**
     *  数据分片
     * @param tableNames 实际表集合; t_user0、t_user1、t_user2
     * @param preciseShardingValue 分片键 name 用户名值
     * @return
     */
    @Override
    public String doSharding(Collection<String> tableNames, PreciseShardingValue<String> preciseShardingValue) {

        Props props = new Props("application-shardingJDBC.properties");
        // 初始化分表的虚拟节点数量和范围
        virtualNodeCount = props.getInt(SystemConstant.SHARDING_TABLE_VIRTUAL_NODE_COUNT);
        virtualNodeCountRang = props.getStr(SystemConstant.SHARDING_TABLE_VIRTUAL_NODE_COUNT_RANG);


        // 根据用户名的hash值对《virtualNodeCount》进行取余后,得到余数,余数一定在0,《virtualNodeCount》之间的
        Integer mod =  preciseShardingValue.getValue().hashCode() % virtualNodeCount;

        // 由于获取的字符串的hash值可能存在负数,所以需要需要取其绝对值
        mod = ShardingUtils.getAbsoluteValue(mod);

        // 虚拟节点范围映射到实际物理节点
        Integer shardingValue = ShardingUtils.getPhysicNodeByVisualNode(mod, virtualNodeCountRang);

        for (String each : tableNames) {
            // 将余数与配置的实际表名进行匹配
            if (each.endsWith(String.valueOf(shardingValue))) {
                logger.info("logic table : "+ each);
                return each;
            }
        }

        throw new UnsupportedOperationException();
    }
}

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

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

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

原文  https://segmentfault.com/a/1190000022983968
正文到此结束
Loading...