Lunarain_079's Inn

Back

Feed 设计:写扩散的利与弊#

在中小规模场景下,Feed 系统常常会采用 写扩散(Write Fan-out) 的模式,这种方式能快速迭代、快速上线,的确是一个值得推荐的起点。 但如果为了节省缓存,直接将 MySQL 数据库表设计成写扩散(收件箱模型),并在 DB 层直接读取加快访问,就会带来一些实际问题。


问题#

我们的产品现在的feed分发途径是这样的,以一个用户 A 发动态为例,写扩散模式的分发过程如下:

用户 A 发布 Feed  

分发 Consumer 查询 A 的粉丝列表  

将 Feed 写入所有粉丝的收件箱 (Inbox)
plaintext

这样做的好处是:

  • 粉丝读取时非常快(直接读db收件箱即可)
  • 实现简单,业务迭代速度快
  • 初期避免了缓存的使用,降低了redis运维成本

这样的流程乍一看是没有什么问题的,但是仔细想想你就能发现:

一:关注前后的历史消息缺失#

这是db层面写扩散最大的致命问题。

  1. 关注后无法看到历史动态

    • 关注用户 A 之后,只能看到关注后的 Feed,收件箱不会保存着用户之前,甚至关注前一秒发送的feed用户期望关注后能在首页立即看到 A 的部分历史动态,否则体验割裂
  2. 取关后再关注出现消息断层

    • 如果中途取关,再次关注时,中间这一段时间的消息永远缺失,这会让用户的动态出现「消息漏洞」,这也是用户不能接受的

二:副本数量爆炸,数据库压力过大#

写扩散意味着一条 Feed 需要写入 所有粉丝的收件箱,这种架构对关系型数据库极不友好消,息副本量会呈指数级增长,如果你也使用的是mysql这样写性能较弱的关系型数据库,feed可能会成为让你的整个系统崩溃的第一个模块。

举个例子: 想象一个日用户活动量在100的网站,每个人都有10个关注,每个人每日会进行10个活动如点赞,收藏等(我们的网站模仿github activity feed,会记录用户活动),这样每日的增长量就会在万级别以上,对于一个中小规模的feed系统负载会有些大

而在 MySQL 环境下,单机写 QPS 稳定值在 1000 左右,如果出现一个500数量级别粉丝用户,一次操作就需要写5000条数据,极容易打爆数据库


问题三:扩展性差,难以支持复杂需求#

DB 层直接写收件箱的模式过于死板,扩展性很差,首先同步写入导致架构难以修改,其次,前期我们的项目对广告,推荐的要求不高,但再向后发展呢?广告/推荐需要的异步实时构建 Feed 流 的需求就很难实现。随着业务发展(如推荐、广告变现需求),DB 层的 Inbox 架构会变成负担。

为了缓解历史消息缺失问题,我们尝试过去构建一个补偿机制去进行处理,比如第一次关注用户时会选取被关注用户的前20个feed发送到关注用户的收件箱中,如果发现是取关后重新关注就会去进行查询并进行消息的补洞

然而这样做会带来更多副本写入,反而加重问题二的数据库压力。


推拉结合#

因此,成熟的 Feed 系统都会采用 推拉结合(Hybrid Push & Pull) 模式:

  • 推(写扩散):在缓存层面构建inbox,去保证活跃用户的实时性(高频互动关系)
  • 拉(读扩散):解决历史消息、弱关系(推荐,广告)场景,用于inbox层的构建

feed的设计业界已经非常成熟,这里推荐阅读推特系统设计bilibili的动态设计去进行进一步的了解,推特作为业界标杆不用多说b站曾经也是同步写收件箱,中间使用纯拉模式(写扩散)进行过渡,最后实现推拉结合的架构。


总结#

  1. 如果是一个新项目,还是建议先用推模式

    • 快速把系统设计出来,然后让产品去验证、迭代,等客户数大幅上涨到后,再考虑升级为推拉集合模式
    • 如果可能,选择 KV 数据库 而不是 MySQL,提高写入能力上限
  2. 缓存不可或缺

    • 成熟系统建议要在缓存层抽象 Inbox
    • 异步构建缓存层,可以显著提升灵活性与可扩展性
  3. 最终形态:推拉结合

    • 只用推 or 只用拉都会遇到瓶颈
    • 推拉结合模式 是大规模系统的业界最佳解,拉/推模式可以用,但不要只用拉/推模式
Feed 系统设计:写扩散的利与弊
https://www.lunarain.top/blog/feed%E6%84%9F%E6%83%B3
Author Lunarain_079
Published at August 27, 2025
Comment seems to stuck. Try to refresh?✨