惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

爱范儿
爱范儿
WordPress大学
WordPress大学
C
Check Point Blog
GbyAI
GbyAI
U
Unit 42
Google DeepMind News
Google DeepMind News
B
Blog RSS Feed
Blog — PlanetScale
Blog — PlanetScale
J
Java Code Geeks
I
InfoQ
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Hugging Face - Blog
Hugging Face - Blog
Vercel News
Vercel News
博客园 - 【当耐特】
美团技术团队
小众软件
小众软件
S
SegmentFault 最新的问题
Jina AI
Jina AI
阮一峰的网络日志
阮一峰的网络日志
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
The Cloudflare Blog
Last Week in AI
Last Week in AI
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
V
Visual Studio Blog

Aimee's Blog

秒杀怎么防机器人:验证码之外的几道防线 防薅羊毛怎么做:号码风控、设备指纹与分层策略 接口限流怎么选:固定窗口、滑动窗口、令牌桶 批量打包下载怎么设计:流式 ZIP 与异步任务 图片处理怎么做:异步处理与 CDN 实时参数 大文件上传怎么设计:分片、断点续传、秒传 报表可重跑的设计:快照、水位线与幂等 UV 统计为什么有误差:精确计数与近似计数怎么选 大数据量导出怎么做:流式写入与异步任务 排行榜的两类需求,难度差了一个数量级 内容审核怎么选:先审后发,还是先发后审 实时热榜怎么设计:Redis ZSet 与热度分值 互关状态怎么保持同步:双向关系的维护与缓存失效 并发点赞的计数设计:原子操作与最终对账 删了父评论,子评论怎么处理——先想清楚用户看到的是什么 定时消息任务触发了两次 同一条消息推送了三次 活动结束了,用户还在收短信 接到注销需求,先问两个问题 微服务与服务拆分:何时拆、怎么拆 异步与事件驱动架构:把协作从「打电话」改成「发消息」 高可用设计:怎么让系统尽量不宕机 可扩展性设计:怎么让系统加机器就能扛更多 缓存架构:多级缓存怎么搭 高并发三板斧:限流、熔断、降级 架构设计到底在设计什么 —— 从单体到微服务的演进 服务成本账:一个服务一个月烧多少钱 可观测性:线上出问题怎么查 API 设计:好接口长什么样 消息队列:为什么要 MQ,以及丢失、重复、顺序怎么破
消息量大,写扩散还是读扩散——接到这个需求先问清楚规模
Aimee · 2026-08-03 · via Aimee's Blog

消息量大,写扩散还是读扩散——接到这个需求先问清楚规模

做站内信、Feed 流之前,先问一个问题:这个平台上,关注量最大的用户大概有多少粉丝?

答案决定了架构方向。


写扩散(Fanout on Write)

用户 A 发了一条动态,系统立刻把这条消息写入所有关注者的收件箱:

-- A 发动态,假设 A 有 500 个粉丝
INSERT INTO inbox (user_id, message_id, created_at) 
VALUES 
  (follower1_id, messageId, now),
  (follower2_id, messageId, now),
  ...  -- 500 条

优点:读取时极快,直接按 user_id 查收件箱,不需要联表,分页简单。

缺点:大 V 发一条动态,可能写几百万条收件箱记录。写入慢、存储放大严重、如果是同步写更会卡住发布接口。

写扩散适合:粉丝数量级在万级以下的平台,写入压力可控。


读扩散(Fanout on Read)

用户打开收件箱时,系统去查"我关注的所有人"发的动态,聚合后展示:

-- 查我关注的人
SELECT followed_id FROM follow WHERE follower_id = ? 

-- 再去内容表查这些人的动态
SELECT * FROM post 
WHERE author_id IN (followed_id_list) 
ORDER BY created_at DESC 
LIMIT 20

优点:发布动态时无需写扩散,存储节省,大 V 发布不会产生写入风暴。

缺点:读取时计算量大,关注人数多时 IN 子句巨大,查询慢;实时性差,要维护缓存。

读扩散适合:大 V 粉丝数量级在百万以上的平台,重心是削减写入开销。


混合方案:实际系统的选择

单纯的读扩散或写扩散各有缺陷,主流大型平台(微博等)用混合方案:

  • 普通用户:写扩散。粉丝数量少,写入放大可接受,读取快。
  • 大 V 用户(粉丝数超过某个阈值,如 10 万):读扩散。用户打开 Feed 时,在已有写扩散结果的基础上,再去查这些大 V 的最新动态,合并排序后展示。
// 伪代码:混合拉取
List<Post> normalFeed = inboxDao.query(userId, page);   // 来自写扩散收件箱
List<Long> bigVIds = followDao.getBigVFollowing(userId);
List<Post> bigVFeed = postDao.queryLatest(bigVIds, after: lastReadTime); // 实时拉
return mergeSortByTime(normalFeed, bigVFeed);

缺点是合并排序增加了复杂度,大 V 关注数量多了也有性能压力,要限制"大 V 关注上限"或做缓存。


收件箱的数据结构

无论哪种方案,收件箱的存储通常用 Redis Sorted Set,score 是时间戳:

ZADD inbox:{userId} {timestamp} {messageId}
ZREVRANGE inbox:{userId} 0 19  // 拿最新 20 条

优点是天然排序、O(log N) 插入、范围查询快。缺点是内存占用,一般只保留最近 N 条(如最近 1000 条),超出的走数据库归档查询。


动手写代码前先量一下:平台最大粉丝数是多少,DAU 级别是多少,读写比是多少。这些数字决定了哪种方案的边界在哪里,不是技术偏好,是规模决定的。