Spring/SpringBoot系列之SpringBoot集成ShardingSphere实现分库分表实战【二十四】

1. 前言

去年开发一个项目的时候,因为系统的核心数据是定时从外界发送过来的,数据量比较大,后来很快单表就达到了千万级别,这就需要分库分表,最后选择了ShardingSphere,原因就是比较容易上手。

2. Sharding JDBC简介

官网地址:https://shardingsphere.apache.org/

Spring/SpringBoot系列之SpringBoot集成ShardingSphere实现分库分表实战【二十四】_第1张图片
如上图所示,当前版本是4.x,并且官网支持中文阅读。点击文档下拉4.x版本:
Spring/SpringBoot系列之SpringBoot集成ShardingSphere实现分库分表实战【二十四】_第2张图片
简介如下:

  • ShardingSphere是一套开源的分布式数据库中间件解决方案组成的生态圈,它由Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar(计划中)这3款相互独立的产品组成。 他们均提供标准化的数据分片、分布式事务和数据库治理功能,可适用于如Java同构、异构语言、云原生等各种多样化的应用场景。
  • ShardingSphere定位为关系型数据库中间件,旨在充分合理地在分布式的场景下利用关系型数据库的计算和存储能力,而并非实现一个全新的关系型数据库。 它与NoSQL和NewSQL是并存而非互斥的关系。NoSQL和NewSQL作为新技术探索的前沿,放眼未来,拥抱变化,是非常值得推荐的。反之,也可以用另一种思路看待问题,放眼未来,关注不变的东西,进而抓住事物本质。 关系型数据库当今依然占有巨大市场,是各个公司核心业务的基石,未来也难于撼动,我们目前阶段更加关注在原有基础上的增量,而非颠覆。
  • ShardingSphere已经在2020年4月16日从Apache孵化器毕业,成为Apache顶级项目。

Spring/SpringBoot系列之SpringBoot集成ShardingSphere实现分库分表实战【二十四】_第3张图片

2.1 Sharding-JDBC简介

定位为轻量级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标准的数据库。

Spring/SpringBoot系列之SpringBoot集成ShardingSphere实现分库分表实战【二十四】_第4张图片

2.2 Sharding-Proxy和Sharding-Sidecar(TODO)

这两部分的介绍可以阅读官网,这里就不介绍了。

3. 项目实战

本项目基于SpringBoot 2.1.5使用ShardingSphere和Mybatis-Plus实现分库分表。

3.1 pom.xml引入依赖

<?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.shardingsphere</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>基于 Spring Boot 2.1.15 使用sharding-sphere + Mybatis-Plus 实现分库分表</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.1.1</version>
        </dependency>

        <dependency>
            <groupId>io.shardingsphere</groupId>
            <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
            <version>3.1.0</version>
        </dependency>
        <dependency>
            <groupId>io.shardingsphere</groupId>
            <artifactId>sharding-jdbc-spring-namespace</artifactId>
            <version>3.1.0</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

3.2 创建数据库和表

ds0
  ├── user_0
  └── user_1

ds1
  ├── user_0
  └── user_1

既然是分库分表 库结构与表结构一定是一致的。

数据库: ds0

CREATE DATABASE IF NOT EXISTS `ds0`/*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci */;
USE `ds0`;

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for user_0
-- ----------------------------
DROP TABLE IF EXISTS `user_0`;
CREATE TABLE `user_0`(
	`id` INT(11) NOT NULL,
	`name` VARCHAR(255) DEFAULT NULL,
	`age` INT(11) DEFAULT NULL,
	PRIMARY KEY (`id`)
)ENGINE=INNODB DEFAULT charset=utf8mb4;

-- ----------------------------
-- Table structure for user_1
-- ----------------------------
DROP TABLE IF EXISTS `user_1`;
CREATE TABLE `user_1`(
	`id` INT(11) NOT NULL,
	`name` VARCHAR(255) DEFAULT NULL,
	`age` INT(11) DEFAULT NULL,
	PRIMARY KEY (`id`)
)ENGINE=INNODB DEFAULT charset=utf8mb4;

SET FOREIGN_KEY_CHECKS = 1;

数据库:ds1

CREATE DATABASE IF NOT EXISTS `ds1`/*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci */;
USE `ds1`;

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for user_0
-- ----------------------------
DROP TABLE IF EXISTS `user_0`;
CREATE TABLE `user_0`(
	`id` INT(11) NOT NULL,
	`name` VARCHAR(255) DEFAULT NULL,
	`age` INT(11) DEFAULT NULL,
	PRIMARY KEY (`id`)
)ENGINE=INNODB DEFAULT charset=utf8mb4;

-- ----------------------------
-- Table structure for user_1
-- ----------------------------
DROP TABLE IF EXISTS `user_1`;
CREATE TABLE `user_1`(
	`id` INT(11) NOT NULL,
	`name` VARCHAR(255) DEFAULT NULL,
	`age` INT(11) DEFAULT NULL,
	PRIMARY KEY (`id`)
)ENGINE=INNODB DEFAULT charset=utf8mb4;

SET FOREIGN_KEY_CHECKS = 1;

3.3 application.yml

# 数据源 ds0,ds1
sharding:
  jdbc:
    datasource:
      names: ds0,ds1
      # 第一个数据库
      ds0:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.jdbc.Driver
        jdbc-url: jdbc:mysql://192.168.67.1:3306/ds0?characterEncoding=utf-8
        username: root
        password: 123
      # 第二个数据库
      ds1:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.jdbc.Driver
        jdbc-url: jdbc:mysql://192.168.67.1:3306/ds1?characterEncoding=utf-8
        username: root
        password: 123
    # 水平拆分的数据库(表) 配置分库 + 分表策略 行表达式分片策略
    config:
      sharding:
        # 分库策略
        default-database-strategy:
          inline:
            sharding-column: id
            algorithm-expression: ds$->{id % 2}
        # 分表策略 其中user为逻辑表 分表主要取决于age行
        tables:
          user:
            actual-data-nodes: ds$->{0..1}.user_$->{0..1}
            table-strategy:
              inline:
                sharding-column: age
                # 分片算法表达式
                algorithm-expression: user_$->{age % 2}
            # 主键 UUID 18位数 如果是分布式还要进行一个设置 防止主键重复
            # key-generator-column-name: id        
      # 打印执行的数据库
      props: 
        sql:
          show: true
          
# 打印执行的sql语句          
spring:
  main:
    allow-bean-definition-overriding: true

使用配置文件方式实现分库以及分表,配置说明:

  1. 逻辑表 user:水平拆分的表的总称。例:用户数据根据主键尾数拆分为2张表,分别是user0到user1,他们的逻辑表名为user。
  2. 真实表: 在分片的数据库中真实存在的物理表。即上个示例中的user0到user1
  3. 分片算法:Hint分片算法,对应HintShardingAlgorithm,用于处理使用Hint行分片的场景。需要配合HintShardingStrategy使用。
  4. 分片策略:行表达式分片策略,对应InlineShardingStrategy。使用Groovy的表达式,提供对SQL语句中的=和IN的分片操作支持,只支持单分片键。对于简单的分片算法,可以通过简单的配置使用,从而避免繁琐的Java代码开发,如: user$->{id % 2} 表示user表根据id模2,而分成2张表,表名称为user_0到user_1。
  5. 自增主键策略:通过在客户端生成自增主键替换以数据库原生自增主键的方式,做到分布式主键无重复。采用UUID.randomUUID()的方式产生分布式主键。或者 SNOWFLAKE

3.4 实体类/mapper/service/controller

实体类:

package com.shardingsphere.demo.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import lombok.Data;

@Data
@TableName("user")
public class User extends Model<User> {
    /**
     * 主键id
     */
    private int id;

    /**
     * 名称
     */
    private String name;

    /**
     * 年龄
     */
    private int age;
}

mapper(记得在启动类上添加mapper扫描路径@MapperScan(“com.shardingsphere.demo.dao”)):

package com.shardingsphere.demo.dao;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.shardingsphere.demo.entity.User;

public interface UserMapper extends BaseMapper<User> {
}

service:

package com.shardingsphere.demo.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.shardingsphere.demo.entity.User;

public interface UserService extends IService<User> {

}

serviceImpl:

package com.shardingsphere.demo.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.shardingsphere.demo.dao.UserMapper;
import com.shardingsphere.demo.entity.User;
import com.shardingsphere.demo.service.UserService;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}

controller:

package com.shardingsphere.demo.controller;

import com.shardingsphere.demo.entity.User;
import com.shardingsphere.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class UserController {
    @Autowired
    private UserService userService;

    @GetMapping("/select")
    public List<User> select(){
        return userService.list();
    }

    @GetMapping("/insert")
    public Boolean insert(User user){
        return userService.save(user);
    }
}

4. 测试

启动项目,然后打开浏览器分别访问:

http://localhost:8080/insert?id=1&name=1hd&age=12
http://localhost:8080/insert?id=2&name=1hd&age=13
http://localhost:8080/insert?id=3&name=1hd&age=14
http://localhost:8080/insert?id=4&name=1hd&age=15

然后查看控制台日志:
Spring/SpringBoot系列之SpringBoot集成ShardingSphere实现分库分表实战【二十四】_第5张图片
Spring/SpringBoot系列之SpringBoot集成ShardingSphere实现分库分表实战【二十四】_第6张图片
Spring/SpringBoot系列之SpringBoot集成ShardingSphere实现分库分表实战【二十四】_第7张图片
Spring/SpringBoot系列之SpringBoot集成ShardingSphere实现分库分表实战【二十四】_第8张图片
根据分片算法和分片策略,不同的id以及age取模后添加到不同库的不同表里,这就达到了分库分表的结果。

查询测试,访问http://localhost:8080/select,查看控制台日志:
在这里插入图片描述
分别从ds0数据库两张表和ds1两张表查询结果 然后汇总结果返回。

你可能感兴趣的:(java)