用户收藏信息的表结构设计
收藏记录表:维护用户和收藏信息之间的关系
字段名 | 数据类型 | 长度 | 主键 | 描述 |
---|---|---|---|---|
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
哈希取模代码见下:
代码语言:javascript复制// 分表数量
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,这样在路由时就会出现找不到的情况。
如果出现这种情况的话,我们需要进行数据迁移,假如原有
扩容前/表序号 | 0 | 1 | 2 |
---|---|---|---|
uid | 0 | 1 | 2 |
uid | 3 | 4 | 5 |
uid | 6 | 7 | 8 |
uid | 9 | 10 | 11 |
扩容后/表序号 | 0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|
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服务器,此时都没有意义了。 因此,我们必须换一种思路,对数据库进行拆分,从而提高数据库写入能力,这就是所谓的分库。
场景分析
用户触发收藏操作不是高并发行为,暂时不考虑分库。