用户中心是几乎每一个公司必备的基础服务,用户注册、登录、信息查询与修改都离不开用户中心。
当数据量越来越大时,需要多用户中心进行水平切分。最常见的水平切分方式,按照userId取模分库:
例如:
通过userId取模,将数据分布到多个数据库实例上去,提高服务实例个数,降低单库数据量,以达到扩容的目的。
这样水平切分之后,userId属性上的查询可以直接路由到库,如上图,假设访问uid=10的数据,取模后能够直接定位db1。
但是分库之后,对于其他字段的查询,就不能这么幸运了。假设访问userName=”lizhi”的数据,由于不知道数据落在哪个库上,往往需要遍历所有库,当分库数量多起来,性能会显著降低。
所以我要们寻找如何高效查询的方法!(以下用userName为例)
索引表法
思路:userId直接定位到库,userName不能直接定位到库,如果通过userName能查询到userId,问题解决。
解决方案:
1)建立一个索引表记录userName->userId的映射关系
2)用userName来访问时,先通过索引表查询到userId,再定位相应的库
3)索引表属性较少,可以容纳非常多数据,一般不需要分库
4)如果数据量过大,可以通过userName来分库
潜在不足:多一次数据库查询,性能下降一倍。
缓存映射法
思路:访问索引表性能较低,把映射关系放在缓存里性能更佳。
解决方案:
1)userName查询先到cache中查询userId,再根据userId定位数据库
2)假设cache miss,采用扫全库法获取userName对应的userId,放入cache
3)userName到userId的映射关系不会变化,映射关系一旦放入缓存,不会更改,无需淘汰,缓存命中率超高
4)如果数据量过大,可以通过userName进行cache水平切分
潜在不足:多一次cache查询
userName生成userId
思路:不进行远程查询,由userName直接得到userId
解决方案:
1)在用户注册时,设计函数userName生成userId,userId=f(userName),按userId分库插入数据
2)用userName来访问时,先通过函数计算出userId,即userId=f(userName)再来一遍,由userId路由到对应库
潜在不足:该函数设计需要非常讲究技巧,有userId生成冲突风险
userName基因融入userId
思路:不能用userName生成userId,可以从userName抽取“基因”,融入userId中
假设分8库,采用userId%8路由,潜台词是,userId的最后3个bit决定这条数据落在哪个库上,这3个bit就是所谓的“基因”。
解决方案:
1)在用户注册时,设计函数userName生成3bit基因,userName_gene=f(userName)
2)同时,生成61bit的全局唯一id,作为用户的标识
3)接着把3bit的userName_gene也作为userId的一部分
4)生成64bit的userId,由id和userName_gene拼装而成,并按照userId库插入数据
5)用userName来访问时,先通过函数由userName再次复原3bit基因,userName_gene=f(userName),通过userName_gene%8直接定位到库