[系统设计] 新鲜事系统:写扩散与读扩散的实现与对比

[系统设计] 新鲜事系统:写扩散与读扩散的实现与对比

  • [Bilibili 同步视频](#Bilibili 同步视频)
  • [📌 先搞懂:什么是新鲜事(News Feed)?](#📌 先搞懂:什么是新鲜事(News Feed)?)
  • [🔄 模式一:读扩散(Pull Model)------ 主动"撩"来的信息流](#🔄 模式一:读扩散(Pull Model)—— 主动“撩”来的信息流)
    • [🔧 底层实现逻辑](#🔧 底层实现逻辑)
    • [📊 优劣拆解](#📊 优劣拆解)
    • [📝 简单伪代码示意](#📝 简单伪代码示意)
  • [📤 模式二:写扩散(Push Model)------ 坐等"被撩"的信息流](#📤 模式二:写扩散(Push Model)—— 坐等“被撩”的信息流)
    • [🔧 底层实现逻辑](#🔧 底层实现逻辑)
    • [⚡ 关键优化:异步执行 + 复合索引](#⚡ 关键优化:异步执行 + 复合索引)
    • [📊 优劣拆解](#📊 优劣拆解)
    • [📝 简单伪代码示意](#📝 简单伪代码示意)
  • [🤔 选型博弈:读扩散 vs 写扩散,该怎么选?](#🤔 选型博弈:读扩散 vs 写扩散,该怎么选?)
    • [🌟 主流平台选型参考](#🌟 主流平台选型参考)
    • [📋 核心选型逻辑](#📋 核心选型逻辑)
  • [🎯 总结:新鲜事系统的设计精髓](#🎯 总结:新鲜事系统的设计精髓)

Bilibili 同步视频

[系统设计] 新鲜事系统:写扩散与读扩散的实现与对比

✨ 前言:在社交产品的核心架构中,新鲜事(News Feed)系统堪称"流量入口的心脏"------从Twitter的推文流到朋友圈的动态墙,从早已落幕的RSS阅读器到当下热门的社交平台,每一次页面刷新,背后都藏着一套精妙的存储与访问逻辑。今天,我们就来拆解新鲜事系统的两大核心实现模式:读扩散(Pull Model)与写扩散(Push Model),聊聊它们的底层逻辑、优劣博弈,以及实际场景中的选型智慧 🔍

📌 先搞懂:什么是新鲜事(News Feed)?

在深入技术细节前,我们先明确一个核心概念:新鲜事系统,本质上是用户登录社交平台后,看到的所有关注对象发布内容的整合信息流。

比如我们熟悉的👇:

  • ✅ Twitter:关注博主后,实时刷到的推文集合

  • ✅ 朋友圈:好友发布的文字、图片、动态汇总

  • ✅ Facebook:基于关注关系的个性化内容流

  • ❌ RSS阅读器:曾风靡博客时代的订阅工具,可通过RSS协议获取博主新帖,后因社交平台崛起逐渐被淘汰(如今更多作为系统设计面试题出现)

新鲜事系统的核心痛点的是:如何高效存储用户发布的内容,并快速返回给其关注者------这就引出了系统设计中最关键的思维:Trade-off(权衡),而读扩散与写扩散,正是两种截然不同的权衡方案 💡

🔄 模式一:读扩散(Pull Model)------ 主动"撩"来的信息流

读扩散,顾名思义,是用户主动触发读取操作时,才动态生成属于自己的新鲜事流,俗称"按需索取"。就像主动去撩妹,需要的时候才主动出击,非常"主动"~

🔧 底层实现逻辑

当用户点击刷新新鲜事时,系统会执行以下步骤:

  1. 查询该用户的关注列表(比如关注了10位好友);

  2. 分别获取每位关注对象的最新N条内容(通常是前100条,满足用户浏览需求);

  3. 通过k路归并算法(类似LeetCode中的「合并k个有序数组/链表」),将所有关注对象的内容按时间排序,合并成该用户的新鲜事流(取前100条展示)。

这里有个关键知识点:k路归并的执行效率极高,几乎可以忽略不计------因为它在内存 中执行,而数据库读取(DB Read)是在磁盘中执行,两者的访问速度差异高达1000倍!所以读扩散的性能瓶颈,不在于"合并数据",而在于"读取数据" 🚀

📊 优劣拆解

✅ 优势:发布内容极高效

用户发布一条内容时,只需在数据库的Tweet表中创建一条记录(一次简单的DB Write),无需同步给任何关注者,操作轻盈、速度飞快。

❌ 劣势:读取体验拉胯

用户每次刷新新鲜事,都需要执行N次DB Read(N为关注人数),再进行N路归并------这个"现算现返"的过程会卡住用户请求,延迟明显,用户可直接感知到"刷新变慢"。哪怕用SQL语句优化,效率提升也仅为2-3倍,无法从根本上解决问题。

📝 简单伪代码示意

python 复制代码
# 获取用户新鲜事(读扩散核心逻辑)
def get_user_feed(user_id):
    # 1. 获取用户关注的所有对象
    followers = get_followers(user_id)
    # 2. 遍历获取每位关注对象的最新100条内容
    all_tweets = []
    for follower_id in followers:
        tweets = get_latest_tweets(follower_id, limit=100)
        all_tweets.append(tweets)
    # 3. k路归并,按时间排序,取前100条
    merged_tweets = merge_k_sorted_lists(all_tweets)
    return merged_tweets[:100]

# 发布内容(读扩散发布逻辑)
def post_tweet(user_id, content):
    # 仅创建一条推文记录,无需同步
    create_tweet(user_id, content)
    return "发布成功"

📤 模式二:写扩散(Push Model)------ 坐等"被撩"的信息流

写扩散,与读扩散恰好相反:用户发布内容时,主动将内容"推送"给所有关注者,俗称"主动投喂"。就像女生发布"单身"动态,关注她的人都会收到提醒,全程"被动等待"即可 💌

🔧 底层实现逻辑

写扩散的核心是新增一张News Feed表,用于存储每个用户的新鲜事流,具体步骤如下:

  1. 用户发布一条内容,先在Tweet表中创建记录(一次DB Write);

  2. 查询该用户的所有粉丝(关注者),为每位粉丝在News Feed表中创建一条对应记录(N次DB Write,N为粉丝数);

  3. News Feed表采用「去标准化(Denormalized)」设计,存储冗余信息(如内容、发布时间),避免后续读取时再次查询Tweet表,提升效率;

  4. 用户刷新新鲜事时,只需查询自己的News Feed表,按发布时间排序,直接返回结果(一次DB Read)。

这里有两个高频专业术语,面试时提出来绝对加分 ✨:

  • 📌 Fan Out(扇出):将用户发布的内容,推送给所有粉丝的过程,是写扩散的核心操作;

  • 📌 Denormalized(去标准化):通过存储冗余信息,牺牲部分存储空间,换取读取速度的提升,是高并发系统的常用优化手段。

⚡ 关键优化:异步执行 + 复合索引

写扩散看似有个致命问题:如果用户有1亿粉丝,发布一条内容就要执行1亿次DB Write,效率极低?别急,两个优化方案直接解决:

  1. 异步执行:将Fan Out过程交给「消息队列(Message Queue)」和「异步任务服务器(Async Task Server)」处理,用户发布内容后,只需确认Tweet表创建成功即可返回,无需等待Fan Out完成,用户无感知延迟;

  2. 复合索引:为News Feed表建立「owner_id + created_at」复合索引(先按用户ID排序,再按发布时间排序),确保用户查询自己的新鲜事时,能快速过滤、排序,避免全表扫描。

📊 优劣拆解

✅ 优势:读取体验极佳

用户刷新新鲜事时,只需一次DB Read,直接从自己的News Feed表中获取排序好的内容,速度飞快,无感知延迟。

❌ 劣势:发布压力巨大

粉丝数越多,发布内容时的Fan Out压力越大------1亿粉丝就需要创建1亿条News Feed记录,对数据库和异步任务的压力极大,且会占用大量存储空间。

📝 简单伪代码示意

python 复制代码
# 发布内容(写扩散核心逻辑)
def post_tweet(user_id, content):
    # 1. 创建推文记录
    tweet_id = create_tweet(user_id, content)
    # 2. 异步执行Fan Out,推送给所有粉丝
    create_async_task(fan_out_tweet, user_id, tweet_id)
    return "发布成功"

# 异步Fan Out任务
def fan_out_tweet(user_id, tweet_id):
    # 获取该用户的所有粉丝
    followers = get_followers(user_id)
    # 为每位粉丝创建News Feed记录
    for follower_id in followers:
        create_news_feed(follower_id, tweet_id, created_at=now())

# 获取用户新鲜事(写扩散核心逻辑)
def get_user_feed(user_id):
    # 一次查询,按时间排序,取前20条(适配翻页)
    return get_news_feed_by_owner(user_id, limit=20, order_by="created_at desc")

🤔 选型博弈:读扩散 vs 写扩散,该怎么选?

没有最好的模式,只有最适合的场景。我们先看看主流社交平台的选型参考,再总结核心选型逻辑 📚

🌟 主流平台选型参考

  • Twitter:采用读扩散(Pull Model);

  • Facebook:采用读扩散(Pull Model);

  • Instagram:采用「读扩散 + 写扩散」混合模式;

  • 朋友圈(推测):采用写扩散(Push Model)------ 因朋友圈无需复杂的内容排序(仅按时间排序),且广告插入采用读扩散逻辑(需实时优化展示顺序)。

📋 核心选型逻辑

  1. 看「关注/粉丝」关系:
  • 若用户关注人数少(如微信朋友圈,最多5000人),读扩散更合适------读取时的DB Read压力不大;

  • 若用户粉丝数极多(如明星账号,百万/千万粉丝),纯写扩散压力过大,可采用混合模式(核心粉丝用写扩散,普通粉丝用读扩散)。

  1. 看「内容排序需求」:
  • 若需复杂排序(如基于用户兴趣、浏览历史的Ranking算法),读扩散更合适------可实时计算排序结果;

  • 若仅按时间排序,写扩散更合适------提前推送排序,读取时无需额外计算。

  1. 核心原则:Trade-off

读扩散:牺牲读取速度,换取发布效率;写扩散:牺牲发布效率,换取读取速度。系统设计的本质,就是在这些权衡中找到最优解,而非追求"完美无缺"。

🎯 总结:新鲜事系统的设计精髓

新鲜事系统的核心,是「内容存储与访问的效率平衡」------读扩散与写扩散,是两种最经典的实现路径,它们各有优劣,却能覆盖绝大多数社交场景。

最后分享一个重要思维:不要因为某一种模式有缺陷就轻易放弃。无论是读扩散的读取延迟,还是写扩散的发布压力,都可以通过技术手段优化(如缓存、异步、混合模式)。真正的系统设计能力,不在于记住两种模式的区别,而在于理解它们的底层逻辑,根据实际场景灵活选型、优化迭代 ✨

后续我们会深入讲解混合模式的实现细节、缓存优化技巧,以及高并发场景下的新鲜事系统设计,敬请期待~

相关推荐
疯狂成瘾者2 小时前
LangChain4j ApacheTikaDocumentParser:多格式文档接入的统一入
java·langchain4j
庞轩px3 小时前
第三篇:泛型深度解析——类型擦除与通配符的奥秘
java·编译·泛型·类型擦除
HoneyMoose11 小时前
Jenkins Cloudflare 部署提示错误
java·servlet·jenkins
阿丰资源11 小时前
基于SpringBoot的物流信息管理系统设计与实现(附资料)
java·spring boot·后端
Predestination王瀞潞11 小时前
Java EE3-我独自整合(第四章:Spring bean标签的常见配置)
java·spring·java-ee
overmind11 小时前
oeasy Python 121[专业选修]列表_多维列表运算_列表相加_列表相乘
java·windows·python
资深数据库专家11 小时前
总账EBS 应用服务器1 的监控分析
java·网络·数据库
房开民11 小时前
可变参数模板
java·开发语言·算法
t***54411 小时前
如何在现代C++中更有效地应用这些模式
java·开发语言·c++