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/conf
与mysql_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什么错的,有些是配置 有些是包冲突,本人在找合适的包的过程极度折磨,耗时良久!在配置文件
这块其实有很多的规则还需要读者自行查询学习,本文的记录到此结束。