是什么
写扩散(fan-out on write)是发布时就把数据分发到所有需要它的地方,读的时候一次取回;读扩散(fan-out on read)是发布只写一处,读的时候再把多路数据合并起来。两者是同一份成本在读写两端的取舍:写扩散把代价压在发布路径上,读扩散把代价压在读取路径上,混合方案按活跃度或粉丝规模分流。
基本信息
| 写扩散 | 发布时写向所有订阅方收件箱:读快;粉丝多时写放大明显,不活跃用户白占存储 |
|---|---|
| 读扩散 | 读时合并关注对象的最新内容:写简单;关注多时聚合慢,缓存压力集中在读侧 |
| 混合 | 按活跃度或粉丝阈值分流:活跃用户预先推送,长尾用户读时合并 |
| 典型组件 | 收件箱用 Redis 列表或有序集合存 ID,正文单独存储,合并时再按 ID 取 |
典型使用场景
- 社交时间线:粉丝量小的作者直接推给粉丝,粉丝量大的账号由读者自己拉取后合并
- 搜索结果预拼:把多表数据提前拼成宽文档写入搜索引擎,牺牲写入换取检索速度
- 报表预聚合:用物化视图或汇总表在写入或调度时算好指标,查询只做过滤
- 通知分发:一条消息要送达多端多用户时,决定是落一次待拉取的收件箱,还是逐个写入
- 边缘缓存预热:把源站内容提前推到各边缘节点,本质是把一次源站读换成 N 次分布式写
混合时间线的分流逻辑
混合时间线的分流逻辑
发布事件 -> 读取作者粉丝规模与活跃度
|- 粉丝少且活跃:写给每个粉丝的收件箱(写扩散,例如阈值 500 以内)
|- 粉丝多:只写作者自己的时间线,读者侧拉取(读扩散)
|- 不活跃粉丝:不推送,减少无效写入与存储
读时间线 = 合并(收件箱最新 N 条, 关注的大账号最新 N 条) -> 按时间排序 -> 截断
缓存策略 = 只缓存活跃用户的合并结果,冷用户每次重算两种扩散逐项对比
| 维度 | 写扩散(推) | 读扩散(拉) |
|---|---|---|
| 写入成本 | 乘以订阅方规模,大号发一条就是海量写 | 一条内容只写一次 |
| 读取成本 | 一次查询即可,延迟低且稳定 | 每次都要合并多路来源并排序截断 |
| 存储占用 | 同一内容存多份,热门内容重复明显 | 只存一份,另外依赖缓存扛读 |
| 修改与删除 | 要回写所有副本,容易出现残留 | 改一处即可,天然一致 |
| 可见性 | 异步推送被积压时读到的内容滞后 | 发布即生效,读时一定能看到 |
搜索与列式库里的同类取舍更容易被忽略:把宽表预拼好灌进搜索引擎、把聚合结果写进物化视图、把统计提前算进汇总表,本质都是用写入成本换读取成本,判断标准是读写比与数据变更频率。
规模变化会让方案失效:上线时粉丝数上限很低,纯写扩散很舒服;等出现百万粉丝账号,同一个设计就会在发布路径上产生尖峰。分流阈值应当落成配置项,而不是写死在代码里。