大规模 Fanout:Push vs. Pull 策略在分布式系统
发布: (2025年12月21日 GMT+8 20:34)
6 min read
原文: Dev.to
Source: Dev.to
现代系统之所以失败,并不是因为它们无法存储数据,而是因为它们无法在恰当的时间将正确的数据交付给正确的消费者。
Fanout(广播)问题是分布式系统中最基本的挑战之一,尤其在社交媒体信息流、通知系统、消息平台和事件驱动架构中表现突出。
当单个事件发生时,如何高效地将其传递给数百万的消费者?主要有两种策略:
- 写时广播(Push)
- 读时广播(Pull)
两者都是有效且被广泛使用的方案,但各自都有严重的权衡取舍。
Fanout 问题
- 一次写入 → 多次读取
- 目标:向所有关注者提供低延迟、个性化的动态,即使用户拥有数百万关注者。
朴素的解决方案——“只把帖子发送给所有人”——在以下方面会崩溃:
- 写放大
- 存储爆炸
- 热分区
- 延迟峰值
写时推送(Fanout‑on‑Write)
当用户创建内容时,系统 立即将 内容推送到每个关注者的预计算时间线。
工作原理
User posts → System pushes post to N follower feeds
优势
- 超快速的时间线读取
- 可预测的读取延迟
- 简单的读取查询(单次查找)
成本 / 权衡
- 巨大的写放大(例如,拥有 1 亿粉丝的名人发布一条内容就会产生 1 亿条时间线写入)
- 高存储使用量
- 热门用户可能导致系统过载
Source:
Fanout‑on‑Read(拉取)
当用户打开他们的动态时,系统 拉取 他们关注的账号的最新帖子,在读取时进行合并、排序和过滤。
工作原理
User opens feed → System pulls content from many sources
优势
- 写入成本极低(没有针对每个粉丝的写入)
- 对拥有极高粉丝数的用户(名人)可良好扩展
- 存储效率高
成本 / 权衡
- 读取更复杂,成本更高
- 与推送相比读取延迟更高
- 更难实现高效缓存
混合 Fanout 策略
纯推送或纯拉取在大规模系统中很少出现;大多数平台采用 混合 方法。
选择性 Fanout
- 对普通用户使用 Push(受限 Fanout)
- 对名人或高 Fanout 内容使用 Pull
为什么混合有效
- 前约 0.01 % 的用户产生极端 Fanout;如果对他们的帖子进行推送会导致存储和队列崩溃。
- 预计算、排序模型和 Feed 物化会在最能产生价值的地方使用。
典型组成
- 预计算的 Feed 条目(Push)
- 动态获取的内容(Pull)——例如,推荐帖子、广告、短视频
行业特定的混合方案
- 职业图谱(规模较小、密度较高)→ 主要使用 Push
- 兴趣类 Feed(规模大、稀疏)→ 主要使用 Pull,结合推荐池和机器学习排序的候选集合
超越社交动态的 Fanout
| 策略 | 典型使用场景 | 关键特性 |
|---|---|---|
| 推送 Fanout | 移动推送通知、紧急警报、交易警报、缓存失效、安全补丁 | 使用消息队列或基于主题的发布/订阅,需要限流,提供低延迟投递 |
| 拉取 Fanout | Kafka 消费者、Prometheus 抓取、对不常访问资产的惰性获取 | 消费者从分区拉取,支持背压、可重放性和故障隔离 |
| 混合 | 价格行情(推送)+ 历史数据(拉取),紧急开关更新(推送)+ 定期一致性检查(拉取) | 在带宽、延迟和存储约束之间取得平衡 |
如何选择合适的 Fanout 策略
在以下情况下使用 Fanout‑on‑Write(Push):
- 读取延迟必须最小化
- Fanout 大小受限(例如,大多数用户)
- 存储成本低且充足
- 可预测的性能至关重要
在以下情况下使用 Fanout‑on‑Read(Pull):
- Fanout 大小不受限制(例如,名人)
- 写入必须保持低成本
- 消费者需求波动剧烈
- 需要背压来保护下游服务
混合方式适用场景:
- 需要对大多数用户提供低延迟读取,同时还能可扩展地处理极端 Fanout
- 可以对部分内容进行预计算,其余内容动态拉取
把 Fanout 想象成物流:
- Push = 送货上门 – 快速、可预测,但成本更高。
- Pull = 仓库自提 – 更便宜且灵活,但速度较慢。
- Hybrid = 亚马逊 Prime – 为高优先级商品提供快速配送,同时以成本有效的方式处理其余商品。