资讯详情

ShardingJDBC

ShardingJDBC

1. 概述

Apache ShardingSphere 是一个开源分布式数据库生态项目,旨在在最大限度地利用原始数据库的生存计算能力,进一步提供全球扩展和叠加计算能力。其核心采用数据库协议和可插拔架构 SQL 提供数据分片、访问路由、数据安全等多种增强功能的方法。

在这里插入图片描述

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

  • 功能列表
    • 数据分片
    • 分库 & 分表
    • 读写分离
    • 定制分片策略
    • 无集中分布式主键
  • 分布式事务
    • 标准化事务接口
    • XA 强一致事务
    • 柔性事务
    • 数据库治理
  • 分布式治理
    • 弹性伸缩
    • 可视化链路跟踪
    • 数据加密

SQL 解析

分为词法分析和语法分析。 先通过词法解析器将 SQL 分成不可分割的单词。然后使用语法分析器对其进行处理 SQL 理解并最终提取分析上下文。 分析上下文包括表、选择、排序、分组、聚合函数、分页信息、查询条件和可能需要修改的占位符标记。

查询优化

分片条件的合并和优化,如 OR 等。

SQL 路由

根据分析匹配用户配置的分片策略,生成路由路径。目前支持分片路由和广播路由。

SQL 改写

将 SQL 改写为可以在真实数据库中正确执行的句子。SQL 改写分为正确性改写和优化改写。

SQL 执行

多线程执行器异步执行。

结果归并

通过统一的方式,将多个执行结果集合并 JDBC 接口输出。结果合并包括流式合并、内存合并和装饰模式的附加合并。

2. MySQL安装

http://repo.mysql.com/yum/mysql-5.7-community/el/7/x86_64/

rpm -ivh http://repo.mysql.com/yum/mysql-5.7-community/el/7/x86_64/mysql57-community-release-el7-10.noarch.yum  rpm --import https://repo.mysql.com/RPM-GPG-KEY-mysql-2022 yum -y install mysql-community-server  systemctl start mysqld   # 启动MySQL systemctl status mysqlld # 检查启动状态 systemctl stop mysqld    # 停止MySQL systemctl enable mysqld  # 开机自启动   grep "password" /var/log/mysqld.log  # 使用此命令查看默认密码 [root@summer1245 ~]# mysql -uroot -pQ5oZnaK.Sdjj 登录数据库 # 默认密码规则必须携带大小写字母和特殊符号,字符长度大于8,否则会报错。 # 因此,在设置相对简单的密码时,首先需要修改set global validate_password_policy和_length参数值。 set global validate_password_policy=0; set global validate_password_length=1; set password for root@localhost = password('root');  # 到目前为止,可视化客户端还不能连接,需要我们授权: grant all on *.* to root@'%' identified by 数据库密码; flush privileges; # 现在可以用了Navicat连接数据库 记得阿里云修改 

3. 主从复制

主从复制,允许未来来自一个MySQL复制到一个或多个数据库服务器(主)MySQL(从)数据库服务器。

复制过程是异步的,可以配置复制数据库或某个表,而不需要永久连接主服务器的更新。

复制主从需要保证一个前提:主服务器必须打开二进制日志

大体流程:

  • 从服务器上开始 I/O thread,将用户名和密码连接到主服务器,请求读取二进制日志,然后读取二进制日志写到本地的一个Realy log(中继日志)里面。
  • 从服务器上面同时开启一个 SQL thread 定时检查 Realy log(这个文件也是二进制的),如果发现有更新立即把更新的内容在本机的数据库上面执行一遍。每个从服务器都会收到主服务器二进制日志的全部内容的副本。
  • 从服务器设备负责决定应该执行二进制日志中的哪些语句。 除非指定,否则主二进制日志中的所有事件都在从上执行,如果需要,可以将从配置为仅处理一些特定数据库或表的事件。
# 在主服务器修改以下配置
vim /etc/my.cnf

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

# 修改后重启
# 在从服务器修改以下配置
> vim /etc/my.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

在主服务器执行

# 授予slave服务器可以同步master服务
grant replication slave, replication client on *.* to 'root'@'slave服务的ip' identified by 'slave服务器的密码';
flush privileges;
# 查看MySQL现在有哪些用户及对应的IP权限(可以不执行,只是一个查看)
select user,host from mysql.user;

查询master服务的binlog文件名和位置

show master status;

slave进行关联master节点

change master to master_host='master服务器ip', master_user='root', master_password='master数据库密码', master_port=3306, master_log_file='mysql-bin.000001',master_log_pos=626;

slave查看主从同步状态

# 启动主从复制
start slave;
# 查看主从同步状态
show slave status\G;

# 停止复制
stop slave;

  1. Slave_IO_Running:Connecting
# 检查以下内容
# 网络不通
# 检查ip,端口
# 密码不对
# 检查是否创建用于同步的用户和用户密码是否正确
# pos不对
# Position不对
  1. Slave_SQL_Running: No

造成这类问题的原因一般是在主从复制的时候,基于创建表,然后又去删除和操作了数据表或者表。

# 解决方案1
# 先stop slave,然后执行了一下提示的语句,再
stop slave;
set global sql_slave_skip_counter=1;
start slave; 
show slave status\G;

# 解决方案2
# master节点执行,获取日志文件和position
show master status;
# slave节点重新绑定
stop slave;
CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.000001', MASTER_LOG_POS=626; 
start slave;

4. 代码具体实现

创建一个SpringBoot项目

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</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>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.0.0-RC1</version>
</dependency>
<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>sharding-core-common</artifactId>
    <version>4.0.0-RC1</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.22</version>
</dependency>

配置文件

server:
  port: 8085
spring:
  main:
    allow-bean-definition-overriding: true
  shardingsphere:
    # 参数配置,显示sql
    props:
      sql:
        show: true
    # 配置数据源
    datasource:
      # 给每个数据源取别名,下面的ds1,ds2,ds3任意取名字
      names: ds1,ds2,ds3
      # 给master-ds1每个数据源配置数据库连接信息
      ds1:
        # 配置druid数据源
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://ip:3306/test?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT%2b8
        username: root
        password: root
        maxPoolSize: 100
        minPoolSize: 5
      # 配置ds2-slave
      ds2:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://ip:3306/test?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT%2b8
        username: root
        password: root
        maxPoolSize: 100
        minPoolSize: 5
      # 配置ds3-slave
      ds3:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://ip:3306/test?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT%2b8
        username: root
        password: root
        maxPoolSize: 100
        minPoolSize: 5
    # 配置默认数据源ds1
    sharding:
     # 默认数据源,主要用于写,注意一定要配置读写分离 ,注意:如果不配置,那么就会把三个节点都当做从slave节点,新增,修改和删除会出错。
      default-data-source-name: ds1
    # 配置数据源的读写分离,但是数据库一定要做主从复制
    masterslave:
      # 配置主从名称,可以任意取名字
      name: ms
      # 配置主库master,负责数据的写入
      master-data-source-name: ds1
      # 配置从库slave节点
      slave-data-source-names: ds2,ds3
      # 配置slave节点的负载均衡均衡策略,采用轮询机制 随机的话是random
      load-balance-algorithm-type: round_robin
# 整合mybatis的配置XXXXX
mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.hua.pojo

如果不做上面的shardingjdbc配置,那么shardingjdbc会采用随机的方式进行选择数据源。如果不配置default-data-source-name,那么就会把三个节点都当做从slave节点,那么新增,修改和删除会出错。

@Repository
public interface UserMapper { 
        
    int addUser(User user);
    List<User> getUserList();
}

主函数记得扫描包

@SpringBootApplication
@MapperScan("com.hua.dao")
public class DemoApplication { 
        
    public static void main(String[] args) { 
        
        SpringApplication.run(DemoApplication.class, args);
    }
}

UserMapper.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.hua.dao.UserMapper">
    <insert id="addUser" parameterType="user">
        insert into user(id,name,age)
        values(#{id},#{name},#{age})
    </insert>
    <select id="getUserList" resultType="user">
        select * from user
    </select>
</mapper>

userService

public interface UserService { 
        

    int addUser(User user);

    List<User> getUserList();
}

UserServiceImpl

@Service
public class UserServiceImpl implements UserService{ 
        

    @Autowired
    UserMapper userMapper;
    @Override
    public int addUser(User user) { 
        
        return userMapper.addUser(user);
    }

    @Override
    public List<User> getUserList() { 
        
        return userMapper.getUserList();
    }
}

UserController

@RestController
public class UserController { 
        
    @Autowired
    UserService userService;

    @RequestMapping("/add")
    public String addUser(){ 
        
        User user = new User();
        user.setId(1);
        user.setName("小明");
        user.setAge(15);
        int i = userService.addUser(user);
        if (i>0){ 
        
            return "添加成功";
        }else{ 
        
            return "添加失败";
        }
    }

    @RequestMapping("/getUserlist")
    public List<User> getUserList(){ 
        
        return userService.getUserList();
    }
}
SQL: insert into user(name,age) values(?,?) ::: DataSources: ds1
# 执行插入的时候是在主节点
# 执行查询的时候是在从节点 两个从节点轮流读

数据库中需要有user表

读写分离建立在主从复制的基础上

5. 分库分表

  1. 高并发的情况下,会造成IO读写频繁,自然就造成读写缓慢,甚至宕机。一般单库不要超过2k并发。
  2. 数据量大的问题,由于MySQL的索引使用的B+Tree,数据量很大的话,会导致索引树特别大,造成查询缓慢。innodb的最大存储限制在64TB.

目的:解决高并发,数据量大的问题。将一个表拆分为N个表,让每个表的数据量控制在一定范围内,保证性能。一个表的数据量不要超过500W。

  • 水平拆分:统一将一个表的数据拆分到不同库不同表,可以根据时间、地区或某个业务维度。拆分后的表结构一致。
  • 垂直拆分:将有很多字段的表拆分为多个表或者多个库,拆分后的表结构不一致,可以根据业务维度进行拆分,如订单表可以拆分为订单、订单支持、订单地址等;也可以根据冷热程度拆分,20%的热点字段拆到一个表,80%冷门字段拆到另一个表。

总之就是垂直拆分就是按照业务来拆,水平拆分就是对表进行拆分,表的垂直拆分就是表的字段过多,字段使用的频率不一,此时可以拆为两个一对一关系的表

水平拆分数据库或者数据表的相同数据结构表的总称,例用户表拆分为两个user0和user1,逻辑表名就是user

spring:
  shardingsphere:
    sharding:
      tables:
        # user 逻辑表名
        user:

tables:
        # user 逻辑表名
        user:
          # 数据节点:多数据源$->{0..N}.逻辑表名$->{0..N} 相同表
          actual-data-nodes: ds$->{ 
        0..2}.user$->{ 
        0..1}
          # 数据节点:多数据源$->{0..N}.逻辑表名$->{0..N} 不同表
          actual-data-nodes: ds0.user$->{ 
        0..1},ds1.user$->{ 
        2..4}
          # 指定单数据源的配置方式
          actual-data-nodes: ds0.user$->{ 
        0..4}
          # 全部手动指定
          actual-data-nodes: ds0.user0,ds1.user0,ds0.user1,ds1.user1,

https://www.kuangstudy.com/zl/sharding#1369532584442216449

数据源分片:

  • 数据源分片(分片键、分片算法)
  • 表分片(分片键、分片算法)

  1. none

对应NoneShardingStragey 不分片策略 sql会发给所有的节点

  1. inline(重点)

对应InlineShardingStragey 使用Groovy表达式。提供对sql语句中的=和in的支持,只支持单分片键,例如user$(userid%5) 表示user表通过字段userid模5进行分表,分为user0–user4

server:
  port: 8085
spring:
  main:
    allow-bean-definition-overriding: true
  shardingsphere:
    props:
      sql:
        show: true
    datasource:
      names: ds0,ds1
      ds0:
      # 配置druid数据源
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://ip:3306/test?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT%2b8
        username: root
        password: root
        maxPoolSize: 100
        minPoolSize: 5
      # 配置ds2-slave
      ds1:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://ip:3306/test?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT%2b8
        username: root
        password: root
        maxPoolSize: 100
        minPoolSize: 5
    sharding:
      default-data-source-name: ds0
      tables:
        user:
          actual-data-nodes: ds$->{ 
        0..1}.user$->{ 
        0..1}
          database-strategy:
            inline:
              sharding-column: age    # 分片字段(分片键)
              algorithm-expression: ds$->{ 
        age%2} # 分片算法表达式
            # 拆分表策略,也就是什么样子的数据放入放到哪个数据表中。
          table-strategy:
            inline:
              sharding-column: age    # 分片字段(分片键)
              algorithm-expression: user$->{ 
        age%2} # 分片算法表达式



mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.hua.pojo

@Autowired
UserService userService;
@Test
void contextLoads() { 
        
    User user = new User();
    user.setName("小明");
    user.setAge(15);  // 15%2=1 所以此条数据就会放到 1库1表中
    userService.addUser(user);
}

server:
  port: 8085
spring:
  main:
    allow-bean-definition-overriding: true
  shardingsphere:
    # 参数配置,显示sql
    props:
      sql:
        show: true
    # 配置数据源
    datasource:
      # 给每个数据源取别名,下面的ds1,ds1任意取名字
      names: ds0,ds1
      # 给master-ds1每个数据源配置数据库连接信息
      ds0:
        # 配置druid数据源
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://ip:3306/ksd-sharding-db?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT
        username: root
        password: root
        maxPoolSize: 100
        minPoolSize: 5
      # 配置ds1-slave
      ds1:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://ip:3306/ksd-sharding-db?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT
        username: root
        password: root
        maxPoolSize: 100
        minPoolSize: 5
    # 配置默认数据源ds0
    sharding:
      default-data-source-name: ds0
      # 配置分表的规则
      tables:
        # user 逻辑表名
        user:
          # 数据节点:数据源$->{0..N}.逻辑表名$->{0..N}
          actual-data-nodes: ds$->{ 
        0..1}.user$->{ 
        0..1}
          # 拆分库策略,也就是什么样子的数据放入放到哪个数据库中。
          database-strategy:
            inline:
              sharding-column: sex    # 分片字段(分片键)
              algorithm-expression: ds$-
        标签: selet传感器b08系列

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台