基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

微信朋友圈的“时间线”功能(拉取好友最新发布的动态),在推模式和拉模式下,Redis分别应该怎么存?

知识点图片

微信朋友圈(Timeline/Feed流)的底层核心逻辑,本质上是解决数据分发与排序的问题。

在Redis中,无论推模式还是拉模式,最合适的数据结构都是 Sorted Set (ZSET)。因为朋友圈需要严格按时间倒序排列,ZSET的 Score 天然适合存储时间戳,Member 适合存储动态(Feed)的ID。

注意:Redis的ZSET中只存 Feed ID,不存完整的图文内容。完整的图文内容通常存在 MySQL/HBase 等数据库或 Redis 的 Hash/String Cache 中,通过 Feed ID 去获取。

下面详细说明推模式和拉模式下,Redis 具体应该怎么存、怎么读、怎么写。


一、 推模式(Push Model / 写扩散)

推模式的核心思想是:每个人都有一个“收件箱”(Inbox),当某人发布动态时,系统主动把这条动态的ID,推送(写入)到他所有好友的收件箱里。

1. Redis 数据结构设计

  • Key: moments:inbox:{user_id} (当前用户的收件箱)
  • Type: ZSET
  • Score: timestamp (动态发布的时间戳,精确到毫秒)
  • Member: feed_id (动态的唯一ID)

2. 工作流程

  • 写操作(发布动态):
    1. 用户A发布了一条动态 feed_id = 1001
    2. 系统查询用户A的所有好友列表(假设有500个好友)。
    3. 系统异步地向这500个好友的 moments:inbox:{friend_id} 中写入这条 feed_id
    4. Redis命令: ZADD moments:inbox:{friend_id} <当前时间戳> 1001
  • 读操作(刷朋友圈):
    1. 用户B打开朋友圈。
    2. 直接读取自己的收件箱,按时间倒序获取分页数据。
    3. Redis命令: ZREVRANGE moments:inbox:{B的user_id} 0 10 (获取最新10条),或者通过 ZREVRANGEBYSCORE 基于上一次滑动的时间戳进行分页。

3. 优缺点

  • 优点: 读性能极高。用户刷朋友圈时只需读一次Redis,延迟极低(O(1)级别的业务复杂度)。
  • 缺点: 写放大严重。如果一个人有5000个好友,发一条动态要写5000次Redis。不仅消耗带宽,且占用极大内存。
  • 存储优化: 收件箱不能无限大。通常会限制 ZSET 的大小(例如只保留最近的 800-1000 条),超出部分通过 ZREMRANGEBYRANK 裁剪掉。

二、 拉模式(Pull Model / 读扩散)

拉模式的核心思想是:每个人只有一个“发件箱”(Outbox),发布动态只写自己的发件箱。当用户刷朋友圈时,系统去拉取他所有好友的发件箱,然后在内存中进行聚合、排序。

1. Redis 数据结构设计

  • Key: moments:outbox:{user_id} (当前用户的发件箱 / 个人主页动态)
  • Type: ZSET
  • Score: timestamp (动态发布的时间戳)
  • Member: feed_id (动态的唯一ID)

2. 工作流程

  • 写操作(发布动态):
    1. 用户A发布了一条动态 feed_id = 1002
    2. 系统只将这条动态写入用户A自己的发件箱。
    3. Redis命令: ZADD moments:outbox:{A的user_id} <当前时间戳> 1002
  • 读操作(刷朋友圈):
    1. 用户B打开朋友圈。
    2. 系统查询用户B的好友列表(假设有500个好友)。
    3. 系统并发地去这500个好友的 outbox 中,拉取最新的一批数据(例如每个好友拉取近3天的动态)。
    4. Redis命令: 并发执行 500 次 ZREVRANGEBYSCORE moments:outbox:{friend_id} +inf <三天前时间戳>。(通常使用 Pipeline 或 Redis 集群并发 mget)。
    5. 应用层服务(如Go/Java服务)将拉取到的这批数据进行多路归并排序(Merge Sort),截取前 10 条返回给前端。

3. 优缺点

  • 优点: 写操作极快,无数据冗余,节省大量Redis内存空间。取消关注/拉黑等权限控制极其容易(下次拉取时剔除该好友即可)。
  • 缺点: 读放大严重。一次刷朋友圈请求,可能涉及数百次Redis查询和大量的网络I/O、内存计算。如果好友很多,系统延迟会变高,容易引发可用性危机。

三、 总结与企业级实战(微信的真实做法)

纯推模式或纯拉模式在千万/亿级日活的产品中都存在致命短板。微信朋友圈(包括微博、小红书等)真实采用的通常是“推拉结合”的混合模式。

但考虑到微信的强社交属性(好友上限最初是5000,后来放宽到10000,属于典型的强关系网络),微信早期核心架构是偏向“推模式(写扩散)”的,后期加入了更多的优化:

推拉结合的 Redis 优化存储方案:

  1. 活跃用户走“推模式”:
    • 只有最近 N 天登录过的活跃好友,系统才会把动态“推”到他们的 inbox (ZSET) 中。
    • 因为活跃用户刷朋友圈的频率高,必须保证他们的读性能。
  2. 僵尸/非活跃用户走“拉模式”:
    • 如果某个好友已经半年没打开微信了,就不给他推送,节省 Redis 内存。
    • 当这个僵尸用户某天突然登录时,系统再去“拉”他所有好友最近的动态,临时构建一个 inbox 给它。
  3. 大V/特殊账号走“拉模式”(类似微博):
    • 虽然微信是双向好友,但如果以后出现单向关注或粉丝极多的账号(如早期的腾讯新闻、现在的视频号),几百万粉丝不可能用推模式。
    • 大V发动态存入自己的 outbox,粉丝刷信息流时,从自己的 inbox 拿到普通好友动态,再主动去大V的 outbox 拉取动态,最后在内存中合并合并(Merge)。

附加存储建议(Feed 内容的缓存):

以上 ZSET 中存储的都只是 feed_id。关于具体的文本、图片URL,应该额外存一个 Cache:

  • Key: feed:detail:{feed_id}
  • Type: String (存储 JSON) 或 Hash
    当且仅当分页查出当前页的 10 个 feed_id 后,再使用 MGET feed:detail:id1 feed:detail:id2 ... 批量获取内容并拼装返回给前端客户端。
00:00
00:00