Sharding-JDBC与Spring Boot结合使用

1、什么是的Sharding-JDBC

Sharding-jdbc是轻量Java框架,为Java的JDBC层提供服务,它是由客户端直接连数据库,以Jar包的形式提供服务,无需额外的部署和依赖,可以理解为增强版的JDBC驱动,可以很好的兼容JDBC和各种ORM框架

2、是为了解决什么样的业务场景(问题)?

随着公司业务量的增长,大部分的数据库架构为了承担流量压力和让数据更安全些,一般会做个主从来搞搞备份,流量大点的在搞个读写分离就可以解决外部流量所带给数据库的压力,这可以称之为“外部压力带来的瓶颈”。除了前面的外部原因,其内部数据存储也存在瓶颈,因为关系型数据库大多采用B+树类型的索引(MySQL)在数据量超过阈值的情况下,索引深度的增加也将使得磁盘访问的 IO 次数增加,进而导致查询性能的下降。单一数据库实例的数据的阈值在 1TB 之内,是比较合理的范围。
通过分库和分表进行数据的拆分来使得各个表的数据量保持在阈值以下,以及对流量进行疏导应对高访问量,是应对高并发和海量数据系统的有效手段

3、分表和分库解决了哪些问题?

分表可以提高查询速度
关系型数据库大多采用 B+ 树类型的索引,在数据量超过阈值的情况下,索引深度的增加也将使得磁盘访问的 IO 次数增加,进而导致查询性能的下降;

分库能够用于有效的分散对数据库单点的访问量,提高数据库的可用性。

4、数据如何拆分

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

数据分片的拆分方式又分为垂直分片和水平分片

5、从理论上对数据的拆分分为垂直拆分水平拆分,一般工作中常采用的是水平拆分,因为拆分难度小,能够很好的解决数据量大查询速度慢的问题,并且扩展相对自由,是数据分片的标准方案.

6、数据分片固然好,如果我们想考虑数据分片时,自己动手去做,就会发现几个巨麻烦的问题。

(1)数据被分片后,我们插入和查询分片表的数据时要考虑具体要操作某某库的某某表,这里要我们自己实现的话,就需要整块代码来实现SQL路由判断,确定当前sql要使用的数据库和表名
(2)因为我们操作分片表一个表名orders可能对应着实际表10多个比如 orders_1、 orders_2、 orders_3 等等,这里在操作时要有表名称修改,也就是程序员自己要实现SQL变更,改成正确的sql去各个库里执行。
(3)当然group by 语法、order by排序语法有数据聚合要处理,需要查询所有sql后进行数据的聚合处理。。。

7、好,说了一大堆进入正题,6的问题出现,作为jar包搬用工的我自然需要找个合适的jar包去解决这些痛点问题,Sharding-jdbc 正好能满足分库分表的需求。

开始使用

8、首先搭建主从复制,使用docker

  • 创建数据卷目录
mkdir db
cd db
mkdir -p mysql_{master,slave}/{conf,data,logs}
  • 修改my.cnf文件
## 主库cnf

[mysqld]
## 设置server_id,注意要唯一
server-id=201

## 开启日志功能,mysql-bin是日志文件名,可自定义
log-bin=mysql-bin 

## 需要同步的数据库名
## 如果需要同步多个数据库,则添加多个binlog-do-db
binlog-do-db=ds0
binlog-do-db=ds1
## 从库cnf

[mysqld]
## 设置server_id,注意要唯一
server-id=202
  • 编写docker-compose.yml文件,其中mysql_master/confmysql_slave/conf路径下放置数据库的配置文件
version : '3.8'
services:
  mysql_master:
    container_name: mysql_master
    restart: always
    image: mysql:5.7
    ports:
      - "3307:3306"
    volumes:
      - /tmp/db/mysql_master/conf:/etc/mysql/conf.d
      - /tmp/db/mysql_master/data:/var/lib/mysql
      - /tmp/db/mysql_master/logs:/var/log/mysql
    environment:
      TZ: Asia/Shanghai
      MYSQL_ROOT_PASSWORD: 123456
  mysql_slave:
    container_name: mysql_slave
    restart: always
    image: mysql:5.7
    ports:
      - "3308:3306"
    volumes:
      - /tmp/db/mysql_slave/conf:/etc/mysql/conf.d
      - /tmp/db/mysql_slave/data:/var/lib/mysql
      - /tmp/db/mysql_slave/logs:/var/log/mysql
    environment:
      TZ: Asia/Shanghai
      MYSQL_ROOT_PASSWORD: 123456
## 运行容器
sudo docker compose up -d
  • 主库开启远程访问, 进入到mysql-master容器内, mysql -uroot -p123456 登录后:
## 远程访问
grant all privileges on *.* to 'root'@'%' identified by '123456' with grant option;
## 刷新权限
flush privileges;
## 显示主库状态
show master status;
  • 从库开启远程访问并启动主从配置
## 远程访问
grant all privileges on *.* to 'root'@'%' identified by '123456' with grant option;
## 远程访问
flush privileges;

## 配置要跟从的主库
## port是数字类型
use ds0;
change master to master_host='你主库的ip地址',master_port=3307,master_user='root',master_password='123456';

## 开启主从
start slave;

## 查看主从状态
show slave status\G

主库创建数据库后,从库不用主动手工创建数据库,启用主从后会自动创建数据库

  • 登录主库 执行sql,创建了10个客户表,后面使用。
create database ds0;
create database ds1;
use database ds1;
CREATE TABLE `clients_1` (
  `user_id` bigint(20) NOT NULL COMMENT '用户ID',
  `name` varchar(100) DEFAULT NULL comment '用户名称',
   `created_at` timestamp COMMENT '创建时间',
  PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='客户表';

CREATE TABLE `clients_2` (
  `user_id` bigint(20) NOT NULL COMMENT '用户ID',
  `name` varchar(100) DEFAULT NULL comment '用户名称',
   `created_at` timestamp COMMENT '创建时间',
  PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='客户表';

CREATE TABLE `clients_3` (
  `user_id` bigint(20) NOT NULL COMMENT '用户ID',
  `name` varchar(100) DEFAULT NULL comment '用户名称',
   `created_at` timestamp COMMENT '创建时间',
  PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='客户表';

CREATE TABLE `clients_4` (
  `user_id` bigint(20) NOT NULL COMMENT '用户ID',
  `name` varchar(100) DEFAULT NULL comment '用户名称',
   `created_at` timestamp COMMENT '创建时间',
  PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='客户表';

CREATE TABLE `clients_5` (
  `user_id` bigint(20) NOT NULL COMMENT '用户ID',
  `name` varchar(100) DEFAULT NULL comment '用户名称',
   `created_at` timestamp COMMENT '创建时间',
  PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='客户表';

CREATE TABLE `clients_6` (
  `user_id` bigint(20) NOT NULL COMMENT '用户ID',
  `name` varchar(100) DEFAULT NULL comment '用户名称',
   `created_at` timestamp COMMENT '创建时间',
  PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='客户表';

CREATE TABLE `clients_7` (
  `user_id` bigint(20) NOT NULL COMMENT '用户ID',
  `name` varchar(100) DEFAULT NULL comment '用户名称',
   `created_at` timestamp COMMENT '创建时间',
  PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='客户表';

CREATE TABLE `clients_8` (
  `user_id` bigint(20) NOT NULL COMMENT '用户ID',
  `name` varchar(100) DEFAULT NULL comment '用户名称',
   `created_at` timestamp COMMENT '创建时间',
  PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='客户表';

CREATE TABLE `clients_9` (
  `user_id` bigint(20) NOT NULL COMMENT '用户ID',
  `name` varchar(100) DEFAULT NULL comment '用户名称',
   `created_at` timestamp COMMENT '创建时间',
  PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='客户表';

9 Spring Boot 接入Sharding-JDBC

  • 引入依赖,首先是pom.xml文件,这是本人自己走通的一个demo
<?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.7.6</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>sharding-jdbc-demo</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </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>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.2</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.8</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
            <version>4.1.1</version>
        </dependency>
        <!--分库分表工具-->
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
            <version>4.1.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-core-common</artifactId>
            <version>4.1.1</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.3.1</version>
        </dependency>
    </dependencies>


    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

主要的包是<artifactId>druid</artifactId><artifactId>sharding-jdbc-spring-boot-starter</artifactId><artifactId>sharding-core-common</artifactId>

  • 配置文件 application.yml
server:
  port: 8088

spring:
  application:
    name: sharding-jdbc-demo

  shardingsphere:
    ## 全局属性
    ## 显示SQL语句
    props:
      sql:
        show: true

    datasource:
      names: master0,master1,slave0,slave1
      master0:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3307/ds0?serverTimezone=UTC&useSSL=false&characterEncoding=utf-8
        username: root
        password: 123456

      slave0:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3308/ds0?serverTimezone=UTC&useSSL=false&characterEncoding=utf-8
        username: root
        password: 123456

      master1:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3307/ds1?serverTimezone=UTC&useSSL=false&characterEncoding=utf-8
        username: root
        password: 123456

      slave1:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3308/ds1?serverTimezone=UTC&useSSL=false&characterEncoding=utf-8
        username: root
        password: 123456

    ## 读写分离策略,可配置多组主从节点
    sharding:
      master-slave-rules:
        ## 一组主从节点的命名
        master0:
          master-data-source-name: master0
          slave-data-source-names: slave0
          ## 负载均衡策略
          ## ROUND_ROBIN 轮询
          ## RANDOM 随机
          ## WEIGHT 权重
          load-balance-algorithm-type: ROUND_ROBIN

        master1:
          master-data-source-name: master1
          slave-data-source-names: slave1
          load-balance-algorithm-type: ROUND_ROBIN

      ## 如果未配置分库规则,则默认使用本数据据源进行读写
      ## 如果不显示定义主从关系,默认所有数据源均是从库,只能读不能写(写操作报错)
      #default-data-source-name: master1
      tables:
        ## 逻辑表的名字
        clients:
          ## 分布式序列配置,支持雪花算法和UUID
          key-generator:
            column: user_id
            type: SNOWFLAKE
          ## 枚举数据节点:数据源.真实表名
          actual-data-nodes: master1.clients_$->{0..9}
          ## 分表策略
          table-strategy:
            inline:
              sharding-column: user_id
              algorithm-expression: clients_$->{user_id % 10}
  • 使用mybatis-plus mapper文件内容
package com.example.demo.mapper;

import com.example.demo.pojo.Clients;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;

/**
* @author ws
* @description 针对表【clients(客户表)】的数据库操作Mapper
* @createDate 2023-07-25 20:18:07
* @Entity com.example.demo.pojo.Clients
*/
@Mapper
public interface ClientsMapper extends BaseMapper<Clients> {

}

表对应类

package com.example.demo.pojo;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.util.Date;
import lombok.Data;

/**
 * 客户表
 * @TableName clients
 */
@TableName(value ="clients")
@Data
public class Clients implements Serializable {
    /**
     * 用户ID
     */
    @TableId
    private Long userId;

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

    /**
     * 创建时间
     */
    private Date createdAt;

    @TableField(exist = false)
    private static final long serialVersionUID = 1L;
}
  • 进行测试
package com.example.demo;

import com.example.demo.mapper.ClientsMapper;
import com.example.demo.mapper.OrdersMapper;
import com.example.demo.pojo.Clients;
import com.example.demo.pojo.Orders;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;
import java.util.Date;

/**
 * HelloController Tester.
 *
 * @author ws
 * @version 1.0
 * @since <pre>7月 25, 2023</pre>
 */
@SpringBootTest(classes = DemoApplication.class)
public class HelloControllerTest {
    @Resource
    private OrdersMapper ordersMapper;

    @Resource
    private ClientsMapper clientsMapper;


    @Test
    void insertClients() {
        for (int i = 1; i < 100000; i++) {
            Clients clients = new Clients();
            clients.setName("编号-" + i);
            clients.setCreatedAt(new Date());
            clientsMapper.insert(clients);
        }
    }

    @Test
    void selectClientsId(){
        Orders order = ordersMapper.selectById(1683778381960814593L);
        System.out.println(order);
    }
} 

  • 测试插入insertClients方法,可以清晰的看出,Sharding-jdbc 数据源使用的master1, 并对sql进行了改写。


  • 插入的数据



总结

Sharding-jdbc对分表分库挺好用的,本文仅是对此框架的简单实践,在起初配置maven包时就遇到了各种异常的报错,有报"DataSource not allow empty"的 有报url什么错的,有些是配置 有些是包冲突,本人在找合适的包的过程极度折磨,耗时良久!在配置文件这块其实有很多的规则还需要读者自行查询学习,本文的记录到此结束。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,185评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,652评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,524评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,339评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,387评论 6 391
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,287评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,130评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,985评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,420评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,617评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,779评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,477评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,088评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,716评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,857评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,876评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,700评论 2 354

推荐阅读更多精彩内容