数据库真的不太好学23333,很多都要自己上网搜素。
- 一、理论层面
- 朋友圈表结构
- 朋友圈架构
- 朋友圈流程举例
- 仅好友可见功能
- 网上的一些trick
- 二、应用层面
- 代码实现
- 好友圈逻辑
- 点赞实现
涉及朋友圈数据的有四个核心的表:
一个是发布。发布数据记录了来自所有用户所有的feed,比如一个用户发布了几张图片,每张图片的URL是什么,在CDN里的URL是什么,它有哪些元属性,谁可以看,谁不可以看等等。
一个是相册。相册是每个用户独立的,记录了该用户所发布的所有内容。
一个是评论。评论就是针对某个具体发布的朋友评论和点赞 *** 作。
一个是时间线。所谓“刷朋友圈”,就是刷时间线,就是一个用户所有朋友的发布内容。
整体架构如下,
-
最上面为接入层,接入主要维护长连接,长连接主要为了安卓系统,一方面能够减少新建连接的性能损耗,另一方面由于谷歌的国内服务基本不可用,安卓的推送通知都是通过长连接哎完成的。
-
接入层后面是逻辑层,逻辑层不仅有朋友圈,也有iOS的系统的通知,因为iOS App进入后台后只有15s的存活期,所以iOS上的推送通知要用API的Push完成。
-
接下来是存储代理层,这一层主要负责一些关键数据的维护 *** 作,比如用户在账号里面的动作 *** 作和事故信息。
-
再往下是KV存储层,这里不存在业务逻辑,只是单纯的Key-Value映射,负载均衡和容错。
两个用户小王和Mary(如下图)。小王和Mary各自有各自的相册,可能在同一台服务器上,也可能在不同的服务器上。现在小王上传了一张图片到自己的朋友圈。上传图片不经过微信后台服务器,而是直接上传到最近的腾讯CDN节点,速度非常快。图片上传到该CDN后,小王的微信客户端会通知微信的朋友圈CDN:这里有一个新的发布(比如叫K2),这个发布的图片URL是什么,谁能看到这些图片,等等此类的元数据,来把这个发布写到发布的表里。
在发布的表写完之后,会把这个K2的发布索引到小王的相册表里。所以相册表其实是很小的,里面只有索引指针。相册表写好了之后,会触发一个批处理的动作。这个动作就是去跟小王的每个好友说,小王有一个新的发布,请把这个发布插入到每个好友的时间线里面去。
救命:什么是索引指针,什么是批处理
Mary上朋友圈了,而Mary是小王的一个好友。Mary拉自己的时间线的时候,时间线会告诉到有一个新的发布K2,然后Mary的微信客户端就会去根据K2的元数据去获取图片在CDN上的URL,把图片拉到本地。在这个过程中,发布是很重的,因为一方面要写一个自己的数据副本,然后还要把这个副本的指针插到所有好友的时间线里面去。如果一个用户有几百个好友的话,这个过程会比较慢一些。这是一个单数据副本写扩散的过程。但是相对应的,读取就很简单了,每一个用户只需要读取自己的时间线表,就这一个动作就行,而不需要去遍历所有好友的相册表。
使用写扩散的原因是,如果使用读是很容易失败的,一个用户如果要去读两百个好友的相册表,极端情况下可能要去两百个服务器上去问,这个失败的可能性是很大的。但是写失败了就没关系,因为写是可以等待的,写失败了就重新去拷贝,直到插入成功为止。
至于赞和评论的实现,是相对简单的。上面说了微信后台有一个专门的表存储评论和赞的数据,比如Kate是Mary和小王的朋友的话,刷到了K2这一条发布,就会同时从评论表里面拉取对应K2的、Mary留下的评论内容,插入到K2内容的下方。而如果另一个人不是Mary和小王的共同朋友,则不会看到这条评论。
救命:什么是写扩列?
仅好友可见功能写扩散是主动把消息写到订阅者的消息列表里,这样订阅者就不用去我的outbox拉取消息 ,所以当我要是有很多订阅者时,我就要写很多次,这就是上面定义中说的写很重
调用判断:当需要判断好友01id的界面能否取得某具体内容数据的时候,只需要通过表2判断该内容所归属usrid的好友关系列表表1中有没有 好友01id。
网上的一些trick1.首先以每个用户的id为key生成一个list,list最好根据需求限制一下长度,毕竟不会有人刷朋友圈的时候会刷到前面几千条数据去吧
2.然后当用户A发布内容的时候往关注A的用户的list里将内容lpush进去(因为关注人可能比较多,可以使用异步 *** 作),用户A删除内容的时候也将关注人list里相对的内容删除
3.当用户要查看朋友圈的时候就返回redis里的list的数据就行(也支持分页)
4.当用户关注或者取消关注一个人的时候需要清空list然后在关系数据库中搜索所有关注人发布的内容并存到list里面
- 消息表很好理解,存储所有用户发送的所有内容,图片存地址。
utf8mb4格式可以存储emoji表情,具体可以参照之前的一篇文章
#消息表 CREATE TABLE friend_circle_message ( id bigint(15) NOT NULL AUTO_INCREMENT COMMENT '主键', uid bigint(15) DEFAULT NULL COMMENT '用户id', content varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, picture varchar(200) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT '' COMMENT '图片', location varbinary(100) DEFAULT '' COMMENT '位置', create_time datetime DEFAULT NULL COMMENT '创建日期', PRIMARY KEY (id) FOREIGN KEY(uid) REFERENCES friend_circle_user(id)
- uid表示作者id,可根据这个uid查询friend_circle_user表知道这个作者是谁。
- 时间轴表在朋友圈中是最关键的,存储着所有用的时间轴信息,因为当用户去拉取好友圈的时候,查询的就是本表,is_own字段用来区分当前数据是自己的发布还是好友发布的消息。
#时间轴表 CREATE TABLE friend_circle_timeline ( id bigint(15) NOT NULL AUTO_INCREMENT, uid bigint(15) DEFAULT NULL COMMENT '用户id', fcmid bigint(15) DEFAULT NULL COMMENT '朋友圈信息id', is_own int(1) DEFAULT '0' COMMENT '是否是自己的', create_time datetime DEFAULT NULL COMMENT '创建日期', PRIMARY KEY (id) )
- 评论表,每个元组就是一个评论;关联着一个用户、一个动态
#评论表 CREATE TABLE friend_circle_comment ( id bigint(15) NOT NULL AUTO_INCREMENT COMMENT'评论编号', fcmid bigint(15) DEFAULT NULL COMMENT '朋友圈信息id', review_to bigint(15) DEFAULT NULL COMMENT '回复对象ID', uid bigint(15) DEFAULT NULL COMMENT '评论者id', content varchar(500) DEFAULT NULL, create_time datetime DEFAULT NULL COMMENT '创建日期', like_count int(10) DEFAULT '0' COMMENT '点赞数', PRIMARY KEY (id) )
4.用户列表,存储所有用户的信息
#用户列表 CREATE TABLE friend_circle_user ( id bigint(15) NOT NULL AUTO_INCREMENT COMMENT '主键', uid bigint(15) DEFAULT NULL COMMENT '用户id主键', nickname varchar(500) COMMENT '昵称比如蛋卷超人', sex int(1) COMMENT '性别', password varchar(500) COMMENT '登陆密码', PRIMARY KEY (id) )
- 发布朋友圈消息
当用户发布一条朋友圈消息的时候,后端逻辑的处理(A和B已经是好友关系):
用户A在朋友圈中发布一条消息,消息表t_friend_circle_message写入一条数据。时间轴表t_friend_circle_timeline中增加一条数据,uid设置A,is_own设置为1,表示在A的时间轴中增加一条自己发布的消息。查询用户A的好友,查到用户B(如果有还有其他好友D、E等等同样处理)时间轴表t_friend_circle_timeline中增加一条数据,uid设置B,is_own设置为0,表示在B的时间轴中增加一条好友发布的消息。 - 添加好友
当用户A,添加用户C为好友之后,触发同步好友时间轴的 *** 作
INSERT INTO t_friend_circle_timeline (uid,fcmid,is_own,create_time) SELECt #{uid},`id`,0,create_time FROM t_friend_circle_message WHERe uid = #{fid};
把消息表t_friend_circle_message好友C发布的所有消息添加到自己的时间轴中。
再把消息表t_friend_circle_message自己发布的消息添加到好友C的时间轴中。
使用好is_own字段,因为都是互相添加好友的消息到自己的时间轴中,所以都应该为false(0)。
点赞其实很好做,记录点赞数++ 就可以实现,但是我们需要判断出当前用户是否点赞过,点过赞的标识出已点赞的状态,所以我们需要记录一条消息的点赞人id,当用户每次点赞的时候去查询一下点赞列表里是否存在当前用户的id。
消息id作为key,点赞人的uid作为value,放到redis中。
(存储的时候没有使用数组或字符串,而是直接把list[long] 存储的uid集合序列化了。在读取遍历的时候比较方便,但是取消点赞的时候需要遍历移除掉其中一位,不确定list合适不合适做为存储结构。)
@Override public Pagepage(long uid, int page, int pageSize) { int startNumber = (page - 1) * 10; Collection list = timelineDetailMapper.page(uid, startNumber, pageSize); list.forEach(i -> getLikedAndCount(i, uid)); return new Page (list, 0, pageSize, page); } private void getLikedAndCount(TimelineDetail timelineDetail, long uid) { Collection list = getLikeList(timelineDetail.getMessageId()); if (CollectionUtils.isNotEmpty(list)) { List nicknames = timelineDetailMapper.listNickname(list); if (CollectionUtils.isNotEmpty(nicknames)) { StringBuilder sb = new StringBuilder(); nicknames.stream().filter(StringUtils::isNotEmpty).forEach(i -> sb.append(i).append(",")); if (sb.length() > 0) { sb.deleteCharAt(sb.length() - 1); } timelineDetail.setLikeNickname(sb.toString()); } list.stream() .filter(i -> i == uid) .forEach(i -> timelineDetail.setLiked(true)); timelineDetail.setLikeCount(list.size()); } }
查询朋友圈的时候需要遍历redis中的值,然后把uid替换成昵称。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)