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

文章目录

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

本文中,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: 6666main:allow-bean-definition-overriding: trueshardingsphere:datasource:
#      ds:
#        maxPoolSize: 100# master-ds1数据库连接信息ds1:driver-class-name: com.mysql.cj.jdbc.DrivermaxPoolSize: 100minPoolSize: 5password: roottype: com.alibaba.druid.pool.DruidDataSourceurl: jdbc:mysql://localhost:3306/sharding_jdbc_master?useUnicode=true&useSSL=false&serverTimezone=Asia/Shanghaiusername: root# slave-ds2数据库连接信息ds2:driver-class-name: com.mysql.cj.jdbc.DrivermaxPoolSize: 100minPoolSize: 5password: roottype: com.alibaba.druid.pool.DruidDataSourceurl: jdbc:mysql://localhost:3306/sharding_jdbc_slave?useUnicode=true&useSSL=false&serverTimezone=Asia/Shanghaiusername: 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
## 显示sqlprops:sql:show: truesharding:
#      # 配置默认数据源ds1 默认数据源,主要用于写
#      default-data-source-name: ds1# 表策略配置tables:# 逻辑表s_user:keyGenerator:column: user_idtype: 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 {@Autowiredprivate UserMapper userMapper;@PostMappingpublic 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*/@PostConstructpublic 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/>*/
@Configurationpublic class MybatisPlusConfig {/*** 实现分页功能需要该配置*/@Beanpublic PaginationInterceptor paginationInterceptor() {PaginationInterceptor page = new PaginationInterceptor();page.setDbType(MYSQL);return page;}/*** 乐观锁插件 (需要在乐观锁字段配合@Version注解)** @return*/@Beanpublic OptimisticLockerInterceptor optimisticLockerInterceptor() {return new OptimisticLockerInterceptor();}/*** 枚举处理配置* @return*/@Beanpublic 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.rhkb.cn/news/191391.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

智能巡检软件哪个好?中小企业如何提升工作效率与质量?

在当今数字化、智能化的时代&#xff0c;智能巡检软件作为一种高效的工具&#xff0c;已经在各行各业得到了广泛的应用。它利用物联网、大数据、人工智能等技术&#xff0c;为巡检工作提供了全面的解决方案&#xff0c;帮助企业实现数据化、智能化管理&#xff0c;提高工作效率…

copilot 产生 python工具函数并生成单元测试

stock.py 这个文件&#xff0c;我只写了注释&#xff08;的开头&#xff09;&#xff0c;大部分注释内容和函数都是copilot # split a string and extract the environment variable from it # input can be , pathabc, pathabc;pathdef, pathabc;pathdef;pathghi # output i…

Leetcode-104 二叉树的最大深度

递归实现 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val val; }* TreeNode(int val, TreeNode left, TreeNode right) {* …

【深度学习】pytorch——常用工具模块

笔记为自我总结整理的学习笔记&#xff0c;若有错误欢迎指出哟~ 深度学习专栏链接&#xff1a; http://t.csdnimg.cn/dscW7 pytorch——常用工具模块 数据处理 torch.utils.data模块DatasetDataLoadersamplertorch.utils.data的使用 计算机视觉工具包 torchvisiontorchvision.d…

浙大恩特客户资源管理系统 fileupload.jsp 任意文件上传

声明 本文仅用于技术交流&#xff0c;请勿用于非法用途 由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;文章作者不为此承担任何责任。 一、产品简介 浙大恩特客户资源管理系统是一款针对企业客户资源管理的…

深度学习_12_softmax_图片识别优化版代码

因为图片识别很多代码都包装在d2l库里了&#xff0c;直接调用就行了 完整代码&#xff1a; import torch from torch import nn from d2l import torch as d2l"获取训练集&获取检测集" batch_size 256 train_iter, test_iter d2l.load_data_fashion_mnist(ba…

arcgis--浮点型栅格数据转整型

利用【Spatial Analyst工具】-【数学】-【转为整型】工具&#xff0c;将浮点型数据转为整型。如下&#xff1a; 【转为整型】对话框参数设计如下&#xff1a; 转换结果如下&#xff1a;

软件工程——名词解释

适用多种类型的软件工程教材&#xff0c;有关名词释义的总结较为齐全~ 目录 1. 软件 2. 软件危机 3. 软件工程 4. 软件生存周期 5. 软件复用 6. 质量 7. 质量策划 8. 质量改进 9. 质量控制 10. 质量保证 11. 软件质量 12. 正式技术复审 13. ISO 14. ISO9000 15.…

正交矩阵的定义

对于n阶矩阵A&#xff0c;如果&#xff0c;其中为单位矩阵&#xff0c;为A的转置矩阵&#xff0c;那么就称A为正交矩阵。 对于正交矩阵&#xff0c; 对于正交矩阵&#xff0c;其列向量都是单位向量&#xff0c;行向量都是单位向量

立体库堆垛机控制程序手动功能实现

手动操作功能模块 手动前后保护锁 *************提升手动程序段 手动上升&#xff0c;下降保护锁 **********货叉手动程序段

【技术干货】开源库 Com.Gitusme.Net.Extensiones.Core 的使用(二)

Com.Gitusme.Net.Extensiones.Core 扩展库 1.0.6 版本已发布。 1、版本变更说明 新增Sokcet套接字扩展。简化Socket操作&#xff0c;支持自定义命令封装&#xff0c;使用方便快捷&#xff0c;让您聚焦业务实现&#xff0c;而不必关心底层逻辑&#xff0c;提高开发效率。日志功…

UML软件建模软件StarUML mac中文版软件介绍

StarUML for mac是一款UML建模器&#xff0c;StarUML for mac提供了几个模版&#xff0c;帮助用户建立使用新的图表&#xff0c;是目前最流行的UML建模工具&#xff0c;给开发工作带来大大的便利。 StarUML mac软件介绍 StarUML 是一个流行的软件建模工具&#xff0c;用于创建…

(四)七种元启发算法(DBO、LO、SWO、COA、LSO、KOA、GRO)求解无人机路径规划MATLAB

一、七种算法&#xff08;DBO、LO、SWO、COA、LSO、KOA、GRO&#xff09;简介 1、蜣螂优化算法DBO 蜣螂优化算法&#xff08;Dung beetle optimizer&#xff0c;DBO&#xff09;由Jiankai Xue和Bo Shen于2022年提出&#xff0c;该算法主要受蜣螂的滚球、跳舞、觅食、偷窃和繁殖…

优雅的Java编程:将接口对象作为方法参数

theme: smartblue 目录 概述 在Java编程中&#xff0c;方法的参数传递方式通常是通过基本类型、对象引用或者集合等方式。然而&#xff0c;一种更加优雅且灵活的设计模式是将接口对象作为方法的参数。这种方式为我们带来了许多好处&#xff0c;包括降低耦合性、实现多态性和可…

虚拟局域网

虚拟局域网(VLAN) VLAN建立于交换技术的基础之上 广播域(broadcast domain)&#xff1a;其中任何一台设备发出的广播通信都能被该部分网络中的所有其他设备所接收&#xff0c;这部分网络就叫广播域利用以太网交换机可以很方便地实现虚拟局域网VLAN(Virtual LAN)对于一个主机和…

andorid 日历选择器

先看效果图&#xff1a; 主要代码 package com.example.flyimport android.annotation.SuppressLint import android.content.Context import android.graphics.Color import android.util.AttributeSet import android.view.LayoutInflater import android.view.View import…

JavaWeb Day10 案例 准备工作

目录 一、需求说明 二、环境搭建 &#xff08;一&#xff09;数据库 &#xff08;二&#xff09;后端 ①controller层 1.DeptController.java 2.EmpController.java ②mapper层 1.DeptMapper.java 2.EmpMapper.java ③pojo层 1.Dept.java 2.Emp.java 3.Result.ja…

邻里注意Transformer(CVPR2023)

Neighborhood Attention Transformer 摘要1、介绍2、相关工作2.1 新的卷积基线 3、方法3.1 邻居注意力3.2 Tiled NA and NATTEN3.3 邻居注意力Transformer 4、结论 代码 摘要 我们提出邻居注意力(NA)&#xff0c;第一个有效和可伸缩的滑动窗口的视觉注意机制。 NA是一种像素级…

Django视图函数和资源

文章目录 1.视图1.1 文件or文件夹1.2 相对和绝对导入urls1.3 视图参数1.4 返回值1.5 响应头1.6 FBV和CBV 2.静态资源2.1 静态文件2.2 媒体文件 1.视图 1.1 文件or文件夹 1.2 相对和绝对导入urls 注意实现&#xff1a;不要再项目根目录做相对导入。 原则&#xff1a; 绝对导入…

通过docker-compose部署elk日志系统,并使用springboot整合

ELK是一种强大的分布式日志管理解决方案&#xff0c;它由三个核心组件组成&#xff1a; Elasticsearch&#xff1a;作为分布式搜索和分析引擎&#xff0c;Elasticsearch能够快速地存储、搜索和分析大量的日志数据&#xff0c;帮助用户轻松地找到所需的信息。 Logstash&#xf…