当前位置: 首页 > 后端技术 > Java

分库分表时库表序号计算方法

时间:2023-04-01 14:39:51 Java

分库分表时数据库序号的计算方法。——详谈横向分库分表1、良好的分库分表方案的可持续性。划分完成后,后面的数据会再次增加到需要划分的程度是否便于操作。数据倾斜问题。数据需要尽可能均匀地分布在每个数据库的每个表中。二、常见的分库分表方案range分库分表是按照数据范围进行切分的,比如按年份划分订单数据。这里简单罗列一下它的缺点:A、数据热点问题B、跨范围hash分库分表内部数据处理这个方案很流行,但是也有很多坑。先来看一些有问题的操作:常见错误案例一:非互质关系导致的数据倾斜问题publicstaticShardCfgshard(StringuserId){inthash=userId.hashCode();//取库数量余数的结果就是库序列号intdbIdx=Math.abs(hash%DB_CNT);//表数量取余的结果就是表序号inttblIdx=Math.abs(hash%TBL_CNT);returnnewShardCfg(dbIdx,tblIdx);}这里通过Hash值分别获取分库和分表的个数,另外获取库序号和表序号。其实稍加思考就会发现,以10个数据库,100张表为例,如果100的余数一个Hash值为0,那么10的余数一定为0。也就是说只有0库的0表可能有数据,其他库的0表永远是空的!这就造成了严重的数据倾斜问题!!常见错误案例2:扩容难以继续我们把10个数据库,100张表看成一共1000张逻辑表,将得到的Hash值取1000的余数,得到[0,999)之间的一个数,然后的数平均分到每个库和每个表中。大概的逻辑代码如下:publicstaticShardCfgshard(StringuserId){//①计算Hashinthash=userId.hashCode();//②分片总数intsumSlot=DB_CNT*TBL_CNT;//③片段序号intslot=Math.abs(hash%sumSlot);//④计算库序号和表序号错误情况intdbIdx=slot%DB_CNT;inttblIdx=插槽/DB_CNT;返回新的ShardCfg(dbIdx,tblIdx);}该方案可以避免数据倾斜的问题,但其分片序号取决于分片总数。如果后面再扩容数据,扩容前后的数据不会在同一个表中。例如扩容前Hash为1986的数据应该存放在6库98表中,但二次扩容后为20库100表,分配到6库99表,表序号已经转移。这样的话,我们以后扩展的时候,不仅要基于库进行数据迁移,还要基于表进行数据迁移。说说正确的解决方案:正确的解决方案一:标准的二次分片方式。上面的错误方案2是根据总库数计算出库表的序号。这里稍作改动,使用总表数计算依据:publicstaticShardCfgshard2(StringuserId){//①计算Hashinthash=userId.hashCode();//②切片总数intsumSlot=DB_CNT*TBL_CNT;//③切片编号intslot=Math.abs(hash%sumSlot);//④重新修改二次评估方案intdbIdx=slot/TBL_CNT;inttblIdx=插槽%TBL_CNT;返回新的ShardCfg(dbIdx,tblIdx);}容量翻倍后,我们表的序号必须保持不变,数据库序号可能还在原数据库中,也可能是翻译到新数据库中(原数据库序号加上原数据库序号分库),完全满足我们需要的扩展持久化方案。当然,这种解决方案也有缺点。连续的shardkey哈希值大概率会分散在同一个库中,部分业务容易出现库热点。正确方案二:RelationalTableRedundancy通过关系表记录library对应的shardkey的关系,也就是关系路由表。publicstaticShardCfgshard(StringuserId){inttblIdx=Math.abs(userId.hashCode()%TBL_CNT);//从缓存中获取整数dbIdx=loadFromCache(userId);if(null==dbIdx){//从路由表中获取dbIdx=loadFromRouteTable(userId);if(null!=dbIdx){//保存到缓存saveRouteCache(userId,dbIdx);}}if(null==dbIdx){//这里可以自由实现计算库的逻辑dbIdx=selectRandomDbIdx();saveToRouteTable(userId,dbIdx);saveRouteCache(userId,dbIdx);}返回新的ShardCfg(dbIdx,tblIdx);}本方案是通过常规的Hash算法计算表序号,在计算数据库序号时,再从路由表中读取数据。它的灵活性在于,如果发现某个库中存在数据倾斜,可以通过设置权重来调整数据的分布。还有一个好处就是后期容易扩展。但是重点也很明显:访问时多了一层路由,会有一定的性能损失。还有一个需要考虑的问题,这里是用userID关联,不会占用很大空间,但是如果是文件的MD5摘要等其他key就不适合用这种方式。正确方案3:如果消除公因的方法是将N库中的N1表升级到M表,在需要保持库序号不变的场景下可以考虑该方案。这里库序号和表序号是独立计算的:publicstaticShardCfgshard(StringuserId){intdbIdx=Math.abs(userId.hashCode()%DB_CNT);//去除计算表序号时公约数的影响inttblIdx=Math.abs((userId.hashCode()/TBL_CNT)%TBL_CNT);returnnewShardCfg(dbIdx,tblIdx);}按照之前作者的说法,这个方案的数据倾斜度并不大。正确方案4:用一致的Hash方法设置一个配置,使配置持久化,在使用privateTreeMapnodeTreeMap=newTreeMap<>();时加载@OverridepublicvoidafterPropertiesSet(){//启动时加载分区配置ListcfgList=fetchCfgFromDb();对于(HashCfgcfg:cfgList){nodeTreeMap.put(cfg.endKey,cfg.nodeIdx);}}publicShardCfgshard(StringuserId){inthash=userId.hashCode();intdbIdx=nodeTreeMap.tailMap((long)hash,false).firstEntry().getValue();inttblIdx=Math.abs(hash%100);returnnewShardCfg(dbIdx,tblIdx);}范围分表非常相似。Range分库分表方式为shardkey本身划分范围,consistentHash是针对shardkey的Hash值的范围配置。