关注关系与时间线:推拉模式、Fanout 与混合架构

全面讲解微型博客的关注关系与时间线生成:关注关系数据模型、推模式(Fanout on Write)与拉模式(Fanout on Read)的原理与代价、推拉混合架构(大 V 例外)、冷热分桶与缓存设计、热搜/榜单时间线与推荐时间线,以及时间线系统的分页与一致性。

时间线(Timeline / Feed)是微型博客的「首页命脉」——用户打开 App 看到的每一帧内容,都来自「关注关系」与「时间线生成」这两套系统。Twitter 早期 1 亿用户时,时间线系统就是其最核心的技术壁垒。

本文系统讲解:关注关系建模、推模式 Fanout、拉模式、混合架构、热搜/榜单时间线、分页与一致性。

一、关注关系数据模型

1.1 关注表

CREATE TABLE follows (
  follower_id BIGINT NOT NULL,   -- 关注者
  followee_id BIGINT NOT NULL,   -- 被关注者
  created_at TIMESTAMPTZ DEFAULT now(),
  PRIMARY KEY (follower_id, followee_id)
);
CREATE INDEX idx_follows_followee ON follows(followee_id, created_at DESC);

1.2 核心查询

-- 我关注了谁(关注列表)
SELECT followee_id FROM follows WHERE follower_id = :uid ORDER BY created_at DESC;

-- 谁关注了我(粉丝列表)
SELECT follower_id FROM follows WHERE followee_id = :uid ORDER BY created_at DESC;

-- 判断是否互关
SELECT count(*) FROM follows
WHERE follower_id = :a AND followee_id = :b;

-- 关注人数/粉丝数(冗余计数)
UPDATE users SET following_count = following_count + 1 WHERE id = :follower;
UPDATE users SET follower_count = follower_count + 1 WHERE id = :followee;

1.3 双向关系与缓存

- 关注动作:写一行 follows + 更新两个计数
- 缓存:Redis Set 缓存关注/粉丝列表(超大账号例外)
  following:{uid} → Set[followee ids]
  followers:{uid} → Set[follower ids]

一句话总结:关注关系的本质是「一张有向边表」——点赞/取关即增删一行,计数用冗余字段保证读 O(1)。

二、推模式:Fanout on Write

2.1 原理

发布时推送:作者发帖 → 系统把帖子副本写入每个粉丝的「个人时间线」列表。

用户 A 发帖
  → 查出 A 的粉丝列表 [B, C, D]
  → 向 timeline:B, timeline:C, timeline:D 各写入一条帖子引用
  → B/C/D 刷新时间线时直接读自己的列表(零计算)

2.2 优点与代价

优点代价
读时间线极快(O(1) 读列表)写放大:粉丝越多,写入越多
天然支持取关回退大 V 发帖 = 千万级写入风暴
实现简单存储冗余(同帖多副本)

2.3 存储

Redis List / SortedSet:
  timeline:{uid} = 帖子 id 列表(按时间倒序)
  新帖写入:LPUSH timeline:{B} {post_id}

容量问题:长期积累无限增长
  裁剪:只保留最近 N 条(如 500 条)
  越界 → 从粉丝时间线移除 → 靠拉模式兜底

一句话总结:推模式用「写放大换读简化」——粉丝时间线是预先生成的快照,读起来零计算,但大 V 的粉丝量决定了写成本的上限。

三、拉模式:Fanout on Read

3.1 原理

读取时合并:用户刷新时间线 → 系统实时查询「我关注的所有人的最新帖子」并合并排序。

用户 B 刷新时间线
  → 查出 B 的关注列表 [A, E, F]
  → 并行查询各人最新帖子
  → 合并 → 排序 → 返回前 N 条

3.2 优点与代价

优点代价
无写放大(发帖零副本)读开销大(多次查询 + 合并)
适合低活跃用户关注多时查询放大
存储省冷启动/刷新慢

3.3 合并查询

-- 拉模式核心查询(多关注合并)
SELECT * FROM posts
WHERE author_id IN (SELECT followee_id FROM follows WHERE follower_id = :uid)
ORDER BY created_at DESC
LIMIT 20;
性能优化:
  - 按关注列表分片并行查各作者最新帖
  - 各作者仅取前几篇,再内存合并
  - 结果缓存短 TTL(如 30s)

一句话总结:拉模式用「读合并换写零副本」——发帖即存一篇,读取时实时聚合,适合粉丝少、关注多的普通用户。

四、混合架构:大 V 例外

4.1 为什么需要混合

纯推模式:大 V(千万粉丝)发一条帖 = 千万次写入,瞬时打爆 Redis。
纯拉模式:普通用户每次刷新都要查询几十上百个作者,慢且费。

混合方案:按账号「热度」分流——大 V 用拉模式,普通用户用推模式。

4.2 混合时间线

用户 B 刷新时间线:
  1. 拉取「普通关注者」的推模式时间线(timeline:{B})
  2. 拉取「大 V 关注者」的最新帖子(实时查询,数量少)
  3. 合并排序 → 分页返回

大 V 判定:粉丝数 > 阈值(如 10 万)→ 走拉模式
             粉丝数 ≤ 阈值 → 走推模式

4.3 时间线生成伪代码

func GetTimeline(ctx, uid, page) []Post {
    // 1. 推模式部分
    pushed := redis.LRange(ctx, "timeline:"+uid, 0, 500)

    // 2. 拉模式部分(大 V)
    bigVs := GetBigVFollowees(ctx, uid)   // 粉丝超阈值的大 V
    pulled := []Post{}
    for _, v := range bigVs {
        posts := QueryRecentPosts(ctx, v, 5) // 每大 V 取 5 条
        pulled = append(pulled, posts...)
    }

    // 3. 合并排序(按时间倒序)
    merged := mergeSort(pushed, pulled)
    return paginate(merged, page)
}

4.4 阈值与自适应

- 静态阈值:固定粉丝数(如 10 万)
- 动态阈值:按系统写入负载调整
- 触达补偿:大 V 发帖后,活跃粉丝的 Timeline 主动补写(增量 push)

一句话总结:混合架构的本质是「推拉分治」——按粉丝量把作者分流,普通用户靠推、大 V 靠拉,合并时各取所需。

五、热搜与榜单时间线

5.1 热搜榜

独立于关注关系,基于全站行为:
  热度分 = 互动量(赞/转/评)× 权重 - 时间衰减
  计算:实时流聚合 → 定期重算榜单
  Redis ZSet:hot_posts(score = 热度分)→ 取 Top N

5.2 榜单时间线

推荐时间线(非关注驱动):
  - 基础召回:热搜、热门作者、同兴趣内容
  - 粗排/精排:CTR 预估模型
  - 去重与多样性:已看内容过滤、作者去重

5.3 与关注时间线的差异

维度关注时间线热搜/推荐时间线
驱动关注关系全站行为/算法
更新新帖即新按热度重算
实时性强中(榜单周期性刷新)
生成推拉混合流聚合 + 排序

一句话总结:热搜与推荐时间线是「内容发现」的另一条腿——它们不靠关注关系,而靠热度与算法把好内容推给「还没关注它的人」。

六、分页与一致性

6.1 时间线分页

游标分页(推荐):
  SELECT ... WHERE created_at < :cursor ORDER BY created_at DESC LIMIT 20
  传入上一页最后一条的 created_at
避免 offset 分页的「翻页时新增导致的重复/跳页」

6.2 一致性挑战

- 拉取时新增帖子 → 游标分页天然处理
- 删除帖子 → 时间线里出现「已删除」占位 → 客户端过滤
- 取关后 → 推模式需从时间线移除该作者帖子(异步清理)
- 缓存与 DB 偏差 → 短 TTL + 主动失效

6.3 降级策略

Redis 故障 → 拉模式兜底(直接查 DB)
大 V 写放大 → 转拉模式/批量异步写入
时间线超长 → 裁剪 + 提示「查看更早请进主页」

一句话总结:时间线的健壮性在于「分页正确 + 降级兜底」——游标分页解决翻页问题,拉模式兜底解决缓存故障,裁剪解决无限增长。

七、总结

微型博客时间线系统的演进路径可以概括为:先建模、再选模式、后混合、终治理。关注关系用「有向边表 + 冗余计数」支撑;时间线生成在「推模式的写放大」与「拉模式的读放大」之间权衡;混合架构按粉丝量分流,让大 V 与普通用户各得其所;热搜榜单用流聚合与热度分补充「发现」能力;分页与降级保证体验稳定。

时间线是微型博客性能与体验的双核心——读得快,用户觉得流畅;写得稳,系统扛得住。把推拉混合做对,就掌握了社交平台最核心的架构能力。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「miniblog」更多文章

  1. 速率限制与防滥用:令牌桶、滑动窗口与分布式限流
  2. 通知系统:通知类型、聚合去重、多端同步与推送架构
  3. 评论与互动系统:评论树、@提及、点赞转发与互动计数一致性