系统设计Twitter信息流Feedsystemdesign面试

系统设计 Deep Dive:设计 Twitter/微博信息流系统

系统设计深度解析:设计Twitter/微博信息流系统。涵盖Fan-out架构、读写优化、实时推送与海量消息存储策略,还原真实系统设计面试的完整思考过程与面试官追问方向。

Sam · · 12 分钟阅读

面试官真实提问

“请设计 Twitter 的信息流(News Feed)系统。当用户打开 Twitter 时,能够看到他关注的人发布的推文。”

“如何优化’拉取信息流’的性能?如果某用户关注了 1000 个人,怎么处理?”

这是系统设计面试中最经典的题目之一,几乎每个大厂都会问。它考察了读写权衡、缓存策略和数据模型设计的综合能力。


需求澄清清单

功能需求

Must-have:

  • 发布推文
  • 拉取关注用户的时间线(信息流)
  • 按时间倒序排列

Nice-to-have:

  • 点赞、评论、转发
  • 媒体内容(图片、视频)
  • 算法推荐排序
  • 无限滚动(Infinite scroll)

规模估算

指标估算值
MAU5 亿
DAU2 亿
每日推文5 亿条
平均关注数每人关注 200 人
读写比10:1(读远多于写)
P99 延迟信息流加载 < 200ms

非功能需求

  • 可用性: 99.9%
  • 一致性: 最终一致性可接受(推文延迟几秒出现 OK)
  • 扩展性: 支持水平扩展

第 1 步:高层设计

┌─────────┐
│ 客户端   │  Web / App
└────┬────┘

┌────▼──────┐
│ 负载均衡器 │
└────┬──────┘

   ┌─┴─┐
┌──▼──┐ ┌──▼──┐
│Tweet│ │ Feed│  写路径 / 读路径
│ API │ │ API │
└──┬──┘ └──┬──┘
     │      │
  ┌──▼──┐ ┌▼──────┐
  │Tweet│ │ Feed  │
  │Store│ │ Store │  Cassandra / Redis
  └──┬──┘ └───────┘

  ┌──▼───────┐
  │ Fan-out  │  Kafka
  │ Service  │
  └────┬─────┘
       │ 异步推送
       └──→ Feed Store

第 2 步:核心组件设计

API 设计

# 发布推文
POST /api/v1/tweets
{
  "text": "Hello, world!",
  "media_urls": []
}

# 拉取信息流
GET /api/v1/feed?user_id=123&since_id=456&max_id=789&count=20

# 获取用户推文
GET /api/v1/users/123/tweets

数据模型

-- 用户表
CREATE TABLE users (
  user_id BIGINT PRIMARY KEY,
  username VARCHAR(50) UNIQUE,
  display_name VARCHAR(100),
  followers_count INT,
  following_count INT,
  created_at TIMESTAMP
);

-- 关注关系
CREATE TABLE follows (
  follower_id BIGINT,
  followee_id BIGINT,
  created_at TIMESTAMP,
  PRIMARY KEY (follower_id, followee_id)
);

-- 推文表
CREATE TABLE tweets (
  tweet_id BIGINT PRIMARY KEY,
  user_id BIGINT,
  text VARCHAR(280),
  created_at TIMESTAMP,
  media_urls JSON,
  INDEX idx_user_created (user_id, created_at DESC)
);

-- 信息流(预生成)
CREATE TABLE feeds (
  user_id BIGINT,
  tweet_id BIGINT,
  created_at TIMESTAMP,
  PRIMARY KEY (user_id, tweet_id)
);

Feed 生成策略(核心考点)

这是这道题最重要的部分,面试官会重点考察你对 Push vs Pull 模型的理解。

方案 1:Pull 模型(扇出读取)

用户请求信息流


查询用户关注了谁
(用户 A, 用户 B, 用户 C...)


查询每个关注用户的最新推文


合并、排序、去重


返回信息流

(拉模式:读时合并)
优点缺点
存储效率高(不预生成信息流)读操作慢(需要查询多个用户 + 合并排序)
写操作快(发推文只需写入 tweets 表)关注人多时性能差(关注 1000 人 = 1000 次查询)
适合关注数少的用户

方案 2:Push 模型(扇出写入)

用户 A 发推文


查询用户 A 的所有粉丝
(粉丝 1, 粉丝 2, 粉丝 3...)


将推文 ID 写入每个粉丝的信息流


粉丝拉取信息流
(直接读取预生成的信息流)

(推模式:写时分发)
优点缺点
读操作极快(一次查询即可)写放大问题(名人发推文 = 数百万次写入)
信息流加载 < 50ms存储浪费(很多人不怎么看信息流)

写放大计算:

  • 假设用户有 100 万粉丝
  • 发 1 条推文 = 写入 100 万条 feed 记录
  • 每秒 1000 名人发推文 = 10 亿次写入/秒不可行

方案 3:Hybrid 模型(推荐)

粉丝数 < 10,000 → Push 模型(快读)
粉丝数 >= 10,000 → Pull 模型(省写)

实现:

def publish_tweet(user_id, tweet):
    # 存入 tweets 表
    tweets_store.write(tweet)
    
    followers = follow_service.get_followers(user_id)
    
    if len(followers) < 10000:
        # Push:写入粉丝的信息流
        for follower_id in followers:
            feed_store.write(follower_id, tweet.id)
    else:
        # 名人:不 Push,粉丝 Pull 时再查
        pass

def get_feed(user_id):
    # 1. 从预生成的信息流读取(Push 的部分)
    push_feed = feed_store.read(user_id)
    
    # 2. 找出粉丝数 >= 10000 的关注用户
    celebrity_followings = get_celebrity_followings(user_id)
    
    # 3. Pull 名人的推文
    pull_tweets = []
    for celeb_id in celebrity_followings:
        pull_tweets.extend(tweets_store.get_recent(celeb_id))
    
    # 4. 合并、排序、去重
    return merge_and_sort(push_feed, pull_tweets)

详细请求流程

发推文流程:

  1. 客户端 POST /api/v1/tweets
  2. Tweet API 验证用户、存储推文
  3. 查询粉丝数
  4. 粉丝 < 10000 → 异步推送到粉丝信息流(Kafka)
  5. 粉丝 >= 10000 → 只存储,不推送
  6. 返回成功

拉取信息流流程:

  1. 客户端 GET /api/v1/feed?user_id=123
  2. Feed API 查 Redis 缓存(用户信息流)
  3. 缓存命中 → 直接返回
  4. 缓存未命中 → 查 Feed Store
  5. 合并名人 Pull 的推文
  6. 写入缓存(TTL 30 秒
  7. 返回信息流

第 3 步:扩展性与优化

瓶颈分析

瓶颈 1:信息流读取

  • 2 亿 DAU,每人每天拉取 50 次 = 100 亿次读取/天
  • 解决: 多级缓存
    • L1:API Server 本地缓存(Guava,TTL 10 秒)
    • L2:Redis Cluster(TTL 30 秒)
    • L3:CDN 缓存(对公开信息流)

瓶颈 2:名人写放大

  • 顶级名人(如马斯克)发推文 = 数亿次写入
  • 解决: Hybrid 模型 + 名人推文单独缓存

瓶颈 3:合并排序性能

  • 关注 50 个名人,每人 20 条推文 = 合并 1000 条
  • 解决: 预排序 + 堆合并(N-way merge)

容量估算

指标计算结果
推文存储5 亿/天 × 500 字节 × 30 天750TB
Feed 存储50% 用户用 Push,每人 200 条1000 亿条记录
Redis 内存1000 亿 × 100 字节10TB(200 个节点)

缓存策略

请求 → 本地缓存 → Redis → Feed Store → DB
       10秒 TTL    30秒 TTL    预生成     原始数据

缓存失效策略:
- 用户发新推文 → 清除自己的 feed 缓存
- 关注/取消关注 → 清除相关 feed 缓存
- 被动失效:TTL 过期自动刷新

面试官常问 Trade-offs 与实战问答

Q1:你选择 Push 还是 Pull 模型?

候选人回答:

“我选择 Hybrid 模型。对于粉丝数 < 10,000 的普通用户,使用 Push 模型,预生成信息流,读取极快。对于粉丝数 >= 10,000 的名人,使用 Pull 模型,避免写放大问题。这样兼顾了读写性能。”

面试官追问:

“为什么选 10,000 这个阈值?”

候选人回答:

“这是基于容量估算的权衡。假设名人每秒发 1 条推文,10,000 粉丝就是每秒 10,000 次写入,Redis 可以承受。如果粉丝数更高,写放大就太严重了。这个阈值可以根据实际负载动态调整。“


Q2:名人发推文导致写放大怎么办?

候选人回答:

“对于名人(粉丝 > 10,000),我不 Push 到粉丝的信息流。粉丝拉取信息流时,先读取预生成的部分,再 Pull 名人的推文。同时我会缓存名人的推文,减少数据库查询。”

面试官追问:

“粉丝拉取名人推文时延迟怎么保证?”

候选人回答:

“名人推文单独缓存(Redis + CDN),缓存命中率很高。即使缓存未命中,名人推文数据库查询也是优化过的(按时间索引),P99 延迟 < 50ms。“


Q3:信息流一致性怎么保证?

候选人回答:

“采用最终一致性。推文发布后,粉丝可能在几秒后看到。对于需要实时性的场景(如用户自己的推文),可以主动轮询WebSocket 推送。大多数场景下,几秒延迟是可以接受的。”

面试官追问:

“如果用户投诉没看到推文怎么办?”

候选人回答:

“提供’刷新’按钮,主动拉取最新信息。同时监控信息流延迟指标,如果延迟过高,触发告警和扩容。“


Q4:合并排序性能怎么优化?

候选人回答:

“使用 N-way merge 算法。每个关注用户的推文按时间排序,用最小堆合并。时间复杂度 O(N log K),N 是推文总数,K 是关注人数。同时会预排序缓存合并结果。”

面试官追问:

“如果关注 1000 个人怎么办?”

候选人回答:

“1000 个关注 × 20 条推文 = 20,000 条推文合并。N-way merge 仍然高效,但会限制返回数量(如最多合并 5000 条)。同时会优先合并活跃用户的推文。“


Q5:缓存失效策略是怎样的?

候选人回答:

主动失效 + TTL 被动失效。用户发推文、关注/取消关注时,主动清除相关缓存。同时设置 TTL(10-30 秒),确保缓存不会过期太久。对于热点数据,TTL 更短;对于冷数据,TTL 更长。”

面试官追问:

“缓存穿透怎么办?”

候选人回答:

布隆过滤器拦截不存在的请求。同时缓存空值(TTL 很短),避免重复查询数据库。“


进阶扩展方向

  • 算法推荐: 不只按时间排序,按相关性/兴趣排序
  • 媒体内容: CDN 分发图片/视频,缩略图预生成
  • 无限滚动: 游标分页(cursor-based pagination)
  • 多媒体信息流: 图片、视频、链接预览混合排序

常见踩坑点

#踩坑点解决方案
1名人写放大:没有做 Hybrid 模型粉丝 > 10,000 用 Pull
2缓存一致性:发推文后缓存没有及时失效主动清除 + TTL
3合并排序 O(N×M):关注 N 个人,每人 M 条推文N-way merge + 限制数量
4时间同步:分布式系统时间不一致使用单调时钟或 NTP

总结

Twitter Feed 系统设计考察:

能力考察点
推拉模型核心 trade-off,必须深入理解
缓存策略多级缓存应对读多写少
容量估算从业务数据推导系统规模
名人问题极端场景下的系统设计

面试提示:这道题没有标准答案。关键是展示你对 trade-offs 的思考。先提出 Push 模型,再讨论它的写放大问题,最后引出 Hybrid 方案——这个过程比直接给出答案更有价值。


推荐阅读


💡 需要面试辅导?

如果你对准备技术面试感到迷茫,或者想要个性化的面试指导和简历优化,欢迎联系 Interview Coach Pro 获取一对一辅导服务。

👉 联系我们 获取专属面试准备方案

准备好拿下下一次面试了吗?

获取针对你的目标岗位和公司的个性化辅导方案。

联系我们