这是我参与「第三届青训营 -后端场」笔记创作活动的的第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,确定好之后,下面说说我们要做的和数据插入相关的操作,也就是做数据冗余。
数据冗余:
方法一:服务同步冗余
顾名思义,由好友中心服务同步写冗余数据,如上图1-4流程:
- 业务方调用服务,新增数据
- 服务先插入T1数据
- 服务再插入T2数据
- 服务返回业务方新增数据成功
优点:
- 不复杂,服务层由单次写,变两次写
- 数据一致性相对较高(因为双写成功才返回)
缺点:
- 请求的处理时间增加(要插入次,时间加倍)
- 数据仍可能不一致,例如第二步写入T1完成后服务重启,则数据不会写入T2
所以我不建议用这种方法,因为插入操作是两倍,当并发量大时,效率问题堪忧。
方法二:服务异步冗余
数据的双写并不再由好友中心服务来完成,服务层异步发出一个消息,通过消息总线发送给一个专门的数据复制服务来写入冗余数据,如上图1-6流程:
- 业务方调用服务,新增数据
- 服务先插入T1数据
- 服务向消息总线发送一个异步消息(发出即可,不用等返回,通常很快就能完成)
- 服务返回业务方新增数据成功
- 消息总线将消息投递给数据同步中心
- 数据同步中心插入T2数据
优点:
- 请求处理时间短(只插入1次)
缺点:
- 系统的复杂性增加了,多引入了一个组件(消息总线)和一个服务(专用的数据复制服务)
- 因为返回业务线数据插入成功时,数据还不一定插入到T2中,因此数据有一个不一致时间窗口(这个窗口很短,最终是一致的)
- 在消息总线丢失消息时,冗余表数据会不一致