关系链--关注与粉丝功能 |青训营笔记

148 阅读4分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第3篇笔记

关系链--关注与粉丝功能

常规方案:

将关注关系存储到一张 follow_relation 表中:

CREATE TABLE `follow_rel` (
    `id` int NOT NULL AUTO_INCREMENT, 
    `user_id` int NOT NULL, 
    `target_id` int NOT NULL, 
    `create_time` bigint NOT NULL, PRIMARY KEY (`id`), KEY
    `idx_uid` (`user_id`), KEY `idx_tid` (`target_id`) 
    ) ENGINE=InnoDB;

1)查询 uid=1 的关注 : select * from follow_rel where user_id = 1 2)查询 uid=1 的粉丝 : select * from follow_rel where target_id = 1 3)查询 是否相互关注 : select * from follow_rel where user_id = 1 union select * from follow_rel where user_id =2

简单的看上去似乎还挺合理,确实,如果我们在用户关系数量不多的情况下,所有关系都维护到一张数据表中,此方案确实没有任何问题,但如果产品的数据有很多,达到上亿级别时,就会涉及到分库分表,而这个方案就会出现问题了,如果业务以用户 ID 进行拆分的话,对于上述粉丝查询的方法就无法实现了,因为 uid=1 的粉丝会因为其粉丝各自的 ID 路由到不同的数据表中,后面查询的时候就会不得不做多表扫描。

对于用冗余数据的简单理解:

一,为什么要冗余数据
​
互联网数据量很大的业务场景,往往数据库需要进行水平切分来降低单库数据量。
​
水平切分会有一个patition key,通过patition key的查询能够直接定位到库,但是非patition key上的查询可能就需要扫描多个库了。
​
此时常见的架构设计方案,是使用数据冗余这种反范式设计来满足分库后不同维度的查询需求。
​
例如:订单业务,对用户和商家都有订单查询需求:
​
Order(oid, info_detail);
​
T(buyer_id, seller_id, oid);
​
如果用buyer_id来分库,seller_id的查询就需要扫描多库。
​
如果用seller_id来分库,buyer_id的查询就需要扫描多库。
​
此时可以使用数据冗余来分别满足buyer_id和seller_id上的查询需求:
​
T1(buyer_id, seller_id, oid)
​
T2(seller_id, buyer_id, oid)
​
同一个数据,冗余两份,一份以buyer_id来分库,满足买家的查询需求;一份以seller_id来分库,满足卖家的查询需求。

改善方案:

从上面的解析中我们可以得到两个结论:在数据量大时①、可以保持高效查询的能力,②、方便今后的分库分表。

所以我们采用以下方案:创建两个表:关注表和粉丝表

1)关注表:

CREATE TABLE `follow_gz` (
  `id` int NOT NULL AUTO_INCREMENT,
  `user_id` int NOT NULL,
  `target_id` int NOT NULL,
  `create_time` bigint NOT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_uid` (`user_id`)
) ENGINE=InnoDB;

2)粉丝表:

CREATE TABLE `follow_fs` (
  `id` int NOT NULL AUTO_INCREMENT,
  `user_id` int NOT NULL,
  `target_id` int NOT NULL,
  `create_time` bigint NOT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_uid` (`user_id`)
) ENGINE=InnoDB;

OK,确定好之后,下面说说我们要做的和数据插入相关的操作,也就是做数据冗余。

数据冗余:

方法一:服务同步冗余

img

顾名思义,由好友中心服务同步写冗余数据,如上图1-4流程:

  • 业务方调用服务,新增数据
  • 服务先插入T1数据
  • 服务再插入T2数据
  • 服务返回业务方新增数据成功

优点:

  • 不复杂,服务层由单次写,变两次写
  • 数据一致性相对较高(因为双写成功才返回)

缺点:

  • 请求的处理时间增加(要插入次,时间加倍)
  • 数据仍可能不一致,例如第二步写入T1完成后服务重启,则数据不会写入T2

所以我不建议用这种方法,因为插入操作是两倍,当并发量大时,效率问题堪忧。

方法二:服务异步冗余

img

数据的双写并不再由好友中心服务来完成,服务层异步发出一个消息,通过消息总线发送给一个专门的数据复制服务来写入冗余数据,如上图1-6流程:

  • 业务方调用服务,新增数据
  • 服务先插入T1数据
  • 服务向消息总线发送一个异步消息(发出即可,不用等返回,通常很快就能完成)
  • 服务返回业务方新增数据成功
  • 消息总线将消息投递给数据同步中心
  • 数据同步中心插入T2数据

优点:

  • 请求处理时间短(只插入1次)

缺点:

  • 系统的复杂性增加了,多引入了一个组件(消息总线)和一个服务(专用的数据复制服务)
  • 因为返回业务线数据插入成功时,数据还不一定插入到T2中,因此数据有一个不一致时间窗口(这个窗口很短,最终是一致的)
  • 在消息总线丢失消息时,冗余表数据会不一致