ShardingJDBC

ShardingJDBC

_

🐤 Apache ShardingSphere概述

Apache ShardingSphere 产品定位为 Database Plus,旨在构建多模数据库上层的标准和生态。 它关注如何充分合理地利用数据库的计算和存储能力,而并非实现一个全新的数据库。ShardingSphere 站在数据库的上层视角,关注他们之间的协作多于数据库自身。

连接、增量和可插拔是 Apache ShardingSphere 的核心概念。

  • 连接:通过对数据库协议、SQL 方言以及数据库存储的灵活适配,快速的连接应用与多模式的异构数据库;
  • 增量:获取数据库的访问流量,并提供流量重定向(数据分片、读写分离、影子库)、流量变形(数据加密、数据脱敏)、流量鉴权(安全、审计、权限)、流量治理(熔断、限流)以及流量分析(服务质量分析、可观察性)等透明化增量功能;
  • 可插拔:项目采用微内核 + 三层可插拔模型,使内核、功能组件以及生态对接完全能够灵活的方式进行插拔式扩展,开发者能够像使用积木一样定制属于自己的独特系统。

DatabasePlus

🐪 简介

Apache ShardingSphere 由 JDBC、Proxy 和 Sidecar(规划中)这 3 款既能够独立部署,又支持混合部署配合使用的产品组成。 它们均提供标准化的基于数据库作为存储节点的增量功能,可适用于如 Java 同构、异构语言、云原生等各种多样化的应用场景。

关系型数据库当今依然占有巨大市场份额,是企业核心系统的基石,未来也难于撼动,我们更加注重在原有基础上提供增量,而非颠覆。

🐼 ShardingSphere-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 标准的数据库。

🦘 ShardingSphere-Proxy

定位为透明化的数据库代理端,提供封装了数据库二进制协议的服务端版本,用于完成对异构语言的支持。 目前提供 MySQL 和 PostgreSQL(兼容 openGauss 等基于 PostgreSQL 的数据库)版本,它可以使用任何兼容 MySQL/PostgreSQL 协议的访问客户端(如:MySQL Command Client, MySQL Workbench, Navicat 等)操作数据,对 DBA 更加友好。

  • 向应用程序完全透明,可直接当做 MySQL/PostgreSQL 使用。
  • 适用于任何兼容 MySQL/PostgreSQL 协议的的客户端。

ShardingSphere-Proxy

🐛 ShardingSphere-Sidecar(TODO)

定位为 Kubernetes 的云原生数据库代理,以 Sidecar 的形式代理所有对数据库的访问。 通过无中心、零侵入的方案提供与数据库交互的啮合层,即 Database Mesh,又可称数据库网格。

Database Mesh 的关注重点在于如何将分布式的数据访问应用与数据库有机串联起来,它更加关注的是交互,是将杂乱无章的应用与数据库之间的交互进行有效地梳理。 使用 Database Mesh,访问数据库的应用和数据库终将形成一个巨大的网格体系,应用和数据库只需在网格体系中对号入座即可,它们都是被啮合层所治理的对象。

shardingsphere-sidecar-brief

❄️ 三个组件的比较

- ShardingSphere-JDBC ShardingSphere-Proxy ShardingSphere-Sidecar
数据库 任意 MySQL/PostgreSQL MySQL/PostgreSQL
连接消耗数
异构语言 仅 Java 任意 任意
性能 损耗低 损耗略高 损耗低
无中心化
静态入口

mysql安装

卸载系统⾃带的Mariadb(如果有)

rpm -qa|grep mariadb

yum -y remove [上面搜到的,没有就算了]

下载:

下载地址:https://downloads.mysql.com/archives/community/

上传到服务器/opt目录

解压缩

tar -zxvf mysql-5.7.36-linux-glibc2.12-x86_64.tar.gz 

改名

mv mysql-5.7.36-linux-glibc2.12-x86_64.tar.gz  mysql

依赖安装

MySQL依赖于库。如果未在本地安装此库,则数据目录初始化和后续服务器启动步骤将失败。如有必要,请使用相应的包管理器进行安装。

基于 Yum 的系统:

# search for info
yum search libaio
# install library
yum install libaio

基于 APT 的系统:

# search for info
apt-cache search libaio
 # install library
apt-get install libaio1

创建用户组与用户

groupadd mysql

useradd -r -g mysql -s /bin/false mysql

#修改mysql⽬录的归属⽤户
chown -R mysql:mysql ./

创建文件夹与配置文件

cd mysql 

mkdir data

vim /etc/my.cnf

文件内容

[mysql]
# 设置mysql客户端默认字符集
default-character-set=utf8mb4
[mysqld]
#设置3306端口
port = 3305
# 设置mysql的安装目录
basedir=/opt/mysql
# 设置mysql数据库的数据的存放目录
datadir=/opt/mysql/data
# 允许最大连接数
max_connections=200
# 服务端使用的字符集默认为UTF8
character-set-server=utf8mb4
# 创建新表时将使用的默认存储引擎
default-storage-engine=INNODB

初始化

./bin/mysqld --initialize --user=mysql --basedir=/opt/mysql --datadir=/opt/mysql/data

错误:

./bin/mysqld: error while loading shared libraries: libnuma.so.1: cannot open shared object file: No such file or directory

如果安装mysql出现了以上的报错信息.这是缺少numactl

#centos  安装
yum -y install numactl

#ubuntu
sudo apt-get install numactl

记住密码
mysqlpassword

编辑启动连接

 cp ./support-files/mysql.server /etc/init.d/mysql

修改/etc/init.d/mysql

vim /etc/init.d/mysql


#修改内容
basedir=/opt/mysq
datadir=/opt/mysql/data

设置MYSQL系统服务并开启⾃启

⾸先增加 mysql 服务控制脚本执⾏权限:

chmod +x /etc/init.d/mysql

同时将 mysqld 服务加⼊到系统服务:

chkconfig --add mysql

最后检查 mysqld 服务是否已经⽣效即可:

chkconfig --list mysql

设置环境变量

vim /etc/profile

#在尾部加上
MYSQL_HOME=/opt/mysql
export PATH=$PATH:$MYSQL_HOME/bin

#刷新环境
source /etc/profile

启动

#启动
systemctl start mysql

#关闭
systemctl stop mysql

登陆mysql

mysql -uroot -p

修改密码

 # 111111= 密码
alter user user() identified by "111111";

设置远程登陆

use mysql;
update user set user.Host='%' where user.User='root';

#刷新权限
flush privileges;

mysql主从复制原理

mysql主从复制原理图

  1. 主服务器MySQL服务将所有的写操作记录在 binlog 日志中,并生成 log dump 线程,将 binlog 日志传给从服务器MySQL服务的 I/O 线程。
  2. 从服务器MySQL服务生成两个线程,一个是 I/O 线程,另一个是 SQL 线程。
  3. 从库 I/O 线程去请求主库的 binlog 日志,并将 binlog 日志中的文件写入 relaylog(中继日志)中。
  4. 从库的 SQL 线程会读取 relaylog 中的内容,并解析成具体的操作,来实现主从的操作一致,达到最终两个数据库数据一致的目的。

注意点:

  • 主从复制是异步的逻辑的 SQL 语句级的复制;
  • 复制时,主库有一个 I/O 线程,从库有两个线程,及 I/O 和 SQL 线程;
  • 实现主从复制的必要条件是主库要开启记录 binlog 的功能;
  • 作为复制的所有 MySQL 节点的 server-id 都不能相同;
  • binlog 文件只记录对数据内容有更改的 SQL 语句,不记录任何查询语句。

mysql主从复制配置

vim /etc/my.cnf

Master节点myc.cnf配置

配置文件比较重要的就全局唯一id(server-id)和二进制日志功能

[mysqld]
## 同一局域网内注意要唯一
server-id=100  
## 开启二进制日志功能,可以随便取(关键)
log-bin=mysql-bin
## 复制过滤:不需要备份的数据库,不输出(mysql库一般不同步)
binlog-ignore-db=mysql
## 为每个session 分配的内存,在事务过程中用来存储二进制日志的缓存
binlog_cache_size=1M
## 主从复制的格式(mixed,statement,row,默认格式是statement)
binlog_format=mixed

Slave节点myc.cnf配置

[mysqld]
## 设置server_id,注意要唯一
server-id=102
## 开启二进制日志功能,以备Slave作为其它Slave的Master时使用
log-bin=mysql-slave-bin
## relay_log配置中继日志
relay_log=edu-mysql-relay-bin
##复制过滤:不需要备份的数据库,不输出(mysql库一般不同步)
binlog-ignore-db=mysql
## 如果需要同步函数或者存储过程
log_bin_trust_function_creators=true
## 为每个session 分配的内存,在事务过程中用来存储二进制日志的缓存
binlog_cache_size=1M
## 主从复制的格式(mixed,statement,row,默认格式是statement)
binlog_format=mixed
## 跳过主从复制中遇到的所有错误或指定类型的错误,避免slave端复制中断。
## 如:1062错误是指一些主键重复,1032错误是因为主从数据库数据不一致
slave_skip_errors=1062

创建主服务器复制用户及相关权限

#创建用户
create user 'slave'@'%' identified by '123456';

#设置用户权限
grant replication slave,replication client on *.* to 'slave'@'%';

#刷新权限
flush privileges;

#查看用户权限
show grants for 'slave'@'%';

查看主服务器 binlog 记录状态

show master status;

master状态

从服务器配置连接主服务器及设置复制的起始节点

change master to master_host='ip', master_user='slave', master_password='password', master_port=3306, master_log_file='mysql-bin.000001',master_log_pos=154;
  • master_host 主服务器密码
  • master_port主服务器端口
  • master_user 上面创建的用户名
  • master_password 上面创建的用户的密码
  • master_log_file show master status 状态查出来的File名称
  • master_log_pos show master status 状态查出来的Position

启动主从复制

start slave;

查看复制状态

show slave status \G

slave状态

两个yes说明成功。

其他

如果不小心在slave进行了增删改操作,或者slave机器重启,事务回滚都会导致下面的异常

slave同步异常状态

解决办法:

#停止同步
stop slave ;

set GLOBAL SQL_SLAVE_SKIP_COUNTER=1;

#启动同步
start slave ;

或者从 ------>从服务器配置连接主服务器及设置复制的起始节点 ||| 那重新配置一次即可解决。

mysql读写分离实现

master库建库建表加入测试数据

#建库 if not exists:判断有没有   default charset:默认字符集  collate utf8_general_ci:排序规则
create database if not exists shardingJdbc default charset utf8mb4 collate utf8mb4_general_ci;

#选择数据库
use shardingJdbc;

#建表
create table if not exists student(
    s_id int(11) not null auto_increment comment '学号',
    s_name varchar(11) not null  comment '姓名',
    s_age int(3) not null  comment '年龄',
    s_sex int(1) not null  comment '性别',
    primary key (s_id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 comment '学生表';


# 给表添加数据
insert into student values(default,'小白',18,0);

创建springboot项目

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

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

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </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.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.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.9</version>
        </dependency>

yaml配置

server:
  port: 8085

spring:
  main:
    allow-bean-definition-overriding: true

  shardingsphere:
    props:
      sql:
        show: true

    datasource:
      # 给每个数据源取名,名称任意
      names: master,slave0,slave1

      # 配置每个数据源的连接信息
      master:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://ip:port/shardingJdbc?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT
        username: root
        password: 123456
        maxPoolSize: 100
        minPoolSize: 5
      slave0:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://ip:port/shardingJdbc?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT
        username: root
        password: 123456
        maxPoolSize: 100
        minPoolSize: 5
      slave1:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://ip:port/shardingJdbc?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT
        username: root
        password: 123456
        maxPoolSize: 100
        minPoolSize: 5

    # 配置默认数据源
    sharding:
    # 默认数据源,主要用于写,注意一定要配置读写分离 ,注意:如果不配置,那么就会把三个节点都当做从slave节点,新增,修改和删除会出错.
      default-data-source-name: master

    # 配置数据源的读写分离,但是数据库一定要做主从复制
    masterslave:
      # 配置主从名称,可以任意取名字
      name: ms
      # 配置主库master,负责数据的写入
      master-data-source-name: master
      # 配置从库slave节点
      slave-data-source-names: slave0,slave1
      # 配置slave节点的负载均衡均衡策略,采用轮询机制,默认随机:ANDOM
      load-balance-algorithm-type: round_robin



# 整合mybatis-plus的配置
mybatis-plus:
  mapper-locations: classpath:mapper/*Mapper.xml
  type-aliases-package: com.qc.entity
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

实体类

package com.qc.entity;

import lombok.Data;

/**
 * @author c
 * @date 2022-04-24 17:07
 */

@Data
public class Student {
    private Integer sId;
    private String sName;
    private Integer sAge;
    private Integer sSex;

}

mapper

import java.util.List;

/**
 * @author c
 * @date 2022-04-24 17:10
 */
@Repository
public interface StudentMapper {

    /**
     * 查询所有
     */
    List<Student> allStudent();

    /**
     * 新增
     */
    void addStudent(Student student);

    /**
     * 修改
     */
    void updateStudent(Student student);

    /**
     * 删除
     */
    void deleteStudent(Integer sId);

}

mapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qc.mapper.StudentMapper">

    <!--  查看所有数据  -->
    <select id="allStudent" resultType="student">
        select s_id,s_name,s_age,s_sex from student
    </select>

    <insert id="addStudent" parameterType="student">
        insert into student value(default ,#{sName},#{sAge},#{sSex})
    </insert>

    <update id="updateStudent" parameterType="student">
        update student
        <set>
            <if test="sName!=null">s_name = #{sName},</if>
            <if test="sAge!=null">s_age = #{sAge},</if>
            <if test="sSex!=null">s_sex = #{sSex},</if>
        </set>
        <where>
            s_id = #{sId}
        </where>
    </update>

    <delete id="deleteStudent" parameterType="integer">
        delete from student
        <where>
            s_id = #{sId}
        </where>
    </delete>


</mapper>

service

package com.qc.service;

import com.qc.entity.Student;

import java.util.List;

/**
 * @author c
 * @date 2022-04-24 17:32
 */
public interface StudentService {
    /**
     * 查询所有
     */
    List<Student> allStudent();

    /**
     * 新增
     */
    void addStudent(Student student);

    /**
     * 修改
     */
    void updateStudent(Student student);

    /**
     * 删除
     */
    void deleteStudent(Integer sId);

}

serviceimpl

package com.qc.service.impl;

import com.qc.entity.Student;
import com.qc.mapper.StudentMapper;
import com.qc.service.StudentService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

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

/**
 * @author c
 * @date 2022-04-24 17:33
 */
@Service
@Transactional(rollbackFor = {RuntimeException.class,Exception.class})
public class StudentServiceImpl implements StudentService {

    @Resource
    private StudentMapper studentMapper;

    @Override
    public List<Student> allStudent() {
        return studentMapper.allStudent();
    }

    @Override
    public void addStudent(Student student) {
        studentMapper.addStudent(student);
    }

    @Override
    public void updateStudent(Student student) {
        studentMapper.updateStudent(student);
    }

    @Override
    public void deleteStudent(Integer sId) {
        studentMapper.deleteStudent(sId);
    }
}

controller

package com.qc.controller;

import com.qc.entity.Student;
import com.qc.service.StudentService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.List;
import java.util.Random;

/**
 * @author c
 * @date 2022-04-24 17:42
 */
@RestController
public class StudentController {

    @Resource
    private StudentService studentService;

    @GetMapping("/allStudent")
    public List<Student> allStudent(){
        return studentService.allStudent();
    }

    @RequestMapping("/addStudent")
    public String addStudent(){
        Student student = new Student();
        student.setSName("新增用户"+ new Random().nextInt());
        student.setSAge(18);
        student.setSSex(0);
        studentService.addStudent(student);
        return "新增方法执行完毕!";
    }

    @RequestMapping("/updateStudent/{sId}")
    public String updateStudent(@PathVariable("sId") Integer sId){
        Student student = new Student();
        student.setSId(sId);
        student.setSName("修改过的id"+ new Random().nextInt());
        student.setSAge(100);
        student.setSSex(1);
        studentService.updateStudent(student);
        return "修改方法执行完毕!";
    }

    @RequestMapping("/deleteStudent/{sId}")
    public String deleteStudent(@PathVariable("sId") Integer sId){
       studentService.deleteStudent(sId);
        return "删除方法执行完毕!";
    }


}

启动类

package com.qc;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.qc.mapper")
public class ShardingJdbcApplication {

    public static void main(String[] args) {
        SpringApplication.run(ShardingJdbcApplication.class, args);
    }

}

结果

结果

执行insert、update、delete等操作时会走master源,而查询操作会在配置的两个slave源轮询。读写分离实现了。

踩坑日志:

如果是springboot项目的话不要使用整合版的druid-spring-boot-starter,会自动配置数据源,但是数据源配置在hardingsphere.datasource下,导致读取不到,然后报 Property ‘sqlSessionFactory’ or ‘sqlSessionTemplate’ are required 这种异常,使用普通版本druid就不会出现这种问题。但是druid的监控就需要在代码中配置。就算这样还是会有 filter 空白,sql监控也监控不到等等问题,就很尬。

或者使用 druid-spring-boot-starter 但是在启动类上加上

package com.qc;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication(exclude = {DruidDataSourceAutoConfigure.class})
@MapperScan("com.qc.mapper")
public class ShardingJdbcApplication {

    public static void main(String[] args) {
        SpringApplication.run(ShardingJdbcApplication.class, args);
    }

}

mysql分库分表

为啥要分库分表

分库分表目的:解决高并发,和数据量大的问题。

  • 高并发情况下,会造成IO读写频繁,自然就会造成读写缓慢,甚至是宕机。一般单库不要超过2k并发,NB的机器除外。
  • 数据量大的问题。主要由于底层索引实现导致,MySQL的索引实现为B+TREE,数据量其他,会导致索引树十分庞大,造成查询缓慢。第二,innodb的最大存储限制64TB。

要解决这些问题,最常见的做法就是分库分表。

分库分表的目的,是将一个表拆成N个表,就是让每个表的数据量控制在一定范围内,保证SQL的性能。 一个表数据建议不要超过500W

方式

数据分片又分为水平拆分垂直拆分:

  • 水平拆分: 统一个表的数据拆到不同的库不同的表中。可以根据时间、地区、或某个业务键维度,也可以通过hash进行拆分,最后通过路由访问到具体的数据。拆分后的每个表结构保持一致
  • 垂直拆分: 就是把一个有很多字段的表给拆分成多个表,或者是多个库上去。每个库表的结构都不一样,每个库表都包含部分字段。一般来说,可以根据业务维度进行拆分,如订单表可以拆分为订单、订单支持、订单地址、订单商品、订单扩展等表;也可以,根据数据冷热程度拆分,20%的热点字段拆到一个表,80%的冷字段拆到另外一个表。

分库分表实现

创建数据库环境

## 分库分表环境
# 建两个库  同一数据库不同数据库都行  方便学习  同一库弄
create database if not exists shardingJdbc0 default charset utf8mb4 collate utf8mb4_general_ci;
create database if not exists shardingJdbc1 default charset utf8mb4 collate utf8mb4_general_ci;

#每个库在来两张表
use shardingJdbc0;

create table if not exists student0(
    s_id int(11) not null auto_increment comment '学号',
    s_name varchar(11) not null  comment '姓名',
    s_age int(3) not null  comment '年龄',
    s_sex int(1) not null  comment '性别',
    primary key (s_id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 comment '学生表';

create table if not exists student1(
    s_id int(11) not null auto_increment comment '学号',
    s_name varchar(11) not null  comment '姓名',
    s_age int(3) not null  comment '年龄',
    s_sex int(1) not null  comment '性别',
    primary key (s_id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 comment '学生表';

use shardingJdbc1;

create table if not exists student0(
    s_id int(11) not null auto_increment comment '学号',
    s_name varchar(11) not null  comment '姓名',
    s_age int(3) not null  comment '年龄',
    s_sex int(1) not null  comment '性别',
    primary key (s_id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 comment '学生表';

create table if not exists student1(
    s_id int(11) not null auto_increment comment '学号',
    s_name varchar(11) not null  comment '姓名',
    s_age int(3) not null  comment '年龄',
    s_sex int(1) not null  comment '性别',
    primary key (s_id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 comment '学生表';

yml配置文件

server:
  port: 8085

spring:
  main:
    allow-bean-definition-overriding: true

  shardingsphere:
    props:
      sql:
        show: true
#

    datasource:
      # 给每个数据源取名,名称任意
      names: ds0,ds1

      # 配置每个数据源的连接信息
      ds0:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://ip:port/shardingJdbc0?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT
        username: root
        password: 123456
        maxPoolSize: 100
        minPoolSize: 5
      ds1:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://ip:port/shardingJdbc1?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT
        username: root
        password: 123456
        maxPoolSize: 100
        minPoolSize: 5

    sharding:
      default-data-source-name: ds0
      # 分库分表配置
      tables:
        # 逻辑表
        student:
          # 数据节点:数据源$->{0..N}.逻辑表名$->{0..N}
          actual-data-nodes: ds$->{0..1}.student$->{0..1}
          # 拆分库策略,也就是什么样子的数据放入放到哪个数据库中。
          database-strategy:
            inline:
              # 根据性别判断存放在那个库(性别只有0和1,   0%2=0   1$2=1   就会选择数据源 ds0 或者 ds1)
              shardingColumn: s_sex    # 分片字段(分片键)
              algorithm-expression: ds$->{s_sex % 2} # 分片算法表达式
          # 拆分表策略,也就是什么样子的数据放入放到哪个数据表中。
          table-strategy:
            inline:
              # 同理   奇数年龄存入1表  或者存入0表
              shardingColumn: s_age    # 分片字段(分片键)
              algorithm-expression: student$->{s_age % 2} # 分片算法表达式


# 整合mybatis-plus的配置
mybatis-plus:
  mapper-locations: classpath:mapper/*Mapper.xml
  type-aliases-package: com.qc.entity
  configuration:
    map-underscore-to-camel-case: true
#    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl


配置类说明

tables: 表示分库分表的配置

逻辑表: 水平拆分的数据库或者数据表的相同路基和数据结构表的总称

逻辑表本身并不存在,比如有一张学生表,因为要分表,所以在数据库实实在在的创建了student0student1 两张表用来存储学生的信息,但是,我们不知道具体要存放在那张表中,但是两张表存放的都是学生信息,所以在逻辑上假装只有一张表,就是逻辑表,包括在增删改的sql中也写逻辑表表名

物理表配置规则: groovy脚本

分库分表配置含义说明

actual-data-nodes: ds$->{0…1}.student$->{0…1}

表示有数据源 ds0 ds1 下面的表有 student0 student1

测试类

package com.qc.mapper;

import com.qc.entity.Student;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;

import static org.junit.jupiter.api.Assertions.*;

/**
 * @author c
 * @date 2022-04-27 18:31
 */
@SpringBootTest
class StudentMapperTest {

    @Resource
    private StudentMapper studentMapper;

    @Test
    void addStudent() {
        Student student = new Student();
        student.setSName("小蓝");
        student.setSAge(129);
        student.setSSex(1);
        studentMapper.addStudent(student);
    }
}

只分表不分库配置

      tables:
        # 逻辑表
        student:
          # 数据源写死
          actual-data-nodes: ds0.student$->{0..1}
          # 拆分表策略,也就是什么样子的数据放入放到哪个数据表中。
          table-strategy:
            inline:
              # 同理   奇数年龄存入1表  或者存入0表
              shardingColumn: s_age    # 分片字段(分片键)
              algorithm-expression: student$->{s_age % 2} # 分片算法表达式

只分库不分表

      # 分库分表配置
      tables:
        # 逻辑表
        student:
          # 数据节点:数据源$->{0..N}.表名写死
          actual-data-nodes: ds$->{0..1}.student0
          # 拆分库策略,也就是什么样子的数据放入放到哪个数据库中。
          database-strategy:
            inline:
              # 根据性别判断存放在那个库(性别只有0和1,   0%2=0   1$2=1   就会选择数据源 ds0 或者 ds1)
              sharding-column: s_sex    # 分片字段(分片键)
              algorithm-expression: ds$->{s_sex % 2} # 分片算法表达式

分片算法: 不管是库还是表,有几个源或者有几个表就 分片键 % 源的数量(表的数量)

分片算法

分片细分

第一种:none

对应NoneShardingStragey,不分片策略,SQL会被发给所有节点去执行,这个规则没有子项目可以配置。

第二种: inline 行表达时分片策略(核心,必须要掌握)

上面示例这种

第三种: 标准分片 - Standard

根据实时间日期 - 按照标准规则分库分表

配置文件

server:
  port: 8085

spring:
  main:
    allow-bean-definition-overriding: true

  shardingsphere:
    props:
      sql:
        show: true

    datasource:
      # 给每个数据源取名,名称任意
      names: ds0,ds1

      # 配置每个数据源的连接信息
 ds0:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://ip:port/shardingJdbc0?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT
        username: root
        password: 123456
        maxPoolSize: 100
        minPoolSize: 5
      ds1:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://ip:port/shardingJdbc1?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT
        username: root
        password: 123456
        maxPoolSize: 100
        minPoolSize: 5


    sharding:
#      default-data-source-name: ds0
      # 分库分表配置
      tables:
        # 逻辑表
        student:
          # 数据节点:数据源$->{0..N}.逻辑表名$->{0..N}
          actual-data-nodes: ds$->{0..1}.student$->{0..1}
          # 拆分库策略,也就是什么样子的数据放入放到哪个数据库中。
          database-strategy:
            # standard  标准分片  ----> 根据日期
            standard:
              # 设置数据库的字段  比如你在数据库的字段为 birthday(生日)
              shardingColumn: birthday
              # 自己创建的类   实现了PreciseShardingAlgorithm接口
              preciseAlgorithmClassName: com.qc.config.BirthdayAlgorithm

              # 拆分表策略,也就是什么样子的数据放入放到哪个数据表中。
              table-strategy:
                inline:
                  # 同理   奇数年龄存入1表  或者存入0表
                  shardingColumn: s_age    # 分片字段(分片键)
                  algorithm-expression: student$->{s_age % 2} # 分片算法表达式







# 整合mybatis-plus的配置
mybatis-plus:
  mapper-locations: classpath:mapper/*Mapper.xml
  type-aliases-package: com.qc.entity
  configuration:
    map-underscore-to-camel-case: true


匹配规则实现

package com.qc.config;

import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;

import java.util.*;

/**
 * @author c
 * @date 2022-06-17 15:44
 *
 * 标准分片实现
 */
public class BirthdayAlgorithm implements PreciseShardingAlgorithm<Date> {

    List<Date> dateList = new ArrayList<>();
    {
        //数据源有少个  匹配规则就应该有多少  不然匹配不到  会报异常
        Calendar calendar1 = Calendar.getInstance();
        calendar1.set(2021, 1, 1, 0, 0, 0);
        Calendar calendar2 = Calendar.getInstance();
        calendar2.set(2022, 1, 1, 0, 0, 0);
        dateList.add(calendar1.getTime());
        dateList.add(calendar2.getTime());
    }

    /**
     *
     * @param collection  所有的数据源 ds0 ds1
     * @param preciseShardingValue 数据库真实的日期值(数据库birthday字段具体值)
     * @return ds[0-1]
     */
    @Override
    public String doSharding(Collection<String> collection, PreciseShardingValue<Date> preciseShardingValue) {

        // 获取属性数据库真实的值
        Date date = preciseShardingValue.getValue();
        // 获取数据源的名称信息列表
        Iterator<String> iterator = collection.iterator();
        String target = null;
        for (Date s : dateList) {
            target = iterator.next();
            //如dateList里面定义的三个日期对比   如果数据晚于指定的日期直接返回 2023
            if (date.before(s)) {
                break;
            }
        }

        //最后返回的是  ds0  ds1等数据源
        return target;
    }
}

官方数据分片说明

dataSources: #数据源配置,可配置多个data_source_name
  <data_source_name>: #<!!数据库连接池实现类> `!!`表示实例化该类
    driverClassName: #数据库驱动类名
    url: #数据库url连接
    username: #数据库用户名
    password: #数据库密码
    # ... 数据库连接池的其它属性

shardingRule:
  tables: #数据分片规则配置,可配置多个logic_table_name
    <logic_table_name>: #逻辑表名称
      actualDataNodes: #由数据源名 + 表名组成,以小数点分隔。多个表以逗号分隔,支持inline表达式。缺省表示使用已知数据源与逻辑表名称生成数据节点,用于广播表(即每个库中都需要一个同样的表用于关联查询,多为字典表)或只分库不分表且所有库的表结构完全一致的情况
        
      databaseStrategy: #分库策略,缺省表示使用默认分库策略,以下的分片策略只能选其一
        standard: #用于单分片键的标准分片场景
          shardingColumn: #分片列名称
          preciseAlgorithmClassName: #精确分片算法类名称,用于=和IN。。该类需实现PreciseShardingAlgorithm接口并提供无参数的构造器
          rangeAlgorithmClassName: #范围分片算法类名称,用于BETWEEN,可选。。该类需实现RangeShardingAlgorithm接口并提供无参数的构造器
        complex: #用于多分片键的复合分片场景
          shardingColumns: #分片列名称,多个列以逗号分隔
          algorithmClassName: #复合分片算法类名称。该类需实现ComplexKeysShardingAlgorithm接口并提供无参数的构造器
        inline: #行表达式分片策略
          shardingColumn: #分片列名称
          algorithmInlineExpression: #分片算法行表达式,需符合groovy语法
        hint: #Hint分片策略
          algorithmClassName: #Hint分片算法类名称。该类需实现HintShardingAlgorithm接口并提供无参数的构造器
        none: #不分片
      tableStrategy: #分表策略,同分库策略
      keyGenerator: 
        column: #自增列名称,缺省表示不使用自增主键生成器
        type: #自增列值生成器类型,缺省表示使用默认自增列值生成器。可使用用户自定义的列值生成器或选择内置类型:SNOWFLAKE/UUID
        props: #属性配置, 注意:使用SNOWFLAKE算法,需要配置worker.id与max.tolerate.time.difference.milliseconds属性。若使用此算法生成值作分片值,建议配置max.vibration.offset属性
          <property-name>: 属性名称
      
  bindingTables: #绑定表规则列表
  - <logic_table_name1, logic_table_name2, ...> 
  - <logic_table_name3, logic_table_name4, ...>
  - <logic_table_name_x, logic_table_name_y, ...>
  broadcastTables: #广播表规则列表
  - table_name1
  - table_name2
  - table_name_x
  
  defaultDataSourceName: #未配置分片规则的表将通过默认数据源定位  
  defaultDatabaseStrategy: #默认数据库分片策略,同分库策略
  defaultTableStrategy: #默认表分片策略,同分库策略
  defaultKeyGenerator: #默认的主键生成算法 如果没有设置,默认为SNOWFLAKE算法
    type: #默认自增列值生成器类型,缺省将使用org.apache.shardingsphere.core.keygen.generator.impl.SnowflakeKeyGenerator。可使用用户自定义的列值生成器或选择内置类型:SNOWFLAKE/UUID
    props:
      <property-name>: #自增列值生成器属性配置, 比如SNOWFLAKE算法的worker.id与max.tolerate.time.difference.milliseconds

  masterSlaveRules: #读写分离规则,详见读写分离部分
    <data_source_name>: #数据源名称,需要与真实数据源匹配,可配置多个data_source_name
      masterDataSourceName: #详见读写分离部分
      slaveDataSourceNames: #详见读写分离部分
      loadBalanceAlgorithmType: #详见读写分离部分
      props: #读写分离负载算法的属性配置
        <property-name>: #属性值
      
props: #属性配置
  sql.show: #是否开启SQL显示,默认值: false
  executor.size: #工作线程数量,默认值: CPU核数
  max.connections.size.per.query: # 每个查询可以打开的最大连接数量,默认为1
  check.table.metadata.enabled: #是否在启动时检查分表元数据一致性,默认值: false

分布式主键

之前的的表我们设置了主键自增,会发现分库分表之后每个表新增的id都是从1开始,然后几个表就会出现id一样的情况
所有需要用到分布式id

ShardingSphere 支持的有

  • SNOWFLAKE
  • UUID

在配置文件添加

    sharding:
#      default-data-source-name: ds0
      # 分库分表配置
      tables:
        # 逻辑表
        student:
          #配置分布式主键  雪花id
          key-generator:
            # 主键的列名
            column: s_id
            type: SNOWFLAKE
Zookeeper集群配置 2022-02-25
Gateway+Sentinel 2022-06-08

评论区