系统设计 Lab
社交 news feed 的成败系于 fan-out 的取舍:写入时把帖子推进每个 follower,还是读取时拉取再合并。
调节日活用户、发帖速率、平均 follower 数和 celebrity follower 数、feed 读取量、feed 大小和 ranking 成本。设计会从读取时 pull 合并,演进到带 feed cache 的 push fan-out、对 celebrity 特殊处理的混合方案、ranking pipeline,最后是跨 region 的 sharded store。
分步讲解
一步一步把它想清楚
要点
常规演进场景
从左到右点击,就是预设的演示路径。每张卡片都会改变 workload 输入。
推荐形态
当前架构路径
Client
Client 创建帖子,并加载首页 feed 时间线
Edge / API
API gateway 把帖子写入和 feed 读取路由到对应的 service
Feed service 提供首页 feed,读 cache 并合并额外内容
Fan-out + cache
Fan-out worker 把每条新帖子推进每个 follower 的 feed cache
Feed cache 保存预计算好的 feed,让读取只需一次 cache fetch
Store
Post store 持久保存每条帖子,随量增长做 shard
Follow graph follower 和 followee 的边,用来算 fan-out 的目标
Ranking / 异步
Ranking service 按预测的相关性给 feed item 打分并重排
Celebrity merge 在读取时合并超大账号的帖子,而不是做 fan-out
瓶颈
写 fan-out 放大
Celebrity fan-out 成本
Feed cache 内存
读取合并成本
帖子写 throughput
为什么会变
决策权衡
有出处支撑的规则
这些是模型背后那些经得起时间考验的 system design 论断。而 slider 的具体阈值,则被刻意标注为教学用的假设。
Feed 通常用 fan-out-on-write,对 celebrity 用混合方案
经典写法在写入时把帖子 push 给 follower 来预计算 feed,再对超高 follower 数的账号特殊处理:在读取时合并它们的帖子。
System Design PrimerRedis 用 sorted list 支撑每个用户的 feed cache
in-memory 的有序结构保存每个用户的 feed,于是一次 feed 读取就是一次快速的 range fetch,而不是一次 database join。
Redis DocsKafka 把帖子写入和繁重的 fan-out 工作解耦
一个持久 log 让帖子写入能立刻返回,而 fan-out worker 异步消费 event,把内容 push 进 follower 的 feed。
Apache KafkaDynamoDB 风格的 partition 扩展 post store 和 graph store
当数据集撑爆单机时,按 key 给帖子和 follow 边做 partition,把 storage 和写 throughput 分散到多个 node 上。
AWS DynamoDB教学用假设
- 写 fan-out 建模为 posts/s 乘以平均 follower 数;读 fan-out 建模为 feed reads/s 乘以合并的 followee 数。
- 单 node 的 fan-out、cache 和 store 预算是保守的教学数字,不是厂商上限。
- Celebrity 混合方案把最大的账号从写 fan-out 中移除,改成每次读取时加一点小合并。