Sharding-JDBC4.0分库分表实战


一、分库分表的缘由

    随着数据量的增大,或者一些特殊业务需求,单表的性能已经不能满足系统的要求,虽然可以对代码,sql的查询做一些优化,或者做一些数据预热,热点数据捞到缓存中,但是不能从根本上解决单库单表的性能问题,这些都只能作为一些优化手段,此时就需要我们的分库分表,由原来的单库-单表演变为我们的多库-多表。

常见的拆表:

  • 垂直拆分   -将单表中不经常用到的字段,拆到一张附属表中,拆完后,俩张表结构不同
  • 水平拆分   -将单表存放的数据,横切分出一半放到另外一张表中,俩表结构相同

如图:
在这里插入图片描述
    这只是对于表的拆分,不管垂直拆分还是水平拆分,都是可以在单库内进行的,但是单库或单台机器所提供的性能毕竟是有限的,所以一般在分表之上,又会进行一个分库,多台机器,多个库,每个库中多张表,这样就是整个机器集群在提供数据查询服务,性能大大提高。

    最终我们整个db服务就演变成这样:
在这里插入图片描述

    说完我们的db集群,再来看我们上图中的分片键路由规则,当我们做了分库分表之后,对于每一条数据的读写操作,我们应该去哪个库哪张表操作呢?数据的插入好说,我们可以随便插入某个库某张表,但是查询、删除、修改呢?总不能每个库每张表遍历,遍历完才能知道对应的数据在哪个库哪张表吧,这样我们分库分表的意义还何在,所以就有了分片键路由规则

分片键:

    举个列子:我们将表分片以后,当执行一条SQL时,通过对字段 order_id 取模的方式来决定,这条数据该在哪个数据库中的哪个表中执行,此时 order_id 字段就是表的分片健,不管是增删改查中哪种操作,都需要携带order_id,我们再通过我们的路由规则计算这条数据应该插入哪个库的哪张表,查询也就不再需要我们遍历所有的数据库,直接定位这条数据在哪个库哪张表,直接去查询,效率大大提升。

路由规则:

    路由规则又可以称之为分片算法,是通过对分片键计算,来得到这条SQL语句应该到哪个库表执行的算法,从执行 SQL 的角度来看,分库分表可以看作是一种路由机制,把 SQL 语句路由到我们期望的数据库或数据表中并获取数据,分片算法可以理解成一种路由规则。

二、分库分表实现

技术选型:

    市面上有很多不错的分库分表的技术框架,当然,当我们对分库分表的实现原理已经熟悉的情况下,也可以手写一个,自己实现分片算法,这里选了俩个市面上常用的开源框架进行对比:mycatshardingJdbc
mycat:

是一个需要单独部署的第三方组件,对项目代码没有入侵
客户端所有的 jdbc 请求都必须要先交给 mycat ,再由 mycat 转发到具本的真实服务器中
由于jdbc请求都需要经过mycat,经过mycat内部进行分片路由计算再转到mysql,所以有额外的性能消耗
支持事务

shardingJdbc:

作为一个组件,需要在项目中集成,项目开发成本增加
在项目中解析sql,路由到具体的mysql库-表,相比mycat,性能较高
有语言限制,目前只支持Java语言
4.0支持事务

这里我说下我为什么选用shardingJdbc,相比于mycat,shardingJdbc目前只支持Java,同时需要在项目代码中集成,但是他的性能较高,同时在代码中集成,能让我们的开发人员更加清楚分库分表的一个底层实现原理,而且不需要额外部署组件,也不需要担心像mycat宕机的风险,而且shardingJdbc4.0也支持事务,和seata整合也比较简单。

准备工作

1、准备一个springboot工程
2、准备好俩个数据库,每个库中5张订单表

在这里插入图片描述
在这里插入图片描述
3、导入shardingJdbc相关依赖

		//mybatis-plus和druid等的依赖,这里就不贴出了,读者自己整合
        <!-- sharding-sphere -->
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
            //最好和此一样版本,4.1.1版本博主测试会报找不到数据源问题,需要重写配置文件
            <version>4.0.0-RC1</version>
        </dependency>

编写配置文件

先编写我们的配置文件,由于考虑到后续还要和nacos,seata整合。此处采用yml写法,后续从nacos上拉去配置文件:application.yml

server:
    port: 4010

spring:
    application:
        name: learn_shardingsphere
    main:
        allow-bean-definition-overriding: true
    shardingsphere:
        datasource:
            default:
                driver-class-name: com.mysql.cj.jdbc.Driver
                password: root
                type: com.alibaba.druid.pool.DruidDataSource
                url: jdbc:mysql://ip:3306/[默认库]?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
                username: root
            ds1:
                driver-class-name: com.mysql.cj.jdbc.Driver
                jdbc-url: jdbc:mysql://ip:3306/taobao1?serverTimezone=GMT%2B8&useSSL=false
                password: root
                type: com.zaxxer.hikari.HikariDataSource
                username: root
            ds2:
                driver-class-name: com.mysql.cj.jdbc.Driver
                jdbc-url: jdbc:mysql://ip:3306/taobao2?serverTimezone=GMT%2B8&useSSL=false
                password: root
                type: com.zaxxer.hikari.HikariDataSource
                username: root
            names: default,ds1,ds2
        #是否开启SQL显示,默认值: false
        props:
            sql:
                show: true
        sharding:
        	#默认数据源配置,当没有配置分片规则的表将使用此数据源
            default-data-source-name: default
            tables:
            	#逻辑表名
                order:
                	##由数据源名 + 表名组成,以小数点分隔
                    actual-data-nodes: ds1.order_$->{1..5},ds2.order_$->{6..9}
                    database-strategy:
                        standard:
                        	#库路由规则
                            precise-algorithm-class-name: org.example.config.PreciseDatabaseShardingAlgorithm
                            #分片键
                            sharding-column: id
                    table-strategy:
                        standard:
                        	#表路由规则
                            precise-algorithm-class-name: org.example.config.PreciseTableShardingAlgorithm
                            #分片键
                            sharding-column: id
logging:
    level:
        com:
            sharding:
                demo:
                    mapper: DEBUG
#mp配置                    
mybatis-plus:
    configuration:
        auto-mapping-behavior: full
        map-underscore-to-camel-case: true
    mapper-locations: classpath:/mapper/*Mapper.xml

库分片规则实现:
我们这里简单规定,取订单id的个位数,小于5则放在ds1库中,大于5放在ds2库中

public class PreciseDatabaseShardingAlgorithm implements PreciseShardingAlgorithm<Long> {

    @Override
    public String doSharding(Collection<String> collection, PreciseShardingValue<Long> id) {
        
        // 分片键值
        long value = id.getValue();

        // 库后缀
        String number;
        String str = String.valueOf(value).substring(String.valueOf(value).length() - 1);

        if (Integer.parseInt(str) <= 5) {
            number = "1";
        } else {
            number = "2";
        }

        if (value <= 0) {
            throw new UnsupportedOperationException("preciseShardingValue is null");
        }

        for (String availableTargetName : collection) {
            if (availableTargetName.endsWith(number)) {
                return availableTargetName;
            }
        }
        throw new UnsupportedOperationException();
    }
}

表分片规则实现:
取订单id个位数,拼接表:order{订单id个位数}

public class PreciseTableShardingAlgorithm implements PreciseShardingAlgorithm<Long> {

    @Override
    public String doSharding(Collection<String> collection, PreciseShardingValue<Long> id) {
        // 分片键值
        long value = id.getValue();

        if (value <= 0) {
            throw new UnsupportedOperationException("preciseShardingValue is null");
        }

        // 表后缀
        String str = String.valueOf(value).substring(String.valueOf(value).length() - 1);

        for (String availableTargetName : collection) {
            if (availableTargetName.endsWith(str)) {
                return availableTargetName;
            }
        }
        throw new UnsupportedOperationException();

    }
}

这里再贴出OrderMpper.xml的写法:
实体类,service实现,文章中就不具体贴出了
这里在xml中订单表都是取的逻辑表:order

    <insert id="insert" parameterType="org.example.entity.Order">
        insert into order(id,name,count,description) values(#{id},#{name},#{count},#{description})
    </insert>

    <update id="update" parameterType="org.example.entity.Order">
        update order set
        name=#{name},
        count=#{count},
        description=#{count}
        where id = #{id}
    </update>

    <delete id="delete">
        delete from order where id = #{id}
    </delete>

    <select id="select" resultType="org.example.entity.Order" parameterType="org.example.entity.Order">
        select * from order where id = #{id}
    </select>
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值