原文地址:http://jm-blog.aliapp.com/?p=590mysql
目前绝大多数应用采起的两种分库分表规则算法
这两种方式有个本质的特色,就是离散性加周期性。sql
例如以一个表的主键对3取余数的方式分库或分表:数据库
那么随着数据量的增大,每一个表或库的数据量都是各自增加。当一个表或库的数据量增加到了一个极限,要加库或加表的时候,
介于这种分库分表算法的离散性,必须要作数据迁移才能完成。例如从3个扩展到5个的时候:app
须要将原先以mod3分类的数据,从新以mod5分类,不可避免的带来数据迁移。每一个表的数据都要被从新分配到多个新的表
类似的例子好比从dayofweek分的7个库/表,要扩张为以dayofmonth分的31张库/表,一样须要进行数据迁移。函数
数据迁移带来的问题是工具
如何在数据量扩张到现有库表极限,加库加表时避免数据迁移呢?
一般的数据增加每每是随着时间的推移增加的。随着业务的开展,时间的推移,数据量不断增长。(不随着时间增加的状况,
例如某天忽然须要从另外一个系统导入大量数据,这种状况彻底能够由dba依据现有的分库分表规则来导入,所以不考虑这种问题。)性能
考虑到数据增加的特色,若是咱们以表明时间增加的字段,按递增的范围分库,则能够避免数据迁移
例如,若是id是随着时间推移而增加的全局sequence,则能够以id的范围来分库:(全局sequence能够用tddl如今的方式也能够用ZooKeeper实现)
id在 0–100万在第一个库中,100-200万在第二个中,200-300万在第3个中 (用M表明百万数据)spa
或者以时间字段为例,好比一个字段表示记录的建立时间,以此字段的时间段分库gmt_create_time in range设计
这样的方式下,在数据量再增长达到前几个库/表的上限时,则继续水平增长库表,原先的数据就不须要迁移了
可是这样的方式会带来一个热点问题:当前的数据量达到某个库表的范围时,全部的插入操做,都集中在这个库/表了。
因此在知足基本业务功能的前提下,分库分表方案应该尽可能避免的两个问题:
1. 数据迁移
2. 热点
如何既能避免数据迁移又能避免插入更新的热点问题呢?
结合离散分库/分表和连续分库/分表的优势,若是必定要写热点和新数据均匀分配在每一个库,同时又保证易于水平扩展,能够考虑这样的模式:
【水平扩展scale-out方案模式一】
阶段一:一个库DB0以内分4个表,id%4 :
阶段二:增长db1库,t2和t3整表搬迁到db1
阶段三:增长DB2和DB3库,t1整表搬迁到DB2,t3整表搬迁的DB3:
为了规则表达,经过内部名称映射或其余方式,咱们将DB1和DB2的名称和位置互换获得下图:
dbRule: “DB” + (id % 4)
tbRule: “t” + (id % 4)
这样3个阶段的扩展方案中,每次次扩容只须要作一次停机发布,不须要作数据迁移。停机发布中只须要作整表搬迁。
这个相对于每一个表中的数据从新分配来讲,不论是开发作,仍是DBA作都会简单不少。
若是更进一步数据库的设计和部署上能作到每一个表一个硬盘,那么扩容的过程只要把原有机器的某一块硬盘拔下来,
插入到新的机器上,就完成整表搬迁了!能够大大缩短停机时间。
具体在mysql上能够以库为表。开始一个物理机上启动4个数据库实例,每次倍增机器,直接将库搬迁到新的机器上。
这样从始至终规则都不须要变化,一直都是:
dbRule: “DB” + (id % 4)
tbRule: “t” + (id % 4)
即逻辑上始终保持4库4表,每一个表一个库。这种作法也是目前店铺线图片空间采用的作法。
上述方案有一个缺点,就是在从一个库到4个库的过程当中,单表的数据量一直在增加。当单表的数据量超过必定范围时,可能会带来性能问题。好比索引的问题,历史数据清理的问题。
另外当开始预留的表个数用尽,到了4物理库每库1个表的阶段,再进行扩容的话,不可避免的要从表上下手。那么咱们来考虑表内数据上限不增加的方案:
【水平扩展scale-out方案模式二】
阶段一:一个数据库,两个表,rule0 = id % 2
分库规则dbRule: “DB0″
分表规则tbRule: “t” + (id % 2)
阶段二:当单库的数据量接近1千万,单表的数据量接近500万时,进行扩容(数据量只是举例,具体扩容量要根据数据库和实际压力情况决定):
增长一个数据库DB1,将DB0.t1整表迁移到新库DB1。
每一个库各增长1个表,将来10M-20M的数据mod2分别写入这2个表:t0_1,t1_1:
分库规则dbRule:
“DB” + (id % 2)
分表规则tbRule:
if(id < 1千万){
return "t"+ (id % 2); //1千万以前的数据,仍然放在t0和t1表。t1表从DB0搬迁到DB1库
}else if(id < 2千万){
return "t"+ (id % 2) +"_1"; //1千万以后的数据,各放到两个库的两个表中: t0_1,t1_1
}else{
throw new IllegalArgumentException("id outof range[20000000]:" + id);
}
这样10M之后的新生数据会均匀分布在DB0和DB1; 插入更新和查询热点仍然可以在每一个库中均匀分布。
每一个库中同时有老数据和不断增加的新数据。每表的数据仍然控制在500万如下。
阶段三:当两个库的容量接近上限继续水平扩展时,进行以下操做:
新增长两个库:DB2和DB3. 以id % 4分库。余数0、一、二、3分别对应DB的下标. t0和t1不变,
将DB0.t0_1整表迁移到DB2; 将DB1.t1_1整表迁移到DB3
20M-40M的数据mod4分为4个表:t0_2,t1_2,t2_2,t3_2,分别放到4个库中:
新的分库分表规则以下:
分库规则dbRule:
if(id < 2千万){
//2千万以前的数据,4个表分别放到4个库
if(id < 1千万){
return "db"+ (id % 2); //原t0表仍在db0, t1表仍在db1
}else{
return "db"+ ((id % 2) +2); //原t0_1表从db0搬迁到db2; t1_1表从db1搬迁到db3
}
}else if(id < 4千万){
return "db"+ (id % 4); //超过2千万的数据,平均分到4个库
}else{
throw new IllegalArgumentException("id out of range. id:"+id);
}
分表规则tbRule:
if(id < 2千万){ //2千万以前的数据,表规则和原先彻底同样,参见阶段二
if(id < 1千万){
return "t"+ (id % 2); //1千万以前的数据,仍然放在t0和t1表
}else{
return "t"+ (id % 2) +"_1"; //1千万以后的数据,仍然放在t0_1和t1_1表
}
}else if(id < 4千万){
return "t"+ (id % 4)+"_2"; //超过2千万的数据分为4个表t0_2,t1_2,t2_2,t3_2
}else{
throw new IllegalArgumentException("id out of range. id:"+id);
}
随着时间的推移,当第一阶段的t0/t1,第二阶段的t0_1/t1_1逐渐成为历史数据,再也不使用时,能够直接truncate掉整个表。省去了历史数据迁移的麻烦。
上述3个阶段的分库分表规则在TDDL2.x中已经所有支持,具体请咨询TDDL团队。
【水平扩展scale-out方案模式三】
非倍数扩展:若是从上文的阶段二到阶段三不但愿一下增长两个库呢?尝试以下方案:
迁移前:
新增库为DB2,t0、t1都放在DB0,
t0_1整表迁移到DB1
t1_1整表迁移到DB2
迁移后:
这时DB0退化为旧数据的读库和更新库。新增数据的热点均匀分布在DB1和DB2
4没法整除3,所以若是从4表2库扩展到3个库,不作行级别的迁移而又保证热点均匀分布看似没法完成。
固然若是不限制每库只有两个表,也能够以下实现:
小于10M的t0和t1都放到DB0,以mod2分为两个表,原数据不变
10M-20M的,以mod2分为两个表t0_一、t1_1,原数据不变,分别搬迁到DB1,和DB2
20M以上的以mod3平均分配到3个DB库的t_0、t_二、t_3表中
这样DB1包含最老的两个表,和最新的1/3数据。DB1和DB2都分表包含次新的两个旧表t0_一、t1_1和最新的1/3数据。
新旧数据读写均可达到均匀分布。
总而言之:
两种规则映射(函数):
离散映射和连续映射这两种相辅相成的映射规则,正好解决热点和迁移这一对相互矛盾的问题。
咱们以前只运用了离散映射,引入连续映射规则后,二者结合,精心设计,
应该能够设计出知足避免热点和减小迁移之间任意权衡取舍的规则。
基于以上考量,分库分表规则的设计和配置,长远说来必须知足如下要求