千家信息网

SpringBoot怎么使用Sharding-JDBC实现数据分片和读写分离

发表于:2024-12-01 作者:千家信息网编辑
千家信息网最后更新 2024年12月01日,本篇内容介绍了"SpringBoot怎么使用Sharding-JDBC实现数据分片和读写分离"的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情
千家信息网最后更新 2024年12月01日SpringBoot怎么使用Sharding-JDBC实现数据分片和读写分离

本篇内容介绍了"SpringBoot怎么使用Sharding-JDBC实现数据分片和读写分离"的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

目录
  • 一、Sharding-JDBC简介

  • 二、具体的实现方式

    • 1、maven引用

    • 2、数据库准备

    • 3、Spring配置

    • 4、精准分片算法和范围分片算法的Java代码

    • 5、测试

一、Sharding-JDBC简介

Sharding-JDBC是Sharding-Sphere的一个产品,它有三个产品,分别是Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar,这三个产品提供了标准化的数据分片、读写分离、柔性事务和数据治理功能。我们这里用的是Sharding-JDBC,所以想了解后面两个产品的话可以去它们官网查看。

Sharding-JDBC为轻量级Java框架,使用客户端直连数据库,以jar包形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动,兼容性特别强。适用的ORM框架有JPA, Hibernate, Mybatis, Spring JDBC Template或直接使用JDBC;第三方的数据库连接池有DBCP, C3P0, BoneCP, Druid等;支持的数据库有MySQL,Oracle,SQLServer和PostgreSQL;多样化的配置文件Java,yaml,Spring Boot ,Spring命名空间。其实这里说的都是废话,大家可以不看,下面我们动手开始正式配置。

二、具体的实现方式

1、maven引用

我这里用的配置方式是Spring命名空间配置,所以只需要引用sharding-jdbc-spring-namespace就可以了,还有要注意的是我用的不是当当网的sharding,注意groupId是io.shardingsphere。如果用的是其它配置方式可以去http://maven.aliyun.com/nexus/#nexus-search;quick~io.shardingsphere网站查找相应maven引用

    io.shardingsphere    sharding-jdbc-spring-namespace    3.0.0.M1

2、数据库准备

我这里用的是mysql数据库,根据我们项目的具体需求,我准备了三个主库和对应的从库。模拟的主库名有master,暂时没有做对应从库,所以对应的从库还是指向master;第二个主库有master_1,对应的从库有master_1_slaver_1,master_1_slave_2;第三个主库有master_2,对应的从库有master_2_slave_1,master_2_slave_2。
数据库中的表也做了分表,下面是对应的mysql截图。


这第一幅图上的主从库都应该在不同的服务器上的,但这里只是为了模拟所以就建在了本地服务器上了。我们读写分离中的写操作只会发生在主库上,从库会自动同步主库上的数据并为读提供数据。数据库的主从复制在上篇博文中做了详细的介绍,大家可以去看看https://www.yisu.com/article/226077.htm


这幅图作为我们本来的主库,下面做的分库和分表都是基于这个库中的订单表分的。所以分库中的表只有订单表和订单明细表。


第三幅图截的是第二个主库,里面对订单和订单明细表做了分表操作,具体的分片策略和分片算法下面再做介绍。第三个主表和第二个主表是一样的,所有的从表都和对应的主表是一致的。

3、Spring配置

数据库信息配置文件db.properties配置可以配置两份,分为开发版和测试版,如下:

# masterMaster.url=jdbc:mysql://localhost:3306/master?useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedStatements=trueMaster.username=rootMaster.password=123456Slave.url=jdbc:mysql://localhost:3306/master?useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedStatements=trueSlave.username=rootSlave.password=123456# maste_1Master_1.url=jdbc:mysql://localhost:3306/master_1?useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedStatements=trueMaster_1.username=rootMaster_1.password=123456Master_1_Slave_1.url=jdbc:mysql://localhost:3306/master_1_slave_1?useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedStatements=trueMaster_1_Slave_1.username=rootMaster_1_Slave_1.password=123456Master_1_Slave_2.url=jdbc:mysql://localhost:3306/master_1_slave_2?useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedStatements=trueMaster_1_Slave_2.username=rootMaster_1_Slave_2.password=123456# master_2Master_2.url=jdbc:mysql://localhost:3306/master_2?useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedStatements=trueMaster_2.username=rootMaster_2.password=123456Master_2_Slave_1.url=jdbc:mysql://localhost:3306/master_2_slave_1?useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedStatements=trueMaster_2_Slave_1.username=rootMaster_2_Slave_1.password=123456Master_2_Slave_2.url=jdbc:mysql://localhost:3306/master_2_slave_2?useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedStatements=trueMaster_2_Slave_2.username=rootMaster_2_Slave_2.password=123456

Spring对应的配置:
Spring-Sphere官网中的demo里用的都是行表达式的分片策略,但是行表达式的策略不利于数据库和表的横向扩展,所以我这里用的是标准分片策略,精准分片算法和范围分片算法。因为我们项目中暂时用的分片键都是user_id单一键,所以说不存在复合分片策略,也用不到Hint分片策略,行表达式分片策略和不分片策略。

                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            

4、精准分片算法和范围分片算法的Java代码

标准分片策略,精准分片算法

package com.jihao.algorithm;import io.shardingsphere.core.api.algorithm.sharding.PreciseShardingValue;import io.shardingsphere.core.api.algorithm.sharding.standard.PreciseShardingAlgorithm;import java.util.Collection;import com.alibaba.fastjson.JSON;/** * 自定义标准分片策略,使用精确分片算法(=与IN) * @author JiHao * */public class PreciseModuleDatabaseShardingAlgorithm implements PreciseShardingAlgorithm{        @Override        public String doSharding(Collection availableTargetNames,                        PreciseShardingValue preciseShardingValue) {                System.out.println("collection:" + JSON.toJSONString(availableTargetNames) + ",preciseShardingValue:" + JSON.toJSONString(preciseShardingValue));        for (String name : availableTargetNames) {                // =与IN中分片键对应的值                String value = String.valueOf(preciseShardingValue.getValue());                // 分库的后缀                int i = 1;                // 求分库后缀名的递归算法            if (name.endsWith("_" + countDatabaseNum(Long.parseLong(value), i))) {                return name;            }        }        throw new UnsupportedOperationException();        }        /**         * 计算该量级的数据在哪个数据库         * @return         */        private String countDatabaseNum(long columnValue, int i){                // ShardingSphereConstants每个库中定义的数据量                long left = ShardingSphereConstants.databaseAmount * (i-1);                long right = ShardingSphereConstants.databaseAmount * i;                if(left < columnValue && columnValue <= right){                        return String.valueOf(i);                }else{                        i++;                        return countDatabaseNum(columnValue, i);                }        }}

标准分片策略,范围分片算法

package com.jihao.algorithm;import io.shardingsphere.core.api.algorithm.sharding.RangeShardingValue;import io.shardingsphere.core.api.algorithm.sharding.standard.RangeShardingAlgorithm;import java.util.ArrayList;import java.util.Collection;import java.util.List;import com.alibaba.fastjson.JSON;import com.google.common.collect.Range;/** * 自定义标准分库策略,使用范围分片算法(BETWEEN AND) * @author JiHao * */public class RangeModuleDatabaseShardingAlgorithm implements RangeShardingAlgorithm{        @Override        public Collection doSharding(                        Collection availableTargetNames,                        RangeShardingValue rangeShardingValue) {                System.out.println("Range collection:" + JSON.toJSONString(availableTargetNames) + ",rangeShardingValue:" + JSON.toJSONString(rangeShardingValue));        Collection collect = new ArrayList<>();        Range valueRange = rangeShardingValue.getValueRange();        // BETWEEN AND中分片键对应的最小值        long lowerEndpoint = Long.parseLong(String.valueOf(valueRange.lowerEndpoint()));        // BETWEEN AND中分片键对应的最大值        long upperEndpoint = Long.parseLong(String.valueOf(valueRange.upperEndpoint()));        // 分表的后缀        int i = 1;        List arrs = new ArrayList();        // 求分表后缀名的递归算法        List list = countDatabaseNum(i, lowerEndpoint, upperEndpoint, arrs);        for (Integer integer : list) {                        for (String each : availableTargetNames) {                                if (each.endsWith("_" + integer)) {                  collect.add(each);                                }                        }                }        return collect;        }                /**         * 计算该量级的数据在哪个表         * @param columnValue         * @param i         * @param lowerEndpoint 最小区间         * @param upperEndpoint 最大区间         * @return         */        private List countDatabaseNum(int i, long lowerEndpoint, long upperEndpoint, List arrs){                long left = ShardingSphereConstants.databaseAmount * (i-1);                long right = ShardingSphereConstants.databaseAmount * i;                // 区间最大值小于分库最大值                if(left < upperEndpoint && upperEndpoint <= right){                        arrs.add(i);                        return arrs;                }else{                        if(left < lowerEndpoint && lowerEndpoint <= right){                                arrs.add(i);                        }                        i++;                        return countDatabaseNum(i, lowerEndpoint, upperEndpoint, arrs);                }        }}

分库的策略用的和分库的代码是一样的,不同之处就是分库用的是databaseAmount,分表用的是tableAmount。下面的ShardingSphereConstants的代码。

package com.jihao.algorithm;/** * ShardingSphere中用到的常量 * @author JiHao * */public class ShardingSphereConstants {                /**         * 订单、优惠券相关的表,按用户数量分库,64w用户数据为一个库         * (0,64w]         */        public static int databaseAmount = 640000;                /**         * 一个订单表里存10000的用户订单         * (0,1w]         */        public static int tableAmount = 10000;        }

到这里所有的配置基本上都已经完成了,下面的测试。

5、测试

下面是测试的mybatis的测试文件,都是最基础的就不讲解了。

                                                                      INSERT INTO t_order (                    user_id, status                 )            VALUES (                    #{userId,jdbcType=INTEGER},                     #{status,jdbcType=VARCHAR}                 )                      INSERT INTO t_order_item (          order_id, user_id        )        VALUES (                #{orderId,jdbcType=INTEGER},                #{userId,jdbcType=INTEGER}        )                                                

下面对应的mapper的Java代码

package com.jihao.dao;import java.util.List;import java.util.Map;import org.apache.ibatis.annotations.Mapper;import com.jihao.entity.Order;import com.jihao.entity.OrderItem;@Mapperpublic interface TestShardingMapper {            int insert(Order record);        int insertItem(OrderItem record);        List searchOrder();        List queryWithEqual();        List queryWithIn();        List queryWithBetween();        List> queryUser();    }

下面是对应的订单entity代码

package com.jihao.entity;/** * 订单 * @author JiHao */public class Order {            private Long orderId;    private Integer userId;        private String status;        public Long getOrderId() {                return orderId;        }        public void setOrderId(Long orderId) {                this.orderId = orderId;        }        public Integer getUserId() {                return userId;        }        public void setUserId(Integer userId) {                this.userId = userId;        }        public String getStatus() {                return status;        }        public void setStatus(String status) {                this.status = status;        }    }

下面是对应的订单明细entity代码

package com.jihao.entity;/** * 测试分片 * @author JiHao */public class OrderItem {                private Long orderItemId;            private Long orderId;    private Integer userId;            public Long getOrderId() {                return orderId;        }        public void setOrderId(Long orderId) {                this.orderId = orderId;        }        public Integer getUserId() {                return userId;        }        public void setUserId(Integer userId) {                this.userId = userId;        }        public Long getOrderItemId() {                return orderItemId;        }        public void setOrderItemId(Long orderItemId) {                this.orderItemId = orderItemId;        }}

下面是测试的controller,并没有写Junit测试。

package com.jihao.controller.test;import java.util.List;import java.util.Map;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;import com.jihao.dao.TestShardingMapper;import com.jihao.entity.Order;import com.jihao.entity.OrderItem;import com.jihao.result.Result;import com.jihao.result.ResultUtil;/** * 测试分片 * @author JiHao * */@Controller@RequestMapping(value = "test")public class TestShardingController {        @Autowired    private TestShardingMapper testShardingMapper;    /**     * 测试添加     * @return     */    @ResponseBody    @GetMapping(value = "/testAdd")    public String testAdd(){            for (int i = 0; i < 10; i++) {            Order order = new Order();//            order.setUserId(50);//            order.setUserId(51);//            order.setUserId(10001);            order.setUserId(20001);            order.setStatus("INSERT_TEST");            int count = testShardingMapper.insert(order);            System.out.println(count);            long orderId = order.getOrderId();            System.out.println(order.getOrderId());            OrderItem item = new OrderItem();            item.setOrderId(orderId);//            order.setUserId(50);//            order.setUserId(51);//            order.setUserId(10001);            order.setUserId(20001);            testShardingMapper.insertItem(item);        }        return "success";    }        /**     * 测试搜索     * @return     */    @ResponseBody    @GetMapping(value = "/testSearch")    public Result searchData(){            List list = testShardingMapper.searchOrder();            System.out.println(list.size() + " all");            List list1 = testShardingMapper.queryWithIn();            System.out.println(list1.size() + " In");            List list2 = testShardingMapper.queryWithEqual();            System.out.println(list2.size() + " Equal");            List list3 = testShardingMapper.queryWithBetween();            System.out.println(list3.size() + " Between");            List> list4 = testShardingMapper.queryUser();            System.out.println(list4.size() + " user");            return ResultUtil.success(null);    }}

这里要重点提出来的是做搜索测试的时候,因为主从库都在我本地服务器上,并没有做主从复制,大家可以根据我上篇博文配置一下就可以顺利操作了,如果没有配置的话从库里是不会有数据的,所以在做完写操作时把主库中的数据手动传输给从库,这样才能读出数据。

"SpringBoot怎么使用Sharding-JDBC实现数据分片和读写分离"的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注网站,小编将为大家输出更多高质量的实用文章!

0