一次数据库分表分库的真实场景应用

用户收藏信息的表结构设计

收藏记录表:维护用户和收藏信息之间的关系

字段名数据类型长度主键描述
id varchar 36 Yes UUID
user_id varchar 32 No 用户Id
resource_id bigint 20 No 收藏资源Id
resource_name varchar 512 No 收藏资源名称
author varchar 512 No 资源创作者
album varchar 512 No 资源所属专辑
resource_type varchar 10 No 收藏资源类型 1:歌曲 2:节目 3:广播
  • 预期业务量可达百万用户。
  • 平均每人收藏百首歌曲:一百万用户*每人收藏一百首歌=一亿条收藏信息。
  • 根据使用场景分析,用户收藏属于频繁操作的场景,需要频繁的读和写数据库。
  • 按照上面的表结构设计,单表存储上亿条记录,且面临着大量的读写操作。
  • 要解决上面的这个问题,思考进行数据库分表。

分表

场景

数据库一般采用Master-Slave复制模式的MySQL架构,只能够对数据库的读进行扩展,而对数据库的写入操作还是集中在Master上,并且单个Master挂载的Slave也不可能无限制多,Slave的数量受到Master能力和负载的限制。

因此,需要对数据库的吞吐能力进行进一步的扩展,以满足高并发访问与海量数据存储的需要。

前提

  • 很大的数据量
  • 数据量随着[时间/业务规模/场景操作频繁次数]会持续高速增长

目的:

  • 对于访问极为频繁且数据量巨大的单表来说,我们首先要做的就是减少单表的记录条数,以便减少数据查询所需要的时间,提高数据库的吞吐。
  • 在分表之前,首先需要选择适当的分表策略,使得数据能够较为均衡地分不到多张表中,并且不影响正常的查询

分表策略

为了使数据能够较为均衡地分不到多张表中,并且不影响正常的查询,我们需要制定合适的分表策略。

  • 收藏信息是和用户绑定在一起的,是用户的一种行为,无论收藏、取消收藏、及获取收藏信息都需要告知具体是哪个用户操作的。
  • 对此可以利用用户ID制定分表策略,这样既不影响查询,又能够使数据较为均衡地分布到各个表中。
  • user_id是有自己的生成规则的,不是自增长生成的且不能保证以后会不会出现字母,直接取模的话,怕是不能达到均衡分表的目标。
  • 对uid进行hash操作,后在进行取模操作

按百万用户划分、每张表存储一万个用户的收藏信息,即需要划分出来128张表。

  • 分表策略:hash(user_id) % 128 = table_id
  • 路由规则:table_name + table_id = collect_0…collect_127

如果业务场景会随着时间规律增长,可考虑使用按时间日期分表,如每天的员工的打卡记录,假设某公司有20W员工每人每天打卡两次,即:

  • 一个月的打卡记录 = 20W*2*30 = 1200W 条记录
  • 一年内的打卡记录 = 1200W*12 = 14400W 条记录

那么我们就可以考虑按照日期进行分表存储。
如:work_record_20180620…work_record_20181231

哈希取模代码见下:

// 分表数量
private static final int SUB_TABLE_NUM = 128;
public static final int uidConvertSubNum(String uid) {
int h;
// 将不规则的uid转为'随机、无规则'的大数
int hashUid = (uid == null) ? 0 : (h = uid.hashCode()) ^ (h >>> 16);
// 将散列值与长度做与运算得到下标值,等同于取模运算
int subNum = hashUid & (SUB_TABLE_NUM - 1);
return subNum;
}

在分表前请注意!

  • 对于取模得到表序号的路由算法,需要在编码时提前确认分表数量,即根据业务量推算出要分多少表,如128张表。
  • 后期如果要改变分表数量(一般是扩容增表)这样的话,同一个uid算出来的表序号可能会不一致!

即uid为1230521,分表数为128时算出来的表序号为0,当分表数量增大为256时,算出来的表序号为7,这样在路由时就会出现找不到的情况。

如果出现这种情况的话,我们需要进行数据迁移,假如原有

扩容前/表序号012
uid 0 1 2
uid 3 4 5
uid 6 7 8
uid 9 10 11

 

扩容后/表序号012345
uid 0 1 2 3 4 5
uid 6 7 8 9 10 11

 

  • 即:3、4、5、9、10、11六个数字迁移到新的表当中去。
  • 如果新增加一倍的表,那么需要将原有的每张表中的一半数据进行移动。
  • 随着时间的增长,业务规模会不断的增大,到之后可能分表也会数据量很庞大,那么我们就会分更多的表,如果前期对于业务量和业务规模没有很好的预期的话,那么到后期就不可避免的要进行数据迁移。

需要解决的问题

主键冲突问题

分表之后,数据被分配到不同的表中(类似于分片),不能再借助数据库自增长特性直接生成,否则会造成不同分片上的数据表主键会重复。

  • 通过给主键字段设置为自增序列显然不可取。
  • 使用Mysql自带的UUID()函数,达到分表后主键不重复的效果。

分库

目的

分表的实质还是在一个数据库上进行的操作,很容易受数据库IO性能的限制。
无法给数据库的并发处理能力带来质的提升。面对高并发的读写访问,当数据库master服务器无法承载写操作压力时,不管如何扩展slave服务器,此时都没有意义了。
因此,我们必须换一种思路,对数据库进行拆分,从而提高数据库写入能力,这就是所谓的分库。

场景分析

用户触发收藏操作不是高并发行为,暂时不考虑分库。

posted @ 2018-06-22 06:51  翎野君  阅读(459)  评论(0编辑  收藏  举报