推拉模型系统设计分析(上)

139 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第21天,点击查看活动详情

Solution A:拉模型(Pull Model)

主动型的模型。当你打开微博,查看自己的新鲜事订阅(其它你所关注的用户发的微博)时,系统会先去取出你所关注的的用户列表,再分别把这些你所关注的的用户的时间线上的微博取出来,最终按时间执行归并排序,返回给你所需要的新鲜事订阅。

当你需要获取新鲜事列表时,系统是去你所关注(Follow)用户的时间线列表,拉取你所需要的新鲜事。

  1. 用户打开新鲜事列表,获取所有关注的其他用户
  2. 获取这些用户时间轴中的前 100 条新鲜事
  3. 将【2】中取到的新鲜事,按时间排序,合并成为一个 100 条的新鲜事列表(K 路归并

img

复杂度

假设该用户关注了 N 个用户:

100*N次DB读 + N路归并 = 100*N次DB访问 + 100log(N)内存处理

一般内存处理的时间<< DB 访问时间,因此可忽略不计。【拉模型】下,若用户发布了一条新鲜事,会发生啥?

该模型下,用户发表一条新鲜事,只需在用户自己的时间轴中插入一条数据,即执行一次DB写。

伪代码

// 获取新鲜事列表
public List<Feed>  getNewsFeed(request) {
  List<Feed> followings = db.readFollowings(request.user);
  List<Feed> newsFeedList = [];
  
  followings.forEach(f -> {
    List<Feed> feeds = db.getTimeLine(f.to_user, 100);
    newsFeedList.push(feeds);
  });
  return K_sort(newsFeedList);
}
​
// 发布新鲜事
public String postNews(request, content) {
  db.insertNews(request.user, content);
  return "success";
}

缺陷

当用户想拉取自己订阅的新鲜事list,需执行较多DB操作,用户需等待这一系列 DB 操作执行完成,系统才会将新鲜事list返给客户端显示,对于用户就得等待较长时间。

Solution B:推模型(Push Model)

系统为每一个用户都维护了一个新鲜事列表,当某用户发了一条新鲜事,所有关注该用户的新鲜事列表里都会被插入一条该新鲜事;当一个用户查看自己订阅的新鲜事列表时,仅需从自己的列表中取出前 K 条新鲜事即可。

算法描述

  1. 为每个用户建立一个存储他的新鲜事的列表,列表中只包含他所关注的用户发布的新鲜事
  2. 当某个用户发布新鲜事后,会将该新鲜事逐个推送至每个关注他的用户的新鲜事列表
  3. 当用户需要查看自己订阅的新鲜事列表时,只需按时间顺序,从该新鲜事列表中取出

复杂度分析

当用户在刷新自己订阅的新鲜事列表时,只需1次DB读取。

当用户发一条新鲜事,若该用户被 N 个用户关注,则需执行 N 次 DB 写入。

在用户请求到达服务端后,服务端可先返回给用户“已发送成功”消息,用户无需等待所有数据插入完成,该操作可在系统异步执行,即【Fanout】:

假设现有四个用户之间好友关系如下:

张东升在 2020 年 06 月 20日 8:30 发一条新鲜事:“一起去爬山啊!”,此时新鲜事数据库表数据如下:

img

老陈发了一条新鲜事“今天碰上一个臭小子!”

img

严良发了一条:“我爸爸在哪呢?”:

img

朱朝阳结束了疲惫的一天发了条“终于记完了今天的日记。”:

img

基本伪代码:

// 获取新鲜事列表
function getNewsFeed(request) {
  return db.getNews(request.user);
}
​
// 发布新鲜事
function postNews(request, content) {
  db.insertNews(request.user, content);
  AsyncTask.asyncExec(request.user, content);
  return "success";
}
​
AsyncTask.asyncExec = (user, content) => {
  let followings = db.readFollowings(request.user);
​
  followings.forEach(f => {
    db.insertNews(f, content);
  });
}

缺陷

  • 浪费DB空间: 同一条新鲜事会在数据表中存储多条,尽管这问题可通过在 fanout 的表中,只记录新鲜事 id 来优化,但是相比较而言,这一方案是更浪费DB存储空间
  • 新鲜事更新可能会不及时: 如一个明星有 1M 粉丝,整个 Fanout 的过程可能要持续相当一段时间,有些粉丝可能已经收到这个明星发布的新鲜事,但是有的粉丝可能半小时后才收到,影响用户吃瓜的体验!