一,用户中心,以用户数据为例

User(uid, login_name, passwd, sex, age, nickname, …)
其中uid为主键id,其它字段为用户属性

此方案架构在业务初期单表单库能够搞定,但是随着业务量的迅速增长,数据量越来越大时,这时候就需要对数据库进行水平拆分了,常见的水平切分算法有
“范围法”和“哈希法”。
1,范围发:以用户的uid主键为范围规则划分
•user-db0:存储0到1千万的uid数据
•user-db1:存储1到2千万的uid数据
范围法的优点:
•切分简单,根据uid,按照范围,很快能够定位到数据在哪个库上
•扩容简单,如果容量不够,只要增加user-db2即可
范围法的不足:
•uid必须要满足递增的特性
•数据量不均,新增的user-db2,在初期的数据会比较少
•请求量不均,一般来说,新注册的用户活跃度会比较高,故user-db1往往会比user-db0负载要高,导致服务器利用率不平衡

2,哈希法:以用户的uid进行划分
•user-db0:uid %2=0 的数据保存在db0库上
•user-db1:uid %2=1 的数据保存在db1库上
哈希法的优点:
•切分策略简单,根据uid取模,根据取模结果很快能够定位到数据在哪个库上
•数据量均衡,只要uid是均匀的,数据在各个库上的分布一定是均衡的
•请求量均衡,只要uid是均匀的,负载在各个库上的分布一定是均衡的
哈希法的不足:
•扩容麻烦,如果容量不够,要增加一个库,重新hash可能会导致数据迁移,如何平滑的进行数据迁移,是一个需要解决的问题(这个以后有时间在进行说明)

二,用户数据水平拆分后的问题

用户数据通过水平拆分后,会带来什么问题呢?
1,对于uid的属性查询,可以直接用过uid取模路由找到存储的库,如:uid=12的数据,可以直接取模定位到数据在user-db0库上面,
对于非uid的数据查询,比如现在要通过login_name来进行登陆,这个时候怎么定位数据存储的库呢?(此问题哈希法和范围法同时存在)
根据楼主这些年的开发经验,用户中心业务需求通常有以下两类:
用户登录:通过login_name/phone/email查询用户的实体,1%请求属于这种类型
用户信息查询:登录之后,通过uid来查询用户的实例,99%请求属这种类型
2,新增用户时由于新用户没有uid,这个时候怎么确定新用户应该路由到哪个库呢?

三,实现思路

对于非uid查询用户信息的实现思路:
1,通过建立login_name与uid
的映射关系来快速定位到相应的库,这样表的数据相对较少,可以保存很多的数据,不足之处在于需要先查询到uid所属的库然后在查询用户数据,速度相对较慢
2,缓存映射法,将login_name与uid保存在内存中,如redis用login_name做key uid当做val,这样只需要在redis中通过
login_name查询出uid然后取模即可对应到相应的库,数据一旦保存就不再需要增删改,速度相对较快,不足之处在于需要多一次redis查询并且比较消耗内存
3,uid通过一定的规则来生成,里面融入login_name
元素,下次通过login_name登录时只需要再次通过此规则来生成id然后取模就可以定位到数据库了,不足之处在于算法需要精心的设计并且有uid冲突的风险

对于新增用户,新用户没有uid的数据路由实现思路

1,单独的表,表进行id自增,写一个单独的服务接口,每次新增用户数据时先在这张表里面新增一条数据然后返回最新的自增id,把id设置给uid,这样就可以通过uid取模路由到相应的库了(表的历史自增数据可以进行定期清理)。不足之处在于每次都要访问这张表,会有性能瓶颈。 

2,单独的表,表里面保存最大的maxId,每次新增或者服务初始化的时候首先获取表里面的maxId,获取maxId之后在maxId的基础上加上1000-2000(具体多少按照数据量大小来定),然后把这些id通过循环写入内存(比如redis),然后再把加上的数量+maxId重新写入到maxId,下次没有id或者快要用完的时候再拉取下一批id再次写入(需要考虑重复写入问题,比如分布式同一时刻用完一起拉取数据并写入数据问题)。不足之处在于如果服务重启了拉取的id如果没有消耗完会导致id空洞问题,但是比第一个方案效率更高


关于Id有几个注意的地方,如果你的Id有可能出现在页面上, 比如订单Id, 用户Id
,尽量不要使用自增的,因为别人可以根据这些Id,看出你现在的用户规模,订单量。通过在两天中相同时间下单,可以看出你的交易量,如果权限没有控制好,通过遍历可以查出所有的信息。
 

我们这里以用户表为例,看把用户信息写入数据库的一些方法,以及这些方法的一些问题。 

四,总结

1,数据库水平切分的两种方案

* 范围法
* 哈希法
2,数据库水平切分后遇到的问题

* 用uid进行的操作可以定位到数据库,但是通过login_name进行登录这种类型的需求无法直接定位到数据库
3,通过非uid进行操作数据的解决方案

* 建立login_name与uid的映射关系表
* 将login_name与uid之前的映射关系存放在缓存中,如redis
* uid通过一定的规则来生成,里面融入login_name元素
* 新增用户,新用户没有uid的路由保存实现方案