04 关注流设计

Wu Jun 2019-12-25 15:59:03
08 系统设计 > 2 业务设计

一、需求分析

1.1、数据现状

1.2、展示需求

需求可总结为以下4个接口

1.2.1 个人主页接口

  1. media主页统一展示各种类型动态

1.2.2 关注页接口

  1. 已关注, 展示所关注media的所有动态
  2. 已关注, 查询所关注media的新动态数量
  3. 未关注时,关注页展示数据

二、方案一:拉模式

拉模式是指,直接从各个来源拉取数据,再聚合排序截取。

以文章库测试,批量查询用户关注人的动态(in $media_id_list):关注1000人时,响应时间大概50ms;关注2000人时,响应时间大概70ms。

2.1、个人主页接口

2.1.1 在 media 主页统一展示各种类型动态

0)外部支持

需要各来源提供接口:

1)接口参数

media_idmax_time(可选,默认当前时间),size(可选,默认10,最大20)

2)执行流程

  1. 根据media_idmax_time,去各来源,并发请求 n 条数据
  2. 对各来源返回的数据,根据发布时间排序,截取前 n 条
  3. 对这 n 条数据,请求评论信息,封装返回

2.2、关注页接口

2.2.0 前置准备

1)用户请求最新动态的时间

使用redis记录用户请求关注页最新动态的时间。keytime_mark:$user_idvalue$time_mark

读取time_mark时若key不存在,则返回0,表示未读过。

2.2.1 已关注,展示所关注 media 的所有动态

0)外部支持

需要各来源提供接口:

1)接口参数

user_idmax_time(可选,默认当前时间),size(可选,默认10,最大20)

2)执行流程

  1. 根据user_id查询用户关注的media_id list
  2. 若用户有关注
    1. redis读取time_mark
      1. max_time大于time_mark,则使用max_time覆盖redis中的time_mark
      2. max_time不大于time_mark,则跳过。
    2. 根据media_id listmax_time,去各来源,并发请求 n 条数据
    3. 对各来源返回的数据,根据发布时间排序,截取前 n 条
    4. 对这 n 条数据,拼接分享信息,请求评论信息,封装返回
  3. 若用户没有关注,请看下面“2.2.3 章节”

2.2.2 已关注,查询所关注 media 的新动态数量

0)外部支持

需要各来源提供接口:

1)接口参数

user_id

2)执行流程

  1. 根据user_id查询用户关注的media_id list
  2. 若用户有关注
    1. redis读取time_mark
    2. 根据media_id listtime_mark,去各来源,并发请求动态数量
    3. 对各来源返回的数量,合并返回
  3. 若用户没有关注,返回 0

2.2.3 未关注时,关注页展示数据

用户未登陆或未关注任何media时,展示推荐media的数据。后台维护一个推荐media_id list

1)接口参数

max_time(可选,默认当前时间)

2)执行流程

  1. 获取后台维护的推荐media_id list
  2. 根据media_id listmax_time,去各来源,并发请求 n 条数据
  3. 对各来源返回的数据,根据发布时间排序,截取前 n 条
  4. 对这 n 条数据,请求评论信息,封装返回

三、方案二:拉模式进阶 - 集中模式

集中模式是指,将各来源的动态 id集中到一张feed 表。用户从feed 表批量获取动态 id,再根据动态 id去各来源请求动态内容

3.0、前置准备

3.0.1 新建 feed 表

mysql 新建 feed 表,记录每个用户发布的动态。

属性 类型 含义
id bigint 主键
media_id bigint 媒体id
source int 来源(1视频;2文章;3微头条)
source_id bigint 来源id
pub_time bigint 发布时间

唯一索引(source+source_id
普通索引(media_id+pub_time
普通索引(pub_time

3.0.2 数据同步

0)外部支持

需要各来源在发布、删除动态后,发送 kafka 消息:

1)历史数据

遍历所有 media,请求其各渠道已发布内容,插入到 feed 表

2)实时数据

Feed 系统监听 kafka 消息,并对feed 表进行增删。

3.1、个人主页接口

3.1.1 在 media 主页统一展示各种类型动态

0)外部支持

需要各来源提供接口:

1)接口参数

media_id,max_time(可选,默认当前时间),size(可选,默认10,最大20)

2)执行流程

  1. 根据 media_id,max_time,sizefeed 表查询source_id list
  2. 根据source_id list从各来源查询来源内容(来源处有缓存)
  3. 对来源返回的来源内容,拼接分享信息,请求评论信息,封装返回

3.2、关注页接口

3.2.0 前置准备

1)用户请求最新动态的时间

使用redis记录用户请求关注页最新动态的时间。keytime_mark:$user_idvalue$time_mark

读取time_mark时若key不存在,则返回0,表示未读过。

3.2.1 已关注,展示所关注 media 的所有动态

1)接口参数

user_idmax_time(可选,默认当前时间),size(可选,默认10,最大20)

2)执行流程

  1. 根据user_id查询用户关注的media_id list
  2. 若用户有关注
    1. redis读取time_mark
      1. max_time大于time_mark,则使用max_time覆盖redis中的time_mark
      2. max_time不大于time_mark,则跳过。
    2. 根据media_id listmax_time,去feed 表,批量请求source_id list
    3. 根据source_id list从各来源查询来源内容(来源处有缓存)
    4. 对来源返回的来源内容,请求评论信息,封装返回
  3. 若用户没有关注,请看下面“3.2.3 章节”

3.2.2 已关注,查询所关注 media 的新动态数量

1)接口参数

user_id

2)执行流程

  1. 根据user_id查询用户关注的media_id list
  2. 若用户有关注
    1. redis读取time_mark
    2. 根据media_id listtime_mark,去feed 表查询动态数量。返回
  3. 若用户没有关注,返回 0

3.2.3 未关注时,关注页展示数据

用户未登陆或未关注任何media时,展示推荐media的数据。后台维护一个推荐media_id list

1)接口参数

max_time(可选,默认当前时间)

2)执行流程

  1. 获取后台维护的推荐media_id list
  2. 根据media_id listmax_time,去feed 表,批量请求source_id list
  3. 根据source_id list从各来源查询来源内容(来源处有缓存)
  4. 对来源返回的来源内容,请求评论信息,封装返回

四、方案三:推拉结合

针对“关注页接口”。若“内容来源表”或“feed 表”数据量增大后,批量请求(in $media_id_list)性能代价过大;或者分库分表了,无法批量请求,单个请求再聚合的性能代价过大。这时可以使用推模式。

推模式:为每个用户建立一个“收件箱”——follow_feed 表media发布动态后,将动态给每个粉丝的“收件箱”都推送一条。用户查询“关注页接口”时,只需查询自己的“收件箱”,空间换时间。“关注页接口”读多写少,推模式在“写”的时候扩散数据,使读写更加平衡。

推拉结合:如果有大v(10万)存在,将大v的动态推送给所有粉丝时,代价也很大。这时可以推拉结合,区分大v小v,对少数的大v使用拉模式,只对多数小v推送。

4.0、前置准备

4.0.1 新建 follow_feed 表

mysql 新建 follow_feed 表,记录每个用户的关注对象发布的动态。

属性 类型 含义
id bigint 主键
user_id bigint 用户id
media_id bigint 媒体id
source int 来源(1视频;2文章;3微头条)
source_id bigint 来源id
pub_time bigint 发布时间

唯一索引(user_id+source+source_id
普通索引(user_id+pub_time

4.0.2 数据同步

0)外部支持

需要各来源在发布、删除动态后,发送 kafka 消息:

需要关注系统在用户关注、取关后,发送 kafka 消息:

1)历史数据

遍历所有media,请求其各渠道已发布内容,对每个粉丝插入到follow_feed 表

2)实时数据

Feed 系统监听 kafka media_post:$source消息。若media_id是大v则跳过。否则查询所有粉丝,结合每个粉丝的user_idfollow_feed 表进行增删。

Feed 系统监听 kafka follow_change消息。若media_id是大v则跳过。否则,关注时请求media_id的所有动态内容,结合user_id插入到follow_feed 表;取关时删除follow_feed 表中所有media_id+user_id的内容。

4.0.3 用户请求最新动态的时间

使用redis记录用户请求关注页最新动态的时间。keytime_mark:$user_idvalue$time_mark

读取time_mark时若key不存在,则返回0,表示未读过。

4.1、关注页接口

4.1.1 已关注,展示所关注 media 的所有动态

1)接口参数

user_idmax_time(可选,默认当前时间),size(可选,默认10,最大20)

2)执行流程

  1. 根据user_id从查询用户关注的media_id list
  2. 若用户有关注
    1. redis读取time_mark
      1. max_time大于time_mark,则使用max_time覆盖redis中的time_mark
      2. max_time不大于time_mark,则跳过。
    2. 若用户有关注大v,(异步)对大v使用“方案一”或“方案二”请求来源内容
    3. 根据user_id,去follow_feed 表,批量请求source_id list
    4. 根据source_id list从各来源查询来源内容(来源处有缓存)
    5. 对来源返回的来源内容和大v查询的来源内容聚合排序截取,请求评论信息,封装返回
  3. 若用户没有关注,请看下面“4.1.3 章节”

4.1.2 已关注,查询所关注 media 的新动态数量

1)接口参数

user_id

2)执行流程

  1. 根据user_id查询用户关注的media_id list
  2. 若用户有关注
    1. redis读取time_mark
    2. 若用户有关注大v,(异步)对大v使用“方案一”或“方案二”请求动态数量
    3. 根据user_idtime_mark,去follow_feed 表查询动态数量。
    4. 合并大v动态数量与普通动态数量,返回
  3. 若用户没有关注,返回 0

4.1.3 未关注时,关注页展示数据

用户未登陆或未关注任何media时,展示推荐media的数据。后台维护一个虚拟用户,关注推荐的media_id list,接口返回虚拟用户的关注页即可。

1)接口参数

max_time(可选,默认当前时间)

2)执行流程

通过虚拟用户的user_id和参数max_time,调用“4.1.1 章节”接口

五、总结

参考资料