继续回答星球水友的问题,大数据量,高并发,朋友关系链和粉丝关系链怎么设计?什么是关系链业务?关系链主要分为弱朋友关系和强朋友关系两种类型,两种类型都有典型的互联网产品应用。弱好友关系的建立不需要双方同意:用户A在未经用户B同意的情况下关注了用户B,此时用户A和用户B处于弱好友关系。对于A,暂时理解为“跟随”;用户B关注用户A不需要用户A的同意。此时用户A和用户B也是弱好友。对于A,暂时理解为“粉丝”;偶像与粉丝的关系是典型的微博粉丝关系链。的弱友谊应用。建立牢固的友谊需要双方同意:用户A请求加用户B为好友,用户B同意。此时用户A和用户B是互为强好友,即A是B的好友,B也是A的好友;QQ好友关系链,是一个典型的强好友关系应用。朋友中心是典型的多对多业务:一个用户可以添加多个好友,也可以被多个好友添加。其典型架构为:friend-service:好友中心服务,为调用者提供友好的RPC接口db:forfriends存储层应该如何实现弱友情关系进行数据存储?通过弱友情的业务分析,不难理解其核心元数据为:guanzhu(uid,guanzhu_uid);fensi(uid,fensi_uid);其中:guanzhu表,用户记录uid和所有关注用户guanzhu_uidfensi表,用于记录所有uid粉丝用户fensi_uid需要强调的是,当产生弱关系时,会产生两条记录,一条关注记录,一条粉丝记录。例如:用户A(uid=1)关注了用户B(uid=2),A又关注了一个用户,B又多了一个关注者,所以:guanzhu表需要插入记录{1,2},1已经跟随2记录{2,1}需要插入到fensi表中。2fans1如何查询用户关注了谁?答:在guanzhu的uid上创建索引:select*fromguanzhuwhereuid=1;得到结果,1hasfollowed2.如何查看用户关注了谁?答:在fensi的uid上创建索引:select*fromfensiwhereuid=2;得到结果,2个追随者1。强友情,存储层应该如何实现?方案一通过强友情的业务分析,很容易理解其核心元数据为:friend(uid1,uid2);其中:强友谊中一方的uid1,uiduid2,强友谊中,对方uiduid=1的用户添加uid=2的用户,双方同意加对方为好友。对于这个深厚的友谊,应该将记录{1,2}还是记录{2,1}插入数据库呢?答:两者都可以。为了避免歧义,可以人为约定插入记录时uid1的值必须小于uid2。例如:uid=1、2、3的用户有3个,互为强友。那边的数据库里可能有这样的三条记录:{1,2}{2,3}{1,3}如何查询一个用户朋友呢?答:假设要查询所有uid=2的好友,只需要在uid1和uid2上建立索引,然后:select*fromfriendwhereuid1=2unionselect*fromfriendwhereuid2=2即可得到结果。解2强朋友关系是弱朋友关系的特例。A和B必须互相关注(也可以说同时互为对方的粉丝),即也可以用follow表和粉丝表来实现:guanzhu(uid,guanzhu_uid);fensi(uid,fensi_uid);例如:用户A(uid=1)和用户B(uid=2)是强好友,即相互关注:用户A(uid=1)关注用户B(uid=2)),A关注一个用户多了,B多了一个粉丝,所以:guanzhu表需要插入{1,2}记录,fensi表需要插入{2,1}记录同时,用户B(uid=2)也关注了userA(uid=1),B多关注了一个用户,A多了一个粉丝,所以:guanzhu表需要插入{2,1}记录,fensi表需要插入{1,2}记录有什么好处和两种实现方式各自的缺点?强好友关系的两种实现方式:friend(uid1,uid2)表数据冗余guanzhu表和fensi表(以下简称正表T1和负表T2)数据量小的时候,貌似没有区别,但是当数据量大的时候,数据冗余的优势就体现出来了:friend表,当数据量大的时候,如果使用uid1分库,那么在uid2上的查询就需要遍历多库正表T1对表T2通过数据冗余实现好友关系。{1,2}{2,1}分别存在于两张表中,所以两张表都使用uid划分数据库,只需要一次查询就可以找到对应的关注和粉丝,无需扫描多个数据库画外音:如果有10亿条关系链,必须横向切分。数据冗余是多对多的关系。当数据量很大时,通常的做法是水平拆分数据。如何实现数据冗余?接下来的问题是如何在朋友中心服务中实现数据冗余。常用的方法有以下三种。方法一:服务同步冗余顾名思义,朋友中心服务同步写入冗余数据,如上图1-4所示:业务方调用该服务,新数据服务先插入到T1数据服务中并然后插入到T2数据服务中,返回业务方添加数据成功优点:不复杂,服务层由一次写入变为两次写入,数据一致性比较高(只返回双写成功)。缺点:请求的处理时间增加(需要插入两次,时间翻倍),数据仍然可能不一致。比如第二步写T1完成,服务重启后,数据就不会写到T2了。如果系统对处理时间比较敏感,则介绍第二种常用方案。方式二:服务异步冗余数据的双写不再由朋友中心服务完成。服务层异步发送消息,通过消息总线发送到专门的数据复制服务写入冗余数据,如图1-上图6流程:业务方调用服务,新数据服务先插入T1数据服务向消息总线发送异步消息(直接发送出去即可,不需要等待返回,一般很快就会完成)服务返回业务方添加数据成功。总线发送消息Post到数据同步中心数据同步中心插入T2数据优点:请求处理时间短(只插入一次)缺点:系统复杂度增加,需要额外的组件(消息总线)和服务(专用datareplicationservice)被引入)因为当返回的业务线数据成功插入时,数据不一定插入到T2,所以数据有一个不一致的时间窗口(这个窗口很短,最终是一致的)当消息总线丢失消息,冗余表数据会不一致如果要解除“数据冗余”对系统的耦合,介绍第三种常用方案。方式三:离线异步冗余数据的双写不再由朋友中心服务完成,而是由离线服务或任务完成,如上图1-6所示:业务方调用服务,新增数据服务先插入T1数据服务,将新添加的数据返回给业务端。成功的数据会被写入数据库的日志中。离线服务或任务读取数据库的日志。将T2数据插入离线服务或任务。优点:数据双写与业务完全解耦请求处理时间短(只插入一次)缺点:当返回的业务线数据成功插入时,数据可能没有插入到T2,因此数据存在时间窗口不一致(这个窗口很短,最终一致)数据的一致性取决于离线服务或任务的可靠性以上三种方案各有优缺点,可以根据实际情况选择。数据冗余固然可以解决多对多关系的数据库水平切分问题,但也带来了新的问题。如何保证正表T1和负表T2的数据一致性?从上面的讨论可以看出,无论是哪种方案,由于两步操作不能保证原子性,总是存在数据不一致的可能。高吞吐量的分布式事务是业界尚未解决的问题。此时架构优化的方向:最终一致性。并不是要完全保证数据的实时一致性,而是尽早发现不一致,修复不一致。最终一致性是高吞吐量互联网服务一致性的常见做法。更具体地说,有三种通用的解决方案来确保数据的最终一致性。方法一:如上图离线扫描正负冗余表的所有数据,离线启动一个离线扫描工具,连续对比正负表T1和T2。如发现数据不一致,进行补偿修复。优点:相对简单,开发成本低在线服务无需修改,修复工具与在线服务解耦缺点:扫描效率低,会扫描大量“已经一致”的数据由于扫描量大数据,一轮扫描需要的时间比较长,即如果数据不一致,不一致的时间窗口比较长。有没有一种优化方法可以提高效率,只扫描“可能不一致”的数据,而不是每次都扫描所有数据?方法二:行扫描增量数据每次只扫描增量日志数据,可以大大提高效率,缩短数据不一致的时间窗口,如上图1-4所示流程:第一步写正后表T1成功,写入日志log1,写入反向表T2。第二步成功后写入日志log2。当然,我们还需要一个离线扫描工具来不断地比较loglog1和loglog2。如发现数据不一致,将进行补偿和修复。:虽然比方法一复杂,但还是比较简单的数据扫描,效率高,只扫描增量数据缺点:对线上服务稍作修改(成本不高,多写了2条日志)虽然比方法一实时性高,但是时效性还是不高,不一致窗口取决于扫描周期。有没有办法实时检测一致性并修复?方法三:实时在线“消息对”检测这次不是写入日志,而是向消息总线发送消息,如上图1-4流程所示:第一步写入后正表T1成功,发送消息msg1第二步写入负表T2成功后,发送消息msg2这次不是需要周期扫描的离线工具,而是实时订阅消息服务不断接收消息。假设在正常情况下,msg1和msg2的接收时间应该在3s以内。如果检测服务在收到msg1后没有收到msg2,它会尝试检测数据的一致性,并对不一致的地方进行补偿。优点:效率高,实时性高缺点:方案比较复杂。线上引入了消息总线组件,线下添加了订阅总线的检测服务。但技术方案本身就是一种投入产出比的折衷,具体采用哪一种可以根据业务对一致性的需求来决定。方法。总结(1)关系链业务是典型的多对多关系,分为强友和弱友(2)数据冗余是多对多业务数据横向切分的普遍做法(3)数据冗余常见的解决方案有三种:同步冗余服务异步冗余离线异步冗余(4)数据冗余会带来一致性问题,高吞吐量的互联网业务,很难完全保证事务的一致性,常见的做法是最终的一致性(5)最终一致性的普遍做法是尽快发现不一致并修复数据。常见的解决方案有三种:离线全量扫描正常离线增量扫描正常在线实时检测方法希望大家能给点启发,思路比结论重要。【本文为专栏作者《58神剑》原创稿件,转载请联系原作者】点此阅读更多该作者好文
