【shardingjdbc】sharding-jdbc分库分表入门demo及原理分析

news2024/12/23 5:48:04

文章目录

    • 场景
    • 配置:
    • 概念及原理:
    • 代码:
    • 思考:

本文中,demo案例涉及场景为sharding jdbc的分库情况。
通俗点说就是由原来的db0_table水平拆分为 db1 t_table ,db2.t_table。

demo本身很简单,难点在于分片策略配置到底该怎么写,以及引发一些延伸的思考。代码是复制粘贴的事,思维是决定一个人上下限的事。

不同版本之间的分片配置写法可能有差异,虽然短短几行配置 博主也是花了点时间才配好,还是那句话 不声明版本的教程都是耍流氓,本文以springboot 2.5.x 和 sharding-jdbc 4.0.x 为例。

场景

我们模拟的场景:主从库都有一张表结构相同的 s_user表,当ID为偶数时数据存放放在master库 ,当ID为奇数时 数据存放在slave库

在这里插入图片描述

配置:

yml配置:配置解释在注释写的比较详细了 这里不再重复

(手动敲非常容易出错,直接复制下面模板是真的很爽)

spring:
  port: 6666
  main:
    allow-bean-definition-overriding: true
  shardingsphere:

    datasource:
#      ds:
#        maxPoolSize: 100
      # master-ds1数据库连接信息
      ds1:
        driver-class-name: com.mysql.cj.jdbc.Driver
        maxPoolSize: 100
        minPoolSize: 5
        password: root
        type: com.alibaba.druid.pool.DruidDataSource
        url: jdbc:mysql://localhost:3306/sharding_jdbc_master?useUnicode=true&useSSL=false&serverTimezone=Asia/Shanghai
        username: root
      # slave-ds2数据库连接信息
      ds2:
        driver-class-name: com.mysql.cj.jdbc.Driver
        maxPoolSize: 100
        minPoolSize: 5
        password: root
        type: com.alibaba.druid.pool.DruidDataSource
        url: jdbc:mysql://localhost:3306/sharding_jdbc_slave?useUnicode=true&useSSL=false&serverTimezone=Asia/Shanghai
        username: root

      # 配置数据源
      names: ds1,ds2

#    masterslave:
#      # 配置slave节点的负载均衡均衡策略,采用轮询机制
#      load-balance-algorithm-type: round_robin
#      # 配置主库master,负责数据的写入
#      master-data-source-name: ds1
#      # 配置主从名称
#      name: ms
#      # 配置从库slave节点
#      slave-data-source-names: ds2
#
    # 显示sql
    props:
      sql:
        show: true


    sharding:
#      # 配置默认数据源ds1 默认数据源,主要用于写
#      default-data-source-name: ds1

      # 表策略配置
      tables:
        # 逻辑表
        s_user:
          keyGenerator:
            column: user_id
            type: SNOWFLAKE

          # 分表节点 可以理解为分表后的那些表
          #由数据源名 + 表名组成,以小数点分隔。多个表以逗号分隔,
          #支持inline表达式。缺省表示使用已知数据源与逻辑表名称生成数据节点。
          #用于广播表(即每个库中都需要一个同样的表用于关联查询,多为字典表)或只分库不分表且所有库的表结构完全一致的情况
          # 1..2 表示从1到2  和我们上面定义的ds1 ds2对应
          actualDataNodes: ds$->{1..2}.s_user

      # 分库策略 注意和tables节点是同一层级
      default-database-strategy:
        inline:
           # 根据哪列分库
          sharding-column: user_id
          # 分库算法:取模 (+1 是因为 ds从1开始的  如果是ds0开始 则为 user_id % 2)
          algorithm-expression: ds$->{user_id % 2+1}
#logging:
#  level:
#    root: debug


概念及原理:

这里补充一些概念:

  1. 分片算法: 例如取模(%),范围(比如1-10W 10W-20W),日期(比如按月、日),哈希等,

  2. 虽然是对真实表(分表后的每张表)进行操作;但我们sql语句只需要操作逻辑表(未拆分前的表 其实已经不是真实存在的)

  3. sharding jdbc 会根据分片算法 拆分SQL
    例如拆分键为user_id 原语句为
    select * from user where id <200000
    分片算法按照范围(1-10W 10W-20W)时
    会拆分SQL:
    select * from user where id < 100000

    select * from user where id >=100000 and id < 200000
    并在内存中 经过了归并算法后组装结果

  4. sharding jdbc是驱动层 可以理解为 jdbc plus
    (对jdbc的拓展 重写了 connection,prestatement/statement,resultset (shardingConnection,shardingResultSet等类) )

  5. 与mycat 不同的是 ,mycat是代理层,比如sharding jdbc只适合Java环境 而代理层可以在不同语言使用, mycat需要额外部署,既然涉及到部署组件 就要考虑高可用问题(集群)

  6. 分页原理: 为了保证分页准确性 会将当前为止的所有数据都查出来 再进行归并组装。

说通俗点,中间件分库分表就是通过一些算法 将我们的sql进行拆分,查询或更改改不同库表的数据; 博主认为 分页查询时的归并结果 帮助我们解决了很多算法难点 自己写太容易出错了。

代码:

配置好了之后,我们的代码很简单:

package com.qiuhuanhen.shardingjdbcdemo.controller;

import cn.hutool.core.lang.UUID;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.qiuhuanhen.shardingjdbcdemo.entity.DO.UserDO;
import com.qiuhuanhen.shardingjdbcdemo.mapper.UserMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

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

@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {

    @Autowired
    private UserMapper userMapper;

    @PostMapping
    public void addUser() {

        for (int i = 0; i < 100; i++) {
            UserDO userDO = new UserDO();
			// userId在yml的sharidng配置下面指定了雪花算法
            userDO.setUserName("名字"+UUID.fastUUID().toString());
            userDO.setAddress("地址");
            userDO.setAge(new Random().nextInt());
            userDO.setArea("区域");
            userMapper.insert(userDO);
        }

    }

    /**
     * 分页查询: 多数据源会将数据分页范围内的数据全部查出 组合后再排序 确保分页正确性
     * (sharding jdbc做了归并处理 并不会将结果全存放在内存)
     *
     * 另外原生ResultSet里面有个fetchSize属性,是分批获取的意思(知识点补充 与本demo无关)
     * @return
     */
    @PostConstruct
    public List<UserDO> getUserList() {

        Page<UserDO> userDOPage = userMapper.selectPage(new Page<>(2, 2), new QueryWrapper<UserDO>().lambda().orderByAsc(UserDO::getAge));

        return userDOPage.getRecords();
    }
}


package com.qiuhuanhen.shardingjdbcdemo.entity.DO;

import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;

import java.io.Serializable;

@Data
@EqualsAndHashCode(callSuper = false)
@TableName("s_user")
public class UserDO implements Serializable {

    private static final long serialVersionUID=1L;

    private Long userId;

    private String userName;

    private Integer age;

    private String address;

    private String area;


}

package com.qiuhuanhen.shardingjdbcdemo.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.qiuhuanhen.shardingjdbcdemo.entity.DO.UserDO;

public interface UserMapper extends BaseMapper<UserDO> {


}

import com.baomidou.mybatisplus.autoconfigure.MybatisPlusPropertiesCustomizer;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.extension.handlers.MybatisEnumTypeHandler;
import com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import static com.baomidou.mybatisplus.annotation.DbType.MYSQL;

/**
 * ClassName: MybatisConfig <br/>
 * Description: 分页插件 <br/>
 */
@Configuration

public class MybatisPlusConfig {

    /**
     * 实现分页功能需要该配置
     */
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        PaginationInterceptor page = new PaginationInterceptor();
        page.setDbType(MYSQL);
        return page;
    }

    /**
     * 乐观锁插件 (需要在乐观锁字段配合@Version注解)
     *
     * @return
     */
    @Bean
    public OptimisticLockerInterceptor optimisticLockerInterceptor() {
        return new OptimisticLockerInterceptor();
    }


    /**
     * 枚举处理配置
     * @return
     */
    @Bean
    public MybatisPlusPropertiesCustomizer mybatisPlusPropertiesCustomizer() {
        return properties -> {
            GlobalConfig globalConfig = properties.getGlobalConfig();
            globalConfig.setBanner(false);
            MybatisConfiguration configuration = new MybatisConfiguration();
            configuration.setDefaultEnumTypeHandler(MybatisEnumTypeHandler.class);
            properties.setConfiguration(configuration);
        };
    }

}

pom文件:

<?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.5.12</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.qiuhuanhen</groupId>
	<artifactId>sharding-jdbc-demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>sharding-jdbc-demo</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>11</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
        </dependency>

		<!-- for spring boot -->
		<dependency>
			<groupId>org.apache.shardingsphere</groupId>
			<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
			<version>4.0.0-RC1</version>
		</dependency>

		<!-- spring-boot druid连接池 -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid-spring-boot-starter</artifactId>
			<version>1.2.4</version>
		</dependency>

		<!-- mysql driver -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>8.0.12</version>
		</dependency>

		<!--lombok-->
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<version>1.18.22</version>
		</dependency>

		<!-- jdk 9 以上 javax.xml.bind模块-->
		<dependency>
			<groupId>org.glassfish.jaxb</groupId>
			<artifactId>jaxb-runtime</artifactId>
			<version>2.3.5</version>
		</dependency>



		<!-- spring-boot mybatis-Plus -->
		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-boot-starter</artifactId>
			<version>3.3.1</version>
			<exclusions>
				<exclusion>
					<artifactId>tomcat-jdbc</artifactId>
					<groupId>org.apache.tomcat</groupId>
				</exclusion>
			</exclusions>
		</dependency>

		<dependency>
			<groupId>cn.hutool</groupId>
			<artifactId>hutool-all</artifactId>
			<version>5.8.12</version>
			<scope>compile</scope>
		</dependency>

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

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<configuration>
					<image>
						<builder>paketobuildpacks/builder-jammy-base:latest</builder>
					</image>
				</configuration>
			</plugin>
		</plugins>
	</build>

</project>

思考:

  1. 分库分表会有什么问题?

    我们会看到一些规范,比如数据量500W或者1000W 占用内存多少G以上,才开始考虑进行分库分表,也就意味着分库表肯定有缺陷,那么弊端有哪些呢?

    我们且先不谈读写分离分库分表 造成的数据一致性和延迟这么复杂的问题,仅拿我们demo来分析,首先分库一定会涉及到数据源切换的问题,这无疑也是一种开销,如果我们数据量本身不大 查询速度就很快,分库分表反而可能效率更低。此外,分库表之后 不能join , 这很考验我们的代码规范 冗余可以一定程度上解决这个问题。分布式事务也是另一大问题,一般情况下 我们可以用seata去解决,而在高并发下 可能要考虑性能问题。

    以上都是比较常见的问题,博主最疑惑的,其实是关于跨库分页查询问题


  1. sharding jdbc分库如何实现的 分页查询?

    其实也没有什么好的方法,为了保证分页结果正确性,必须将当前分页为止的总量全部查询出来

    (如果业务允许 我们自己也可以维护 同步一张未拆分的大表到其它效率高的非关系型数据库中进行分页查询 不走分库的分页查询 这和本文就跑远了)

    如原本的分页SQL为:
    在这里插入图片描述

    拆分后的SQL为:
    在这里插入图片描述

    为什么两个库是limit 0,4之后归并结果 而不是 limit 2,2 呢?因为在翻页时 结果可能不正确, 我们自己创建一些数据按大小排序查询 验证一下就很好理解了。
    比如表1有1,2,3,4 ,表2有1,3,7,8 ,我们业务需要的按从小到大排序取limit 2,2 , 我们预期想要的是 2和3 ,但是各自取limit 2,2 归并后的结果就是3,4了。

    那么问题来了,也就意味着当分页偏移大的时候 要去各自库表查询大量的数据,(这里也是对第一个问题的补充,如果数据量本来就不大 单表分页反而可能更快 ,分库表之后需要多次查询) 那这些大量的数据 取出来再拼接 不会导致jvm OOM吗? 是的 大家疑惑都是一致的 官方文档专门对这个进行了解释:
    在这里插入图片描述
    前文提到 博主认为归并做得很好的一点就是 它不会存储不需要的数据,经过一系列归并算法之后 只保留我们需要的数据,和我们将所有结果取出 再进行对比的简单粗暴算法不一样。


  1. resutlSet 的思考
    JDBC的resultSet 这是很基础又很久远的东西了,我们注意到sharding jdbc特地提到,其实resultSet是逐条返回的 , 不知道各位同学是否还记得 原始的jdbc 是通过resultSet.next去取值 一直遍历到next没值为止, resultSet本质是在维护一个游标。

    由此也会引申出一个问题,例如我们查询 select * from t ,有100W条结果,但是客户端(我们的java程序) 其实是逐条接收的,那么剩下未接收的数据在哪里呢?首先我们可以推断 数据库不可能是逐条从磁盘IO读取给我们 这样效率太慢了,未接收的数据推断是在缓存池里面。

    我们或多或少听过 近期查询最多的结果 或者完全一样的语句 会保存在缓存池里面,缓存池包括查询缓存和InnoDB缓冲池 ,其中查询缓存就是缓存相同的请求 实际业务中 泛用性不高,InnoDB缓冲池则使用较多 或许我们在八股文看过 InnoDB缓冲池淘汰机制是通过LRU算法。回到博主的推断,近期查询的结果会存放在缓存池,那我们也可以反推,近期查询最多的结果既然会保存在缓存池 那么说明缓存池是会(暂时)存放我们查询内容的 从而进行对比,所以未接收的数据 存放在缓存池的观点 也应该是成立的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1207443.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

宝马所有发动机号码的位置以及型号说明

每个汽车制造商都会为自己生产的汽车底盘和发动机分配一个内部代码&#xff0c;以标识研发和生产项目&#xff0c;而宝马对这些底盘代码和发动机代码更是规划的井井有条&#xff0c;比如发动机有M、N、B、S、P或W等代码标识&#xff0c;而底盘和车身则有E、F、G或i等代码标识。…

2FRE16-43/160LBK4M比例流量阀放大器

比例阀控制放大器技术是电液比例系统中的一个重要组成部分&#xff0c;它主要负责对比例阀进行控制。这种技术可以实现对液压流量或压力的精确控制&#xff0c;从而使系统以更高的精度和更快的响应速度执行各种操作。 比例阀控制放大器主要由三个部分组成&#xff1a;比例阀、…

58 权限提升-网站权限后台漏洞第三方获取

目录 当前知识点在渗透流程中的点当前知识点在权限提升的重点当前知识点权限提升权限介绍利用成功后的思想需要总结的思路演示案例:某挂壁程序后台权限提升-后台功能某BC广告导航页权限提升-漏洞层面苏丹大西瓜GlassFish中间件-第三方 涉及资源 这里包括网站权限、其它权限、系…

自己搭设开源密码管理工具 bitwarden

简介 Bitwarden是一款自由且开源的密码管理服务&#xff0c;用户可在加密的保管库中存储敏感信息&#xff08;例如网站登录凭据&#xff09;。Bitwarden平台提供有多种客户端应用程序&#xff0c;包括网页用户界面、桌面应用&#xff0c;浏览器扩展、移动应用以及命令行界面。[…

老卫带你学---go语言net/http原理解析

go语言net/http原理解析 对于golang&#xff0c;实现一个最简单的http server 非常简单&#xff0c;代码如下&#xff1a; package mainimport ("net/http""fmt" )func Indexhandler(w http.ResponseWriter,r *http.Request) {fmt.Fprintln(w,"hel…

【操作系统】内存的连续分配管理

文章目录 前言连续分配管理的方式单一连续分配固定分区分配动态分区分配如何记录内存的使用情况如何选择分区如何对分区进行回收 前言 当我们的进程在完成编译以后&#xff0c;需要装入内存&#xff0c;此时就有两种方式来进行内存的分配&#xff1a;连续分配、非连续分配。 …

CNKI上最新硕士博士论文pdf格式文件owner密码找回难度

听人说CNKI上比较早期的硕士博士论文pdf格式文件密码修改权限Owner密码是123456&#xff0c;想办法找了几个文件试了试果然如此。 但又听人说CNKI上最新硕士博士论文pdf格式文件owner密码已经不是了。虽然直接移除这种密码的工具到处都是&#xff0c;推测一下新增的owner密码及…

高并发场景下,如何设计订单库存架构,一共9个关键性问题

库存系统可以分为实物库存和虚拟库存两种类型。实物库存的管理涉及到采购、存储、销售和库存轮换等复杂的活动&#xff0c;需要进行供应链管理和仓库管理等工作。相比之下&#xff0c;虚拟库存的管理相对简单&#xff0c;主要适用于线上资源的数量管理&#xff0c;包括各类虚拟…

Python自动化测试之request库详解(一)

在做接口测试&#xff0c;接口自动化测试的时候都会用到很多工具&#xff0c;如postman、jmeter、pytest等工具&#xff0c;除了这些工具外&#xff0c;我们也会用python的第3方库requests来做接口测试。接下来我会专门讲request的系列专题&#xff0c;希望大家能学好request库…

Ansys Lumerical | 用于增强现实系统的表面浮雕光栅

在本示例中&#xff0c;我们使用 RCWA 求解器设计了一个斜面浮雕光栅 (SRG)&#xff0c;它将用于将光线耦合到单色增强现实 (AR) 系统的波导中。光栅的几何形状经过优化&#xff0c;可将正常入射光导入-1 光栅阶次。 然后我们将光栅特性导出为 Lumerical Sub-Wavelength Model …

八个开源免费单点登录(SSO)系统

使用SSO服务可以提高多系统使用的用户体验和安全性&#xff0c;用户不必记忆多个密码、不必多次登录浪费时间。下面推荐一些市场上最好的开源SSO系统&#xff0c;可作为商业SSO替代。 单点登录&#xff08;SSO&#xff09;是一个登录服务层&#xff0c;通过一次登录访问多个应…

通配符SSL证书

通配符SSL证书是一种特殊的数字证书&#xff0c;用于在互联网上建立安全的连接&#xff0c;其特点是可以保护多个子域名&#xff0c;并且具有很高的兼容性和扩展性。本文将详细介绍通配符SSL证书的相关概念、优点和应用等。 首先&#xff0c;我们需要了解什么是SSL证书。 SSL证…

听我的,日志还是得好好打!

大家好&#xff0c;我是老三&#xff0c;不知道大家有没有经历过这样的场景&#xff0c;线上出了问题&#xff0c;着急火燎地去排查&#xff1a; 唉&#xff0c;问题可能出在这个地方&#xff0c;捞个日志看下&#xff0c;卧槽&#xff0c;怎么找不到……哪个**不打日志&#…

打破语言壁垒,实现全球商贸:多语言多商户跨境商城源码引领电商新潮流

随着全球化的不断深入&#xff0c;电子商务的蓬勃发展&#xff0c;传统的单语言电商模式已经无法满足日益多元化的市场需求。多语言多商户跨境商城源码&#xff0c;一种创新的电商解决方案&#xff0c;应运而生。它打破了语言和地域的限制&#xff0c;让全球的商家和消费者都能…

c语言函数指针 指针函数

指针数组 数组指针 指针数组 数组指针 Int * br[3] {&a,&b,&c}; Int (*pl)[3] &arr; Int a1;int c 2; int c3; Int arr[3] {a,b,c}; Int* br[3] {&a,&b,&c}; Int* br[3] {&a,&b,&c}; //指针数组 Int (*p)[3] &arr…

新品 | 飞凌嵌入式FCU2601工商业储能EMS能量控制单元发布

FCU2601嵌入式控制单元是飞凌嵌入式为锂电池储能行业设计的EMS能量控制单元产品&#xff0c;设计兼具高性能&#xff0c;多接口&#xff0c;低功耗&#xff0c;广泛满足各类储能系统的本地能源管理应用需求。 FCU2601嵌入式控制单元综合考虑到了储能行业不同场景的差异化需求&…

1x1卷积核

1 1 1\times 1 11卷积核对输入数据的通道做约简。 每个 1 1 1\times 1 11卷积核相当于在输入数据的通道上做了一个降维&#xff08;经过一个神经元个数为1的全连接层&#xff09;&#xff0c;从而相当于大幅度降低了特征图的数量&#xff0c;但不影响特征图的结构。 使用 1 …

双算法SSL证书

国际算法的优势与挑战 1. RSA算法 RSA算法是一种基于大素数分解的非对称加密算法&#xff0c;长期以来一直是SSL证书的主流选择之一。然而&#xff0c;随着计算能力的提高&#xff0c;RSA算法的密钥长度需要不断增加&#xff0c;以维持足够的安全性。 2. ECC算法 椭圆曲线密…

Elasticsearch:检索增强生成 (Retrieval Augmented Generation -RAG)

作者&#xff1a;JOE MCELROY 什么是检索增强生成 (RAG) 以及该技术如何通过提供相关源知识作为上下文来帮助提高 LLMs 生成的响应的质量。 生成式人工智能最近取得了巨大的成功和令人兴奋的成果&#xff0c;其模型可以生成流畅的文本、逼真的图像&#xff0c;甚至视频。 就语…

HelloWorld -从Houdini导出HDA到UE5

1.配置插件 在Houdini安装目录下找到对应版本引擎的插件&#xff0c;例如这里是Houdini19对应UE5.2的版本&#xff0c;我们就要保证先下载好UE5.2&#xff1a; 将Houdini插件粘贴到UE安装目录的Plugins文件夹下&#xff1a; 目前插件配置完成&#xff0c;打开UE会自动启用插…