帖子中心是互联网业务中典型的“一对多”业务,即一个用户可以发布多个帖子,一个帖子只有一个发布者。随着数据量和并发量的逐渐增加,如何设计邮寄中心“一对多”业务的架构,需要考虑哪些因素,是本文将要系统探讨的问题文章。x到x是什么?所谓“1对1”、“1对多”、“多对多”来源于数据库设计中的“实体-关系”ER模型,用来描述实体之间的映射关系。什么是“1对1”业务?在用户中心,一个用户只有一个登录名,一个登录名对应一个用户。这是典型的一对一业务。什么是“一对多”服务?在发帖中心,一个用户可以发多条微博,每条微博只有一个发件人。这是典型的一对多服务。什么是“多对多”业务?Feedattention,一个用户可以关注多个用户,一个用户也可以被多个用户关注。这是典型的多对多业务。邮政中心是一个什么样的业务,典型的业务需求是什么?邮政中心是典型的一对多业务。一个用户可以发布多个帖子,一个帖子只对应一个发布者。任何倒闭的建筑设计都是耍流氓。我们先来看一下邮政中心对应的业务需求。帖子中心是提供帖子发布、修改、删除、查看和搜索的服务。发帖中心,什么写操作?发布(插入)帖子;修改(更新)帖子;删除(删除)帖子;发帖中心,什么读操作?通过tid查询(select)post实体,单行查询;querybyuid(select)用户发布的帖子,列表查询;帖子检索(搜索),如按时间、标题、内容搜索符合条件的帖子;数据量大,并发量大的时候,架构怎么设计?通常,通常通过将元数据与索引数据分开的架构设计方法。架构中的几个关键点,如上图所示:tiezi-center:post服务;tiezi-db:提供元数据存储;tiezi-search:贴子搜索服务;tiezi-index:提供索引数据存储;MQ:铁子-中心和铁子-搜索的通信媒介一般不直接使用RPC调用,而是通过MQ将两个子系统解耦。这时候,如何满足阅读要求呢?贴子中心和贴子搜索分别满足两种不同类型的阅读需求。如上图:tid和uid的查询需求可以通过tiezi-center从metadata中读取返回;其他类型的检索需求可以通过铁子搜索从索引数据中检索返回;写作要求呢?编写需求,如上图所示:新增、修改、删除操作都会从tiezi-center发起;tiezi-center修改元数据;tiezi-center向MQ发送信息修改通知;tiezi-search接受来自MQ的修改信息;tiezi-search修改索引数据;tiezi-search,搜索架构不是本文的重点,不再展开。后文中心元数据水平切分设计将在后续文章中介绍。邮政中心,如何设计数据库元数据?发布中心业务,很容易理解,它的核心元数据是:t_tiezi(tid,uid,time,title,content,…);其中:tid为postID,主键;uid是用户ID,poster;时间、标题、内容等为帖子属性;在数据库设计方面,在业务初期,单一数据库即可满足元数据存储需求。tiezi-center:邮寄中心服务,为调用者提供友好的RPC接口;tiezi-db:存储post数据;索引相关字段可以满足相关业务需求。post记录查询,通过tid查询,约占读请求的90%;select*fromt_tieziwheretid=$tidpostlist查询,通过uid查询其发布的所有posts,占read请求的10%左右;select*fromt_tieziwhereuid=$uid和随着数据量越来越大,如何进行水平切分,线性扩展存储容量?方案一:由于帖子ID切分方式是以帖子为中心,帖子记录查询量占总请求量的90%,所以很容易想到水平切分,对tid字段取模。这种方法简单直接,优点:100%的写请求可以直接定位到库;90%的读请求可以直接定位到图书馆;缺点也很明显:一个用户发布的所有帖子可能落在不同的库上,10%通过uid查询请求会比较麻烦;如上图,一个uid访问需要遍历所有的库。有没有切分的方法保证同一个用户发布的所有帖子都落在同一个库中,查询一个用户发布的所有帖子时,不需要遍历所有的库?方案二:用户ID切切分的方法是用uid切分数据库来解决这个问题。一个新的问题出现了:如果使用uid来划分数据库,保证一个用户的post数据落在同一个数据库,那么如果通过tid查询,是不知道该post落在哪个数据库的,所以还是需要遍历如何优化整个数据库?tid查询是单行记录查询。只要在数据库(或缓存)中记录tid和uid的映射关系,就可以解决这个问题。添加索引库:t_mapping(tid,uid);这个图书馆只有两列,可以承载很多数据;即使数据量太大,索引库也可以使用tid水平切分;这种kv形式的索引结构可以很好的使用缓存来优化查询性能;帖子一旦发布,tid和uid的映射关系不会改变,缓存的命中率会很高;使用uid分库,增加索引库记录tid和uid的映射关系,每次有查询uid的时候,可以直接通过uid定位库。每当有对tid的查询时,可以先查映射表得到uid,然后通过uid定位库。这种方式的优点是:一个用户发表的帖子落在同一个库中;10%的请求通过uid查询列表,可以直接定位到库中;索引表缓存命中率很高,因为tid和uid之间的映射关系不会改变;缺点也很明显:90%的tid请求和100%的修改请求不能直接定位到库中,需要先查询索引表。当然,这个查询是非常块状的,通常可以在5ms以内返回;插入数据时需要对元数据和索引表进行操作,这可能会导致潜在的一致性问题;tid可以一次性直接命中的方案呢?方案三:基因法(1)什么是分库基因?通过uid分库,假设分为16个库,使用uid%16的方法进行数据库路由。这里,uid%16,其实质就是uid的后4位决定了这行数据落在哪个bank上。这4位是子库基因。(2)什么是基因方法子库?在“一对多”业务场景下,使用“1”分库。当生成“多”个数据id时,在id末尾添加分库基因,满足同时满足“1”个分库。”和“多”分库查询需求。如上图,uid=666的用户发布了一个帖子(666的二进制表示为:1010011010):使用uid%16分库来决定要查询哪个数据库将这行数据插入;分库基因为uid的后4位,即1010;生成tid时,先使用分布式ID生成算法生成前60位(上图中绿色部分);将分库基因添加到tid的后4位(上图粉色部分);组装成最终的64bitposttid(上图蓝色部分);这样就保证了同一个用户发布的所有帖子的tids都落在同一个library上,tid的后4位相同,所以:library可以通过uid%16定位;也可以通过tid%16定位library;有人要问,同一个uid发布的tid落在同一个库上会不会出现数据不平衡的情况?只要uid为balanc即可ed是的,平均每个用户发表的帖子数是均衡的,各个库的数据也是均衡的。又得有人问了。一开始将16个pool分成16个pool,sub-bank基因为4位。未来会扩容到32个pool,子库基因变成5bits。256个数据库足够一年中的数据增长,所以提前预留了8bit的基因。用“邮寄中心”来概括典型的“一对多”业务。在架构上,采用了元数据和索引数据分离的架构设计方法:post服务,元数据满足uid和tid的查询需求;搜索服务,索引数据满足复杂的搜索需求;对于元数据存储,在数据量大的情况下,常见的分词方式有3种:tid分词法,根据tid分词库,将同一用户发布的帖子分到不同的库上,通过uid查询遍历所有图书馆;uid切分法,按照uid分库,同一个用户发表的帖子落在同一个库,需要通过索引表或者缓存记录tid和uid的映射关系,通过tid查询时,先找到uid,然后通过uid定位库;遗传方式,根据uid分库,将uid上的分库基因添加到生成的tid中,保证可以通过uid和tid直接定位库;【本文为专栏作者原创稿件】58神剑》,转载请联系原作者】点此查看该作者更多好文
