Java如何实现分库分表

news2024/11/19 10:42:54

一、为啥要分库分表

在大型互联网系统中,大部分都会选择mysql作为业务数据存储。一般来说,mysql单表行数超过500万行或者单表容量超过2GB,查询效率就会随着数据量的增长而下降。这个时候,就需要对表进行拆分。

那么应该怎么拆分呢?

通常有两种拆分方法,垂直拆分和水平拆分。

先说垂直拆分,这个比较简单,我们可以把原先的一张表根据业务属性拆分成多张表。比如用户表user有很多字段,我们可以新建一张用户属性表user_profile,把一些不常用的字段都拆分到user_profile表里,再用user_id作为外键将两张表关联起来就可以了。

再说水平拆分,水平拆分针对的不是表,而是数据。比如订单表,数据量一般都会非常大。我们可以创建多个数据库实例,每个实例上创建多张订单表,把订单数据相对均匀的分散存储到这些表里。查询的时候,根据分表策略可直接定位到数据在哪个表里,可以大大提高查询效率。

下面讲到的都是如何水平拆分。

二、怎么做分库分表

分库分表已经有一些成熟的解决方案,本文是用ShardingSphere-JDBC框架来实现的。

1.什么是ShardingSphere-JDBC

ShardingSphere-JDBC定义为轻量级Java框架,在 Java 的 JDBC 层提供的额外服务。 它使用客户端直连数据库,以 jar 包形式提供服务,无需额外部署和依赖,可理解为增强版的 JDBC 驱动,完全兼容 JDBC 和各种 ORM 框架。

  • 适用于任何基于 JDBC 的 ORM 框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template 或直接使用 JDBC;
  • 支持任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, HikariCP 等;
  • 支持任意实现 JDBC 规范的数据库,目前支持 MySQL,PostgreSQL,Oracle,SQLServer 以及任何可使用 JDBC 访问的数据库。

更多详细内容可直接参考:ShardingSphere官方文档

2.ShardingSphere-JDBC分表实践

ShardingSphere-JDBC分库和分表配置类似,下面介绍下分表怎么实现。

(1)先建分表

先在mysql数据库建10张用户表:tb_user_0到9,建表语句如下,改下表名,执行10遍即可:

CREATE TABLE `tb_user_0`  (
  `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '姓名',
  `sex` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '性别',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

(2)POM依赖

使用spring boot + mybatis-plus + shardingsphere-jdbc来实现,pom主要引入的包配置如下:

  <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.3.1</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
            <version>5.0.0</version>
        </dependency>

(3)实体类和Mapper代码

注意,实体类和Mapper只有一个就行,注意这里的tableName注解一定要和后面配置分表策略的逻辑名一致,不然无法匹配路由策略。

@TableName(value = "tb_user")
public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 主键,注意此处IdType必须是AUTO,不然框架就会自动生成id,分表时生成id的策略就不生效了
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    /**
     * 姓名
     */
    @TableField(value = "name")
    private String name;

    /**
     * 性别
     */
    @TableField(value = "sex")
    private String sex;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    @Override
    public String toString() {
        return "User{" +
            "id=" + id +
            ", name=" + name +
            ", sex=" + sex +
        "}";
    }
}
public interface UserMapper extends BaseMapper<User> {

}

(4)配置数据源和分表规则

我们引入的包是shardingsphere-jdbc-core-spring-boot-starter,直接在application.yml里配置数据源和分表规则就行。

spring:
  shardingsphere:
    datasource:
      # 数据源名称,有几个数据源就写几个,如果是分表,就会写多个
      names: db0
      # 为每个数据源单独配置,注意这里要跟上面写的名称一致
      db0:
        # 数据库连接池实现类型,这里使用的是Hikari
        type: com.zaxxer.hikari.HikariDataSource
        # 数据库驱动类,连接地址,用户名,密码等
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://localhost:3306/sharding?useUnicode=true&characterEncoding=utf8&useSSL=false
        username: root
        password: 123456
    rules:
      sharding:
        tables:
          # 分表的表名,程序中对这张表的操作,都会采用下面的路由方案
          tb_user:
            # 这里是实际的数据节点信息,要把库名和表名都写全,这里也支持使用表达式,比如下面这张$->{0..9}
            actual-data-nodes: db0.tb_user_$->{0..9}
            # 配置分表策略
            table-strategy:
              # 这里选择的标准策略,也可以配置复杂策略,或者也可以用代码来实现
              standard:
                # 分片字段,这里是用用户id作为分片字段
                sharding-column: id
                # 这里是我们自定义的分片算法名称,后面会有实现方案
                sharding-algorithm-name: user-inline
            # 主键生成策略
            key-generate-strategy:
              # 生成主键算法的名称
              key-generator-name: snowflake
              # 主键字段
              column: id
        # 自定义的主键算法
        key-generators:
          snowflake:
            # 使用雪花算法生成主键
            type: SNOWFLAKE
        # 自定义的分表算法
        sharding-algorithms:
          user-inline:
            #使用inline类型实现
            type: inline
            props:
              #分片表达式,用id对10取模,然后分散到10个表中
              algorithm-expression: tb_user_$->{id % 10}
    props:
      # 打印日志,方便我们观察执行的sql语句
      sql-show: true

(5)写单测

先测试插入语句,如下插入100条数据:

@Autowired
    private UserMapper userMapper;

    @Test
    public void insertTest() {
        for (int i=0; i<100; i++) {
            User user = new User();
            user.setName("test" + i);
            user.setSex("男");
            userMapper.insert(user);
        }
    }

执行之后,发现每张表都有数据插入,但是分布并不均匀,这是由雪花算法特性导致的。下图是tb_user_0表的数据:

再测试下查询语句,先测试用id查询:

@Test
    public void selectByIdTest() {
        userMapper.selectById(1668501944537858050L);
    }

查询sql语句如下图,从图中可以看出,根据id查询的时候,会自动走分表路由策略,查询id为1668501944537858050L的数据,会自动去tb_user_table_0中查找。

再测试一下根据name字段查询:

    @Test
    public void selectByNameTest() {
        QueryWrapper<User> qy = new QueryWrapper<>();
        qy.eq("name","test1");
        userMapper.selectList(qy);
    }

查询sql语句如下图,从图中可以看出,如果不是根据分表字段来查询的话,会自动union所有分表查询,这样反而效率会更低。

所以,分库分表时一定要选择合适的字段,并且查询的时候尽量要在查询条件里先指定分库分表的字段,这样可以直接定位到表中,提高查询效率。

3.ShardingSphere-JDBC自定义分表策略类

ShardingSphere-JDBC可支持多种分片算法,比如标准分片,复合分片等,每种分片算法有多种类型,如行表达式INLINE,时间范围分片INTERVAL等,上面的例子我们就是用的标准分片行表达式做的。对于一些需要自定义的分片算法,我们可以通过自定义分片算法类来实现。

比如我们还是要实现取模算法,可以自定义一个UserShardingAlgorithm类来实现StandardShardingAlgorithm接口,实现doSharding接口来自定义分片算法,代码如下:

//分片字段数据类型是什么,这里泛型就写什么
public class UserShardingAlgorithm implements StandardShardingAlgorithm<Long> {

    //精确分片算法实现,collection是实际表,也就是配置文件里的actual-data-nodes内容
    //preciseShardingValue对象包括逻辑表名,分表算法的字段和字段值
    @Override
    public String doSharding(Collection<String> collection, PreciseShardingValue<Long> preciseShardingValue) {
        //对分片字段也就是用户id取模
        String suffix = String.valueOf(preciseShardingValue.getValue() % 10);
        //遍历表名,找到符合要求的表,返回即可
        for (String tableName : collection) {
            if (tableName.endsWith(suffix)) {
                return tableName;
            }
        }
        throw new UnsupportedOperationException();
    }

    //范围分片,我们暂不支持
    @Override
    public Collection<String> doSharding(Collection<String> collection, RangeShardingValue<Long> rangeShardingValue) {
        throw new UnsupportedOperationException();
    }
    
    //初始化信息接口
    @Override
    public void init() {

    }

    //分片算法类型
    @Override
    public String getType() {
        return "USER_SHARDING";
    }
}

在配置文件里,我们只需要改一下分片算法部分的配置即可,之前的配置是这样的:

        sharding-algorithms:
          user-inline:
            type: inline
            props:
              algorithm-expression: tb_user_$->{id % 10}

分片类型改成class_based,也就是自定义类分片算法,配置如下:

        sharding-algorithms:
          user-inline:
            type: class_based # 自定义类分片算法类型
            props:
              strategy: standard
              # 自定义算法类的路径
              algorithmClassName: com.github.learn.sharding.algorithm.UserShardingAlgorithm

还是再跑一下上面selectById单测,如下图,可以顺利去tb_user_0中查询数据,证明我们自定义的分片算法生效了:

4.主键生成策略

ShardingSphere-JDBC提供了两种内置的分布式主键生成器,uuid和雪花算法。

uuid:采用UUID.randomUUID()的方式产生分布式主键。

雪花算法:

雪花算法是由 Twitter 公布的分布式主键生成算法,它能够保证不同进程主键的不重复性,以及相同进程主键的有序性。

(1)实现原理

在同一个进程中,它首先是通过时间位保证不重复,如果时间相同则是通过序列位保证。 同时由于时间位是单调递增的,且各个服务器如果大体做了时间同步,那么生成的主键在分布式环境可以认为是总体有序的,这就保证了对索引字段的插入的高效性。 例如 MySQL 的 Innodb 存储引擎的主键。

使用雪花算法生成的主键,二进制表示形式包含 4 部分,从高位到低位分表为:1bit 符号位、41bit 时间戳位、10bit 工作进程位以及 12bit 序列号位。

  • 符号位(1bit)

预留的符号位,恒为零。

  • 时间戳位(41bit)

41 位的时间戳可以容纳的毫秒数是 2 的 41 次幂,一年所使用的毫秒数是:365 * 24 * 60 * 60 * 1000。 通过计算可知:

  1. Math.pow(2,41)/(365*24*60*60*1000L);

结果约等于 69.73 年。 Apache ShardingSphere 的雪花算法的时间纪元从 2016年11月1日 零点开始,可以使用到 2086 年,相信能满足绝大部分系统的要求。

  • 工作进程位(10bit)

该标志在 Java 进程内是唯一的,如果是分布式应用部署应保证每个工作进程的 id 是不同的。 该值默认为 0,可通过属性设置。

  • 序列号位(12bit)

该序列是用来在同一个毫秒内生成不同的 ID。如果在这个毫秒内生成的数量超过 4096 (2 的 12 次幂),那么生成器会等待到下个毫秒继续生成。

雪花算法主键的详细结构见下图。

(2)配置信息

在ShardingSphere-JDBC中,雪花算法提供了三个属性。

worker-id:工作机器唯一标识

max-vibration-offset:最大抖动上限值,范围[0, 4096)。注:若使用此算法生成值作分片值,建议配置此属性。此算法在不同毫秒内所生成的 key 取模 2^n (2^n一般为分库或分表数) 之后结果总为 0 或 1。为防止上述分片问题,建议将此属性值配置为 (2^n)-1。如果有10个分表,可将此值设置为9,这样数据分布会更均匀一下。

max-tolerate-time-difference-milliseconds:最大容忍时钟回退时间,单位:毫秒,默认10毫秒

(3)多节点worker-id配置

服务器可能是有多个节点的,此时如果worker-id用同一个配置,有可能会产生重复的id,因此每个节点的worker-id最好是不同的。我们可以用ip地址的一部分来作为节点的worker-id,worker-id是十位,我们直接取ip地址的后10位即可,一般都是不会重复的。比如机器的IP为192.168.1.108,二进制表示:11000000 10101000 00000001 01101100,截取最后10位 01 01101100,转为十进制364,设置workerId为364。

实现方式如下:

首先是配置文件,要加入work-id属性配置:

        key-generators:
          user-id-generator:
            type: SNOWFLAKE
            props:
              max-vibration-offset: 9
              worker-id: ${workerId}

然后,加一个配置类,在static代码块中获取ip地址,取后十位,作为worker-id。

@Configuration
public class WorkerIdConfig {

    private static final Logger LOGGER = LoggerFactory.getLogger(WorkerIdConfig.class);

    static {
        try {
            InetAddress address = InetAddress.getLocalHost();
            // IP地址byte[]数组形式,这个byte数组的长度是4,数组0~3下标对应的值分别是192,168,1,108
            byte[] ipAddressByteArray = address.getAddress();
            // workerId取ip地址后十位
            long workerId = ((ipAddressByteArray[ipAddressByteArray.length - 2] & 0x03) << 8) + (ipAddressByteArray[ipAddressByteArray.length - 1] & 0xFF);
            LOGGER.info("当前机器 workerId: {}", workerId);
            System.setProperty("workerId", String.valueOf(workerId));
        } catch (Exception e) {
            LOGGER.error("worker id failed:{}", e.getMessage(), e);
        }
    }
}

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

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

相关文章

vite项目中处理各种静态资源的引入方式介绍

一、引用图片资源 在vite创建的vue3项目中&#xff0c;引用图片资源有以下两种方式&#xff1a; 直接在模板中使用路径引用&#xff1a;在模板中使用标签&#xff0c;通过src属性引用图片。例如&#xff1a; <template><div><img src"./assets/logo.png…

NetApp FAS 存储管理软件,海量非结构化数据存储

NetApp FAS 存储管理软件&#xff0c;海量非结构化数据存储 在 NetApp ONTAP 数据管理软件的支持下&#xff0c;帮助您构建简单、安全且值得信赖的存储基础架构。NetApp FAS 存储阵列可让客户同时兼顾性能和容量。 NetApp FAS 系统经过优化&#xff0c;易于部署和操作&#x…

记录ip段解析成ip

无脑记录者记录使用方法 1.源代码链接 https://github.com/codeexpress/cidr2ip 2.提前准备的内容 go开发语言&#xff0c;链接里面的main.go 3.使用方法 直接新增文件cidrs.txt cidrs.txt文件里面加入需要解析的ip段即可

【实战】 JWT、用户认证与异步请求(上) —— React17+React Hook+TS4 最佳实践,仿 Jira 企业级项目(四)

文章目录 一、项目起航&#xff1a;项目初始化与配置二、React 与 Hook 应用&#xff1a;实现项目列表三、TS 应用&#xff1a;JS神助攻 - 强类型四、JWT、用户认证与异步请求1.login2.middleware of json-server3.jira-dev-tool&#xff08;imooc-jira-tool&#xff09;安装问…

机器学习——自然语言处理(一)

1 分词 1.1 设计原则 切分粒度大&#xff1b;非字典词少、单字字典词少&#xff1b;总体次数少。 1.2 基于词典匹配的分词 1.3 基于语法和规则的分词 目前处在试验阶段 1.4 基于统计的分词 1.5 技术难点 1.5.1 歧义识别 交集型歧义&#xff1a;AB | C or A | BC 组合型…

Jmeter操作数据库运行提示“Cannot load JDBC driver class ‘com.mysql.jdbc.Driver‘”的有效解决

如图所示&#xff0c;在jmeter中运行sql时报错提示“Cannot load JDBC driver class com.mysql.jdbc.Driver” 原因分析&#xff1a;这是因为没有mysql驱动&#xff0c;需要下载对应的jar包 一、下载地址&#xff1a;MySQL :: Download Connector/J 根据需求选择下载&#xf…

数字化转型:智慧物业行业落地与应用的突围之路!

导语 | 红杉中国在《2021 年企业数字化年度指南》中指出&#xff0c;96% 的受访企业已经开展了数字化实践&#xff0c;而其中超过 6 成的受访者都表示期望在未来进一步增加数字化的投入。技术因素或将成为未来两到三年影响企业发展最为重要的外部力量。当前地产与物业行业进入不…

当前最强的免费AI画图、AI绘图工具-2

Midjourney比较贵&#xff0c;而且无法访问&#xff0c;Stable Diffusion部署起来很麻烦。网上有哪些可以直接在网页端或者下载的app可以实现AI画图的工具。我们整理了45个相关工具&#xff0c;这是系列2&#xff0c;收录到 当前最强的免费AI画图、AI绘图工具-2https://www.web…

【C++】-- 高并发内存池

高并发内存池 项目介绍池化技术内存池 定长内存池的实现整体框架threadcachethreadcache整体设计threadcache哈希桶映射对齐规则TLS无锁访问 centralcachecentralcache整体设计centralcache结构设计centralcache的实现 pagecachepagecache整体设计pagecache中获取Span 回收内存…

【C/C++练习】经典的快慢指针问题---移除元素

&#x1f4d6;题目描述 题目出处&#xff1a;移除元素 &#x1f516;示例 &#x1f4d6;题解 对于本题我将按照由易到难的顺序为大家分享三种解题思路&#xff0c;并逐一分析它们的优劣&#xff0c;以及注意事项。 &#x1f516;思路一&#xff1a;暴力求解 我想暴力求解应该…

零-云尚办公项目学习

对于云尚办公项目的学习 1、这是尚硅谷推出的新的OA项目 云尚办公系统是一套自动办公系统&#xff0c;系统主要包含&#xff1a;管理端和员工端 管理端包含:权限管理、审批管理、公众号菜单管理 员工端:采用微信公众号操作&#xff0c;包含&#xff1a;办公审批、微信授权登…

数字通信中的编码(学习笔记)

编码种类 RZ(Return Zero Code)编码 也称为归零码&#xff0c;就是在 一个周期内&#xff0c;用二进制传输数据位&#xff0c;在数据脉冲结束后&#xff0c;需要维持一段时间的低电平。 RZ编码又分为两种&#xff1a; 单极性归零码 低电平表示0&#xff0c;正电平表示1&…

【Java用法】windows10系统下修改jar中的文件并重新打包成jar文件然后运行

windows10系统下修改jar中的文件并重新打包成jar文件然后运行 一、背景描述二、操作步骤2.1 解压jar包2.2 修改配置文件2.3 重新打成jar包2.4 确认是否修改成功2.5 运行程序 一、背景描述 测试环境&#xff08;Linux&#xff09;的代码&#xff08;jar包&#xff09;拉取到本地…

AI数字人:语音驱动面部模型及超分辨率重建Wav2Lip-HD

1 Wav2Lip-HD项目介绍 数字人打造中语音驱动人脸和超分辨率重建两种必备的模型&#xff0c;它们被用于实现数字人的语音和图像方面的功能。通过Wav2Lip-HD项目可以快速使用这两种模型&#xff0c;完成高清数字人形象的打造。 项目代码地址&#xff1a;github地址 1.1…

可再生能源与能源存储技术的结合和互补

在全球对可再生能源的需求日益增长的背景下&#xff0c;如何将可再生能源与能源存储技术相结合&#xff0c;实现能源的高效利用和持续供应成为了一个重要的议题。本文将探讨可再生能源与能源存储技术的结合与互补关系&#xff0c;分析其对能源领域的影响以及未来发展的前景。 …

CSS常用样式

文章目录 字体样式文本样式颜色和背景样式对齐方式下划线、上划线、删除线设置行高 列表样式背景样式背景颜色背景图片背景重复背景大小 鼠标样式伪类样式设置透明度 字体样式 所有样式都写在<style>标签内&#xff0c;里面加选择器 <!DOCTYPE html> <html>…

别小看可拖拽式表单设计器,降本增效就靠它啦!

在经济快速发展的当下&#xff0c;办公已然进入流程化发展阶段。不少企业希望实现降本增效的办公效果&#xff0c;大家不妨可以了解下可拖拽式表单设计器。通过简单的拖拉拽就能实现应用组建&#xff0c;创建属于自己的快速开发框架平台&#xff0c;不仅省下培养专业程序人工的…

安科瑞电化学储能电能管理系统解决方案

1.概述 在我国新型电力系统中&#xff0c;新能源装机容量逐年提高&#xff0c;但是新能源比如光伏发电、风力发电是不稳定的能源&#xff0c;所以要维持电网稳定&#xff0c;促进新能源发电的消纳&#xff0c;储能将成为至关重要的一环&#xff0c;是分布式光伏、风电等新能源…

抖音本地生活团购软件开发

抖音本地生活团购软件开发需要考虑以下几个方面&#xff1a; 功能设计&#xff1a;根据本地生活团购服务特点&#xff0c;设计相应的功能模块&#xff0c;如商家入驻、商品展示、订单管理、支付等。 技术选型&#xff1a;选择适合该项目的技术和框架&#xff0c;如移动…

【MySQL经典练习题】1. 多列数据求最大值

用 SQL 从多行数据里选出最大值或最小值很容易——通过 GROUP BY 子句对合适的列进行聚合操作&#xff0c;并使用 MAX 或 MIN 聚合函数就可以求出。 那么&#xff0c;从多列数据里选出最大值该怎么做呢&#xff1f; 目录 1、建表SQL 2、查询SQL &#xff08;1&…