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