高 困难
Feed流设计#
一句话答案#
Feed 流推模式(写扩散/适合粉丝少)和拉模式(读扩散/适合大 V),实际混合使用,Redis ZSet 存储。
核心要点
Feed 流的两种模式:
推模式(Push / Fan-out on Write):
用户发帖 → 立即推送到所有关注者的 Feed 列表(写时扩散)
优点:读取 Feed 直接从自己的列表取,O(1) 读
缺点:大 V(百万粉丝)发帖时,需要写入百万个列表,写入放大严重
实现:
LPUSH feed:user:1002 postId # 推给关注者 1002
LPUSH feed:user:1003 postId # 推给关注者 1003
...(百万次 Redis 写)
拉模式(Pull / Fan-out on Read):
用户查看 Feed → 实时查询所有关注者的最新帖子(读时合并)
优点:写入简单(只写自己的帖子列表),不依赖关注者数量
缺点:读取时需要合并多个关注者的帖子(可能几百个关注者),读放大严重
实现:
ZREVRANGE posts:user:大V 0 10 # 读取每个关注者的最新10条
# 在内存中合并排序,取 top N
推拉结合(微博/朋友圈实际方案):
普通用户:推模式(关注者少,写放大可接受)
大 V / 热门账号:拉模式(粉丝千万,不做推)
读取时:自己的推 Feed + 关注大V的实时拉取,在应用层合并plaintextRedis 单机瓶颈优化:
问题:
Feed 列表数据量大(每个用户一个 ZSet/List),总 key 数量极多
热点用户的 Feed List 被频繁读写(热 key)
解决方案:
1. Redis Cluster(分片扩展容量)
→ 按用户 ID hash 将 key 分散到不同节点
→ 单节点压力降低(线性扩展)
→ 注意:Feed List 和用户基本信息可能在不同节点(跨 slot 操作需特殊处理)
2. 热 key 拆分(大 V 的 Feed 推送)
→ 大 V 的关注者 Feed 列表分多个 key 存储
→ feed:user:1002:shard:0 / shard:1 / shard:2
→ 读取时聚合多个分片
3. 本地缓存(Caffeine)
→ 热门 Feed 在应用层本地缓存(5s TTL)
→ 极热数据(如首页 Feed)不每次都打 Redis
4. 分级存储
→ 最近 30 天的 Feed 存 Redis(快速读取)
→ 更早的帖子存 MySQL / HBase(历史翻页时再查)plaintext面试回答(2分钟版)
Feed流的核心问题是推拉模式的选择。推模式也叫写扩散,用户发帖时立即把内容写入所有粉丝的Feed列表,读取时直接取自己的列表就行,读性能很好;但大V粉丝量百万级别,一次发帖就要写百万条记录,写放大严重。拉模式也叫读扩散,发帖只写自己的列表,用户刷Feed时实时去关注人那里拉取合并,写入简单但读放大严重。实际生产中采用推拉结合的方案:普通用户粉丝少用推模式,大V和热门账号用拉模式,应用层做合并。存储上用Redis ZSet,score作为时间戳做游标分页,避免offset方式的性能问题。数据量大了之后通过Redis Cluster分片、热key拆分、本地缓存Caffeine以及分级存储来优化。
追问与易错
追问方向:
- “推拉混合怎么实现?”→ 用户发帖时判断粉丝数:普通用户走推模式写入粉丝 Feed 列表,大 V(如粉丝 >10 万)只写自己的发帖列表;用户刷 Feed 时合并自己的推 Feed + 实时拉取关注大 V 的最新帖子
- “新关注加载历史动态?”→ 关注时异步从被关注者的发帖列表中取最近 N 条插入自己的 Feed(推模式补偿);或者读 Feed 时实时合并新关注者的历史帖子(拉模式天然支持)
- “存储选型?”→ Redis ZSet 存 Feed 列表(score=时间戳,支持游标分页),MySQL 存帖子详情和关系数据,最近 30 天热数据在 Redis,更早的冷数据落 HBase/MySQL 历史表
易错点:
- ❌ 所有人都用推模式——大 V 粉丝多推模式写爆炸
- ❌ 忽略分页问题——用 score 游标而非 offset