Feed 流系统设计

Feed 流(信息流)是社交和内容产品的核心——微博首页、朋友圈、抖音推荐,本质上都是在回答同一个问题:”用户打开 App 时,应该看到哪些内容,按什么顺序?”

一、业务场景

核心操作

  • 用户 A 发布内容 → A 的所有粉丝的 Feed 中可见
  • 用户 B 打开 App → 看到关注的人最新发布的动态

核心矛盾

1
2
3
写操作:一条内容需要"分发"N 个粉丝(推模式)
读操作:打开 App 需要"聚合" M 个关注者的内容(拉模式)
N 和 M 的数量级决定了两种模式的取舍

二、推模式(Fanout on Write / 写扩散)

原理:用户发内容时,直接把内容 ID 推送到所有粉丝的 Feed 收件箱中。

1
2
3
4
5
6
7
用户 A 发布动态

查询 A 的所有粉丝:B, C, D, ...(100 万粉丝)

向每个粉丝的 Feed 收件箱中追加该动态 ID

粉丝打开 App 时:直接从自己的收件箱按时间取最新内容

优点:读路径极短,打开 App 即时展示(O(1))
缺点:写放大严重——大 V 发一条,要写 100 万次

存储选型

1
2
3
4
粉丝收件箱 → Redis List / Sorted Set
- Key:feed:timeline:{user_id}
- Value:发布内容 ID 列表
- 每个用户最多保存 1000 条(超过的通过 ZREMRANGEBYRANK 自动裁剪)

三、拉模式(Fanout on Read / 读扩散)

原理:用户发内容只存到自己发件箱,粉丝打开 App 时从所有关注者的发件箱聚合拉取。

1
2
3
4
5
6
7
8
9
粉丝打开 App

查询关注的用户列表:[A, B, C, ...](关注了 500 人)

AB、C 的发件箱各拉取最新 N 条

内存中合并、排序、去重、分页

返回给客户端

优点:写路径简单,无写扩散
缺点:读路径重——关注 500 人就要做 500 次查询 + 内存归并排序

适用场景:用户关注数在 100~500 之间,粉丝数不可控

四、推拉结合(混合模式)

生产环境普遍采用的方案,核心思想:按粉丝数分级处理

1
2
3
4
5
6
7
8
          用户发一条动态

判断粉丝数 > 阈值(如 1 万)?
/ \
是(大 V) 否(普通用户)
↓ ↓
只写发件箱 推送到粉丝收件箱
(拉模式处理) (推模式处理)

粉丝侧读取逻辑调整

1
2
3
4
5
6
7
打开 App

从自己的收件箱取(普通用户推送的内容)

从关注的大 V 发件箱拉取(大 V 的内容)

合并 + 排序 + 返回

阈值选择经验

  • 阈值太低 → 退化为推模式,写放大问题
  • 阈值太高 → 退化为拉模式,读聚合开销大
  • 实践建议:1 万 ~ 10 万粉丝(根据系统压力动态调整)

五、存储与分页

5.1 Redis 存储结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 收件箱:每个用户一个 Sorted Set
Key: feed:inbox:{user_id}
Score: 发布时间戳
Value: 动态 ID

# 发件箱:每个用户一个 Sorted Set
Key: feed:outbox:{user_id}
Score: 发布时间戳
Value: 动态 ID(只存大 V 的发件箱)

# 动态详情:
Key: feed:detail:{post_id}
Value: JSON { 内容, 图片URL, 发布时间, 作者信息... }
TTL: 30

5.2 冷热分离

数据层级 存储 时间范围 策略
热数据 Redis Sorted Set 最近 3 天 TTL = 7 天,自动过期
温数据 MySQL 3 天 ~ 3 个月 按时间分区,定时归档
冷数据 对象存储(COS/OSS) 3 个月以上 压缩存储,按需加载

5.3 分页与一致性

  • 游标分页:使用时间戳作为游标,避免 offset 分页在数据变化时的重复/丢失
  • MySQL 层面WHERE create_time < last_cursor ORDER BY create_time DESC LIMIT 20
  • 关注关系变化:如果在分页过程中关注/取关某人,当前页不受影响,下一页反映最新关系

六、时间线一致性

问题:用户 B 先关注 A,A 发布动态,然后 B 取关 A。B 的 Feed 中是否应该看到这条动态?

推模式下,动态已在 B 的收件箱中,取关后仍可见——这其实是合理的,因为发布时刻 B 确实是 A 的粉丝。

生产实践:读取时做一次关注关系校验,过滤掉已取关的用户内容。开销很小(关注关系可走本地缓存)。

七、生产注意事项

大 Key 问题:大 V 的动态详情可能很大(视频、长文),Redis 大 Key 影响稳定性。解决方案:Feed 收件箱只存 ID(几十字节),详情存独立 Key 或 MySQL。

热点问题:大 V 发内容后,短时间内被大量粉丝拉取。本地缓存(Caffeine)缓存热门内容详情 5 分钟,CDN 缓存图片/视频等静态资源。

消息队列消峰:推模式下,大 V 发布触发百万级粉丝写入,通过消息队列分批消费。每批 1000 个粉丝,持续 10-30 秒完成全量扩散。

八、小结

Feed 流系统的核心选择是推拉策略——没有银弹,只有权衡。推模式读快写慢,适合粉丝数可控的场景;拉模式写快读重,适合关注数可控的场景;混合模式在两者间找平衡,是大多数社交产品的最终选择。