ShardingSphere复合分片

  上一篇《ShardingSphere入门》中有提到,要想利用ShardingSphere设计一套满足项目需求的分库分表解决方案,分片策略是关键,其中分片策略又由分片键和分片算法组成,所以在设计分库分表解决方案的时候,我着重于分片键以及分片算法的设计。

 分片键选择

  shardingsphere分库分表目的首先是解决数据量过大,数据可能存储不下的问题,其次就是性能问题。数据量过大进行分片就可以解决了,那性能问题该如何处理呢?

  在shardingsphere jdbc中,假设我们使用order_id作为分片键,那么如果使用user_id进行数据查询的时候,就会走全路由,也就是会查询所有分片。对于大型商城的订单系统,他们订单表可能会分成百上千个分片,那么如果走全路由,这对性能来说是非常大的损耗,所以我们不能只考虑订单id作为分片键,需要根据业务考量,再结合合适的分片算法,提升sql执行性能。

  针对订单表,考虑到我方系统主要是新增和查询交易,其中查询又分为根据用户id查询以及根据订单id查询,所以我们可以将userid以及orderid设置为分片键。这时候有些人可能会有疑惑,还会有根据时间进行范围查询的呀,怎么不考虑进去?可以结合传统的商城购买场景,凡是范围查询,userid都是必传的,所以这种查询也是包含分片键的,也就是说并不会去查询全分片。

分片算法选择

  为保证使用多个分片键的同时,查询效率还比较高,我这边使用Type为Complex自定义的“求模取余”算法,那么该自定义算法如何设计呢?

  首先我们必须要明确,执行订单表的insert语句时,根据shardingsphere提供的ComplexKeysShardingAlgorithm接口得到的分片结果,其必须是唯一值,也就是说除了计算得到的分片数不能为零之外,根据多个分片键(shardingKeys)以及对应分片值(shardingValues)计算得到的分片数不能为多个值,这一点很重要。区别于订单表的update、delete、select语句,这三个sql语句是可以产生多个分片数的。所以在设计分片算法时候,需要考虑到上述两种不同情况。

  下面说一下我多个分片键的复合分片算法设计:

总体逻辑就是一句话:保证在orderid和userid中就能识别出具体是哪个数据库以及哪张表,同时为了避免通过orderid、userid计算分片信息时只有一个值,我们也得保证orderid和userid中代表数据库和表信息的下标值是一致的。

例如:orderid为:11000001012411211632509625400000,userid为:10000001012411211634589095400001,我们将字符串下标为2-4记为数据库的索引,也就是“00”;将字符串下标为5-9记为表的索引,也就是0001。可以看到userid和orderid的这两个值都是相同的,于是他们生成的分片也只会有一个。所以我们需要关注的就只是如何保证同一订单的userid和orderid字符串下标的2-9位所代表的值是一致的?

  在分布式系统中,分布式orderid都是由服务端生成的,客户端需要请求分布式id服务接口获取不同的订单id。在我所处的系统中,门店id(记为storeid)是在所有交易中都会由客户端传入的,所以我生成分布式订单id以及userid都和storeid关联,通过storeid来保证同一订单的userid和orderid字符串下标的2-9位所代表的值是一致的,也就是保证同一订单的orderid和userid所代表的分片是一致的。

所以难点就在于如何通过storeid生成上述规则的分布式订单id以及userid?

 根据门店id生成分布式订单id以及userid

计算分片

我们配置的数据库数量是4,表数量也是8,在“2”中说明了,我们使用的是求模取余算法,所以我们可以根据门店id就计算出它属于哪个分片。

例如:

假设一个门店是017,我们配置的数据库数量是4ds0,ds1,ds2,ds3),表数量是8(t_order_0000...t_order_0007),那么这个门店所在的数据库就是017/8=2.......1,也就是在下标为2ds2)代表的数据库(第三个数据库)。下标为1的表(第二个表,表数量从0开始记的,余数为0才是第一个)

综上所属就是ds2.t_order_0001

设计分布式id

	package ddd.application.base.sequence;
	
	import common.toolkit.DateUtils;
	import ddd.application.base.StringUtil;
	import ddd.application.constants.ShardingConstant;
	import ddd.application.enum_.DbAndTableEnum;
	import org.apache.commons.lang3.StringUtils;
	import org.apache.commons.lang3.SystemUtils;
	import org.apache.shardingsphere.sharding.algorithm.keygen.SnowflakeKeyGenerateAlgorithm;
	import org.springframework.beans.factory.annotation.Autowired;
	import org.springframework.stereotype.Component;
	
	import java.util.Date;
	import java.util.HashMap;
	import java.util.Map;
	import java.util.UUID;
	
	/**
	 * @author chenjian
	 * @version 1.0
	 * @date 2024/11/05 10:17
	 * @className KeyGenerator
	 * @desc 自定义分布式主键生成器
	 */
	@Component
	public class KeyGenerator {
	
	
	    @Autowired
	    private SequenceGenerator sequenceGenerator;
	
	    /**默认集群机器总数*/
	    private static final int DEFAULT_HOST_NUM = 64;
	
	    /** 通过门店id获取系统内部所有分片相关的id,包括:订单id、用户id。这就导致同一门店获取到的userid或者orderid所在的数据库index、表index都是相同的,
	     *  如是就避免了根据userid和orderid insert、update、delete数据时,会出现计算出多个分片的情况。
	     *
	     *   同时业务方可以根据门店就预估出该门店所属哪个分片。
	     *   例如:
	     *   假设一个门店是017,我们配置的数据库数量是4(ds0,ds1,ds2,ds3),表数量是8(t_order_0000...t_order_0007),那么这个门店所在的数据库就是 017/8 = 2 ....... 1,也就是在下标为2(ds2)代表的数据库(第三个数据库)。下标为1的表(第二个表,表数量从0开始记的,余数为0才是第一个)
	     *   综上所属就是ds2.t_order_0001
	     *
	     * @param targetEnum 待生成主键的目标表规则配置
	     * @param storeId  门店id
	     * @return
	     */
	    public String generateKey(DbAndTableEnum targetEnum, String storeId) {
	
	        if (StringUtils.isBlank(storeId)) {
	            throw new IllegalArgumentException("路由id参数为空");
	        }
	
	        StringBuilder key = new StringBuilder();
	        /** 1.id业务前缀*/
	        String idPrefix = targetEnum.getCharsPrefix();
	        /** 2.id数据库索引位*/
	        String dbIndex = getDbIndexAndTbIndexMap(targetEnum, storeId).get("dbIndex");
	        /** 3.id表索引位*/
	        String tbIndex = getDbIndexAndTbIndexMap(targetEnum, storeId).get("tbIndex");
	        /** 4.id规则版本位*/
	        String idVersion = targetEnum.getIdVersion();
	        /** 5.id时间戳位*/
	        String timeString = DateUtils.formatTime(new Date());
	        /** 6.id分布式机器位 2位*/
	        String distributedIndex = getDistributedId(2);
	        /** 7.随机数位*/
	        String sequenceId = sequ
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值