本文主要描述分库分表的算法方案、按什么规则划分。按部就班比较目前出现的几种规则方式,最后第五种增量迁移方案是我设想和推荐的方式。后续章再讲述技术选型和分库分表后带来的问题。java
随着业务量递增,数据量递增,一个表将会存下大量数据,在一个表有一千万行数据时,经过sql优化、提高机器性能还能承受。为了将来长远角度应在必定程度时进行分库分表,如出现数据库性能瓶颈、增长字段时须要耗时比较长的时间的状况下。解决独立节点承受全部数据的压力,分布多个节点,提供容错性,没必要一个挂整个系统不能访问。node
本文讲述的分库分表的方案,是基于水平分割的状况下,选择不一样的规则,比较规则的优缺点。 通常网上就前三种,正常一点的会说第四种,但不是很完美,前面几种迁移数据都会很大影响,推荐我认为比较好的方案五。git
公式:key mod x (x为天然数)算法
Key能够为主键,也能够为订单号,也能够为用户id,这个须要根据场景决定,哪一个做为查询条件几率多用哪一个。sql
优势:数据库
缺点:bash
能够按日、按月、按季度。服务器
tb_20190101
tb_20190102
tb_20190103
……
复制代码
这个算法要求在订单号、userId上添加年月日或者时间戳,或者查询接口带上年月日,才能定位在哪一个分片。分布式
优势:函数
缺点:
推荐使用场景:日志记录
表0 [0,10000000)
表1 [10000000,20000000)
表2 [20000000,30000000)
表3 [30000000,40000000)
……
复制代码
优势:
缺点:
先说一下一致性hash,有些文章说一致性Hash是一种算法,我认为它并非具体的计算公式,而是一个设定的思路。
1.先假定一个环形Hash空间,环上有固定最大值和最小值,头尾相连,造成一个闭环,如int,long的最大值和最小值。 不少文章会假定2^32个位置,最大值为2^32-1最小值为0,即0~(2^32)-1的数字空间,他们只是按照经常使用的hash算法举例,真实分库分表的状况下不是用这个数字,因此我才会认为一致性hash算法实际上是一个理念,并非真正的计算公式。 以下图
这里不详细说明这个理论,它主要表达的意思是固定好最大值,就再也不修改最大值到最小值范围,后续只修改节点node的位置和增长node来达到减小每一个node要管的数据,以达到减小压力。
备注:
* 不推荐对ip进行hash,由于可能会致使hash(ip)得出的结果很大,例如得出60,若这个节点的前面没有节点,则60号位置的这个节点须要管大部分的数据了。
* 最好生成key的方式用雪花算法snowFlake来作,至少要是不重复的数字,也不要用自增的形式。
* 推荐阅读铜板街的方案 订单号末尾添加user%64
复制代码
利用一致性hash理论,分库选择hash(key)的公式基准为 value= key mod 64,分表公式value= key / 64 mod 64,key为订单号或者userId等这类常常查询的主要字段。(后续会对这个公式有变化) 咱们假定上述公式,则能够分64个库,每一个库64个表,假设一个表1千万行记录。则最大64 * 64 * 1000万数据,我相信不会有这一天的到来,因此咱们以这个做为最大值比较合理,甚至选择32 * 32均可以。
由于前期用不上这么多个表,一开始创建这么多表每一个表都insert数据,会形成浪费机器,因此在咱们已知最大值的状况下,咱们从小的数字开始使用,因此咱们将对上述计算得出的value进行分组。
分组公式:64 = 每组多少个count * group须要分组的个数
数据所在环的位置(也就是在哪一个库中):value = key mode 64 / count * count
复制代码
如下举例为16组,也就是16个库,group=16 这个时候库的公式为value = key mode 64 / 4 * 4,除以4后,会截取小数位得出一个整数,而后 * 4倍,就是数据所在位置。
// 按4个为一组,分两个表
count = 4:
Integer dbValue = userId % 64 / count * count ;
复制代码
hash(key)在0~3之间在第0号库
hash(key)在4~7之间在第4号库
hash(key)在8~11之间在第8号库
……
复制代码
备注:其实一开始能够64个为一组就是一个库,后续变化32个为一组就是两个库,从一个库到两个库,再到4个库,逐步递进。
下图中举例分16组后,变为分到32组,须要每一个库都拿出一半的数据迁移到新数据,扩容直到分64个组。
能够看到当须要进行扩容一倍时须要迁移一半的数据量,以2^n递增,因此进行影响范围会比较大。
优势:
缺点:
(我认为比较好的方案)
一致性hash方案结合比较范围方案,也就是方案三和方案四的结合。
方案四是设定最大范围64,按2^n指数形式从1增长库或者表数量,这样带来的是每次拆分进行迁移时会影响当整体数据量的1/2的数据,影响范围比较大,因此要么就直接拆分到32组、64组一劳永逸,要么每次1/2迁移。
方案四对应迁移方案:
如今我想方法时,保持一致性hash理念,1个1个节点来增长,而不是方案四的每次增长2^n-n个节点。可是代码上就须要进行对新节点内的数据hash值判断。
咱们基于已经发生过1次迭代分了两个库的状况来作后续迭代演示,首先看看已经拆分两个库的状况:
数据落在第64号库名为db64和第32号库名为db32
迭代二: 区别与方案四直接增长两个节点,咱们只增长一个节点,这样迁移数据时由本来影响1/2的用户,将会只影响1/4的用户。
// 按32改成16个为一组,分两变为4个库
count = 16;
Integer dbValue = userId % 64 / count * count ;
if(dbValue<16){
// 上一个迭代这些数据落在db32中,如今走新增节点名为db16号的那个库
dbValue = 16;
return dbValue;
} else {
// 按原来规则走
return dbValue;
}
复制代码
迭代三:
迁移前能够先上线,增长一段开关代码,请求接口特殊处理hash值小于16的订单号或者用户号,这样就只会影响1/4的人
// 在请求接口中增长逻辑
public void doSomeService(Integer userId){
if(迁移是否完成的开关){
// 若是未完成
Integer dbValue = userId % 64 / count * count ;
if(dbValue<16){
//这部分用户暂时不能走下面的逻辑
return ;
}
}
return dbValue;
}
}
复制代码
// 在分片时按32个为一组,分两个库
count = 16;
Integer dbValue = userId % 64 / count * count ;
if(dbValue<16){
// 上一个迭代这些数据落在db32中,有一半须要走新增节点名为db16号的那个库
if(迁移是否完成的开关){
// 若是已经完成,就去db16的库
dbValue = 16;
}
return dbValue;
} else {
// 按原来规则走
return dbValue;
}
复制代码
如此类推,下一轮总共8个节点时,每次迁移只须要迁移1/8。
其实也能够在第一个迭代时,不选择dbValue小于16号的来作。直接8个分一组,只选择dbValue<8的来作,这样第一个迭代的影响范围也会比较案例中小。上述案例用16只是比较好演示
优势:
缺点:
如同上述方案五是方案四+方案一,能够达到逐步迁移数据,还有一种方案。就是方案四+方案三,只是不用取模后分组。
userId % 64 / count * count
由于上述公式,得出结果中,不必定每一片数据都是平均分布的。其实咱们能够取模后,按范围划分分片,以下公式。
第一片 0<userId % 64<15
第二片 16<userId % 64<31
第三片 32<userId % 64<47
第四片 48<userId % 64<63
复制代码
固然范围能够自定义,看取模后落入哪一个值的数量比较多,就切某一片数据就行了,具体就不画图了,跟方案四相似。
由于迁移数据的缘由,方案四中,若是数据量大,达到1000万行记录,每次迁移都须要迁移不少的数据,因此不少公司会尽早分库分表。
可是在业务优先状况下,一直迭代业务,数据一进达到不少的状况下16分支一也是不少的数据时,咱们就能够用一致性Hash理念--按范围分库
个人公众号 :地藏思惟
掘金:地藏Kelvin
简书:地藏Kelvin
CSDN:地藏Kelvin
个人Gitee: 地藏Kelvin gitee.com/dizang-kelv…