数据库实验设计——朋友圈【概念篇】

数据库真的不太好学23333,很多都要自己上网搜素。

一、理论层面

朋友圈表结构

涉及朋友圈数据的有四个核心的表:

一个是发布。发布数据记录了来自所有用户所有的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。

网上的一些trick

1.首先以每个用户的id为key生成一个list,list最好根据需求限制一下长度,毕竟不会有人刷朋友圈的时候会刷到前面几千条数据去吧
2.然后当用户A发布内容的时候往关注A的用户的list里将内容lpush进去(因为关注人可能比较多,可以使用异步操作),用户A删除内容的时候也将关注人list里相对的内容删除
3.当用户要查看朋友圈的时候就返回redis里的list的数据就行(也支持分页)
4.当用户关注或者取消关注一个人的时候需要清空list然后在关系数据库中搜索所有关注人发布的内容并存到list里面

二、应用层面

代码实现

  1. 消息表很好理解,存储所有用户发送的所有内容,图片存地址。
    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表知道这个作者是谁。
  1. 时间轴表在朋友圈中是最关键的,存储着所有用的时间轴信息,因为当用户去拉取好友圈的时候,查询的就是本表,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)
)

  1. 评论表,每个元组就是一个评论;关联着一个用户、一个动态
#评论表
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)
)

好友圈逻辑

  1. 发布朋友圈消息
    当用户发布一条朋友圈消息的时候,后端逻辑的处理(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的时间轴中增加一条好友发布的消息。
  2. 添加好友
    当用户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 Page<TimelineDetail> page(long uid, int page, int pageSize) {
    int startNumber = (page - 1) * 10;
    Collection<TimelineDetail> list = timelineDetailMapper.page(uid, startNumber, pageSize);
    list.forEach(i -> getLikedAndCount(i, uid));

    return new Page<TimelineDetail>(list, 0, pageSize, page);
}

/**
 * 拿到是否点过赞 和点赞总数
 * 再获取点赞的人名。。
 */
private void getLikedAndCount(TimelineDetail timelineDetail, long uid) {
    Collection<Long> list = getLikeList(timelineDetail.getMessageId());
    if (CollectionUtils.isNotEmpty(list)) {
        List<String> 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替换成昵称。

  • 2
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值