Springboot+Sharding-JDBC分库分表实践二之分表策略


前言

上篇文章主要使用SpringBoot+Mybatis-Plus+Sharding-JDBC实现了简单的分表,本篇来讲下Sharding-JDBC提供的几种分表策略,通过使用不同的策略可以实现各种复杂的分表场景

一、分片策略是什么?

包含分片键和分片算法,由于分片算法的独立性,将其独立抽离。真正可用于分片操作的是分片键 + 分片算法,也就是分片策略。目前提供5种分片策略。接下来逐一讲解使用方式

二、使用

1.行表达式分片策略

对应InlineShardingStrategy。使用Groovy的表达式,提供对SQL语句中的=和IN的分片操作支持,只支持单分片键。对于简单的分片算法,可以通过简单的配置使用,从而避免繁琐的Java代码开发,如: t_user_$->{u_id % 8} 表示t_user表根据u_id模8,而分成8张表,表名称为t_user_0到t_user_7。

上篇文章我们已经使用了行表达式分片策略来进行分表操作了,具体使用方式也很简单,只需要简单的配置即可:

t_user_order:
   key-generator-column-name: id  #需要自动生成的字段,使用雪花算法生成
   actual-data-nodes: ds0.t_user_order${0..2}    
   #数据节点,即要对目标表进行几个节点的拆分,
   #${0..2} 为Groovy语法,
   #t_user_order${0..2}解析后为t_user_order0、t_user_order1、t_user_order2三张表
   table-strategy:  #分表策略
     inline: #行表达式策略
       sharding-column: id  #分表键 即通过该字段来分表
       algorithm-expression: t_user_order${id % 3} #算法表达式,这通过对id求余来进行分表

2.标准分片策略

对应StandardShardingStrategy。提供对SQL语句中的=, >, <, >=, <=, IN和BETWEEN AND的分片操作支持。StandardShardingStrategy只支持单分片键,提供PreciseShardingAlgorithm和RangeShardingAlgorithm两个分片算法。PreciseShardingAlgorithm是必选的,用于处理=和IN的分片。RangeShardingAlgorithm是可选的,用于处理BETWEEN AND, >, <, >=, <=分片,如果不配置RangeShardingAlgorithm,SQL中的BETWEEN AND将按照全库路由处理。

配置
t_user:  #t_user表
  key-generator-column-name: id  #主键
  actual-data-nodes: ds0.t_user${0..1}    #数据节点,均匀分布
  table-strategy:  #分表策略
    standard:  #标准分片策略
      sharding-column: sex #分片字段
      #分片算法
      precise-algorithm-class-name: com.none.sharding.infrastruc.shardingAlgorithm.UserShardingAlgorithm
      
UserShardingAlgorithm
public class UserShardingAlgorithm implements PreciseShardingAlgorithm<Long>, RangeShardingAlgorithm<Long> {
    /**
     * 精确分片算法
     * 这里我们可以实现自己的分片算法
     * @param collection 实际分表的节点 比如t_user0\t_user1
     * @param preciseShardingValue    
     * 分片值对象,包括逻辑表名即原表名t_user; 分片键、分片键的值
     * @return 返回实际表名即t_user0
     */
    @Override
    public String doSharding(Collection<String> collection, PreciseShardingValue<Long> preciseShardingValue) {
        System.out.println("--------------preciseShardingValue" + preciseShardingValue.getColumnName());
        System.out.println("--------------collection" + collection);
        return preciseShardingValue.getLogicTableName() + String.valueOf(preciseShardingValue.getValue()%2);
    }

    /**
     * 范围分片算法
     * 默认将使用全路由即表节点有几个就会去查几张表,
     * 然后对表结果进行聚合返回,性能较差
     *
     * @param availableTargetNames available data sources or tables's names
     * @param shardingValue        sharding value
     * @return sharding results for data sources or tables's names
     */
    @Override
    public Collection<String> doSharding(Collection<String> availableTargetNames, RangeShardingValue<Long> shardingValue) {
        return availableTargetNames;
    }
}

3.复合分片策略

对应ComplexShardingStrategy。复合分片策略。提供对SQL语句中的=, >, <, >=, <=, IN和BETWEEN AND的分片操作支持。ComplexShardingStrategy支持多分片键,由于多分片键之间的关系复杂,因此并未进行过多的封装,而是直接将分片键值组合以及分片操作符透传至分片算法,完全由应用开发者实现,提供最大的灵活度。

这里有个场景,比如我们发现公司的订单数据量每年递增基本都在1000W的数据量,如果我们只通过id进行拆分表,按照500W来拆分的话,,其实只需要两张表即可,但是随着数据量的逐年递增,每年都有1000W的数据量进来,那么很难对表进行一个合理的拆分,要么一下子拆分几十张满足几年的数据量,要么到时再进行伸缩,其实都不是一个较好的方案。这时候我们可以通过年份+user_id来组合拆分,即每年都拆分出两张表,看如下实现

配置
t_user_order:
   key-generator-column-name: id  #主键
   #数据节点,数据库中t_user_order20200,t_user_order20201,t_user_order20202
   # #数据节点,数据库中t_user_order20210,t_user_order20211,t_user_order20212
   actual-data-nodes: ds0.t_user_order${2020..2021}${0..2}    #数据节点,均匀分布
   table-strategy:  #分表策略
     complex: #复合策略
       #通过userId、createTime组合分片
       sharding-columns: user_id,create_time
       algorithm-class-name: com.none.sharding.infrastruc.shardingAlgorithm.ComplexShardingAlgorithm
      
ComplexShardingAlgorithm
public class ComplexShardingAlgorithm
        implements ComplexKeysShardingAlgorithm {
    /**
     * 复合分片算法
     * 分片键之间的关系通常都比较复杂,需要考虑in、<=、>、between
     * 等范围查询的场景下的分片规则
     * 这里只做个简单demo 实际场景还需要慎重考虑
     *
     * @param availableTargetNames available data sources or tables's names
     * @param shardingValue        sharding value
     * @return sharding results for data sources or tables's names
     */
    @Override
    public Collection<String> doSharding(Collection availableTargetNames, ComplexKeysShardingValue shardingValue) {
        //范围分片
        Map<String, Range<String>> rangeAndShardingValuesMap =
                shardingValue.getColumnNameAndRangeValuesMap();
        HashSet<Integer> yearSet = new HashSet<>();
        HashSet<Long> userIdSet = new HashSet<>();
        //时间作为范围查询
        if (rangeAndShardingValuesMap.containsKey("create_time")) {
            Range<String> createTimeRange = rangeAndShardingValuesMap.get("create_time");
            String lowerTime = "2020-01-01 00:00:00";
            if (createTimeRange.hasLowerBound()) {
                lowerTime = createTimeRange.lowerEndpoint();
            }
            yearSet.add(LocalDateTime.parse(String.valueOf(lowerTime),
                    DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")).getYear());

            String upperTime = "2021-01-01 00:00:00";
            if (createTimeRange.hasUpperBound()) {
                upperTime = createTimeRange.upperEndpoint();
            }

            yearSet.add(LocalDateTime.parse(String.valueOf(upperTime),
                    DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")).getYear());

        } else {
            //如果查询条件没有createTimed则执行全路由
            yearSet.add(2020);
            yearSet.add(2021);
        }

        //精确分片
        Map<String, Collection<Long>> columnNameAndShardingValuesMap =
                shardingValue.getColumnNameAndShardingValuesMap();
                //create_time 精确分片
        if (columnNameAndShardingValuesMap.containsKey("create_time")) {
            yearSet.clear();
            for (Object value : columnNameAndShardingValuesMap.get("create_time")) {

                yearSet.add(((LocalDateTime) value).getYear());
            }

        }
        //user_id作为精确分片
        if (columnNameAndShardingValuesMap.containsKey("user_id")) {
            for (Object value : columnNameAndShardingValuesMap.get("user_id")) {
                userIdSet.add((long) value % 3);
            }
        } else {
            //如果查询条件没有userId则执行全路由
            userIdSet.add(0L);
            userIdSet.add(1L);
            userIdSet.add(2L);
        }

        ArrayList<String> actualTables = new ArrayList<>();
        for (Integer year : yearSet) {
            for (Long uidMod : userIdSet) {
                actualTables.add(shardingValue.getLogicTableName() + year + uidMod);
            }
        }

        return actualTables;
    }
}```

示例:通过用户id和时间查询订单

```java
/**
     * 查询订单
     *
     * @param uid
     * @return
     */
    @RequestMapping("getOne")
    public UserOrderDO getOneOrder(@RequestParam Long uid,
                                   @RequestParam String dateTimeStart,
                                   @RequestParam String dateTimeEnd) {
        QueryWrapper<UserOrderDO> addressDOQueryWrapper = new QueryWrapper<>();
        addressDOQueryWrapper.lambda()
                .eq(UserOrderDO::getUserId, uid)
                .gt(UserOrderDO::getCreateTime, dateTimeStart)
                .lt(UserOrderDO::getCreateTime, dateTimeEnd);

        return userOrderService.getOne(addressDOQueryWrapper);
    }

生成的sql如下:

Logic SQL: SELECT  id,order_no,user_id,amount,create_time  FROM t_user_order  WHERE (user_id = ? AND create_time > ? AND create_time < ?)
## 实际sql会路由到t_user_order20201表
Actual SQL: ds0 ::: SELECT  id,order_no,user_id,amount,create_time  FROM t_user_order20201 
 WHERE (user_id = ? AND create_time > ? AND create_time < ?) ::: [1, 2020-01-01 00:00:00, 2020-11-01 00:00:00]

总结

以上对Sharding-JDBC的三种分片策略进行了简单介绍,由于Hint分片策略稍微复杂些,我们下篇文章专门来介绍
DEMO地址:https://github.com/nangge/sharding.git

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
### 回答1: 我可以为你提供一些关于使用SpringBoot ShardingJDBC MyBatis分库分表的建议,例如:1. 使用ShardingSphere框架定义数据库分片策略;2. 使用MyBatis实现MyBatis SQL脚本;3. 使用SpringBoot注解的方式定义数据源和ShardingJDBC的配置。 ### 回答2: 使用SpringBoot ShardingJDBCMyBatis可以很方便地实现分库分表功能。首先,ShardingJDBC是一个轻量级的数据库中间件,可以将数据分散到不同的数据库实例中,从而实现分库的效果。其次,MyBatis是一个流的持久层框架,可以通过XML或注解的方式与数据库进交互。 在使用SpringBoot ShardingJDBCMyBatis分库分表时,首先需要配置ShardingJDBC的数据源和分片规则。可以通过编写一个配置类来配置分库分表的规则,例如可以根据某个字段的取值来确定数据应该分散到哪个库或表中。配置完成后,就可以在MyBatis的Mapper接口中直接使用分库分表的数据源,从而实现对不同数据库或表的访问。 在编写Mapper接口时,可以使用MyBatis提供的注解或XML方式来编写SQL语句。在SQL语句中,可以使用ShardingJDBC提供的分片键来实现对特定库或表的访问。例如,在需要查询特定表的数据时,可以使用ShardingJDBC提供的Hint注解将查询操作路由到相应的表上。 总的来说,使用SpringBoot ShardingJDBCMyBatis可以实现简单、高效的分库分表功能。通过配置ShardingJDBC分片规则和使用MyBatis编写SQL语句,可以将数据分散到不同的数据库实例和表中,从而实现了水平扩展和负载均衡的效果。这种方式能够帮助我们提高数据库的性能和容量,从而更好地应对大规模的数据存储需求。 ### 回答3: 使用SpringBoot ShardingJDBC MyBatis可以轻松实现分库分表。 首先,ShardingJDBC是一个分库分表的开源框架,它可以通过数据库中间件实现数据的分散存储。而SpringBoot是一个快速构建项目的框架,可以帮助开发者轻松集成各种组件。 使用SpringBoot ShardingJDBC MyBatis分库分表,首先需要配置ShardingJDBC的数据源、分片策略以及分表策略。可以通过配置文件或者编程方式来完成配置。配置数据源时,可以指定多个数据库的连接信息,并使用分片策略将数据分配到不同的数据库中。配置分表策略时,可以指定不同的分表规则,将数据根据一定的规则分散存储在不同的表中。 在具体的业务逻辑中,可以使用MyBatis来操作数据库。MyBatis是一个简化数据库访问的持久层框架,通过编写SQL语句和映射文件,可以轻松实现数据库的增删改查操作。 在访问数据库时,ShardingJDBC会根据配置的分片策略分表策略,自动将数据路由到指定的数据库和表中。开发者不需要关心数据的具体存储位置,只需要使用MyBatis的API进数据操作即可。 使用SpringBoot ShardingJDBC MyBatis分库分表,可以提高数据库的读写性能,增加数据的存储容量,并且可以实现数据的动态扩容和迁移。此外,由于SpringBootMyBatis的高度集成,开发者可以更加方便地进开发和维护。 总之,使用SpringBoot ShardingJDBC MyBatis分库分表可以帮助开发者更好地管理数据,提升系统的性能和可扩展性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值