社交信息流系统设计
社交信息流案例要支持什么?
- 功能: 发布含文本或媒体的短帖、关注用户、浏览关注对象的信息流并搜索内容;
- 扩展: 转发、收藏,以及指标与分析;
- 质量目标: 高可用、低延迟和可扩展,重点区分内容写入与大规模信息流读取;
- 产品边界: 原文以 Twitter 和 280 字符举例,这里沿用教学名称,不把它当当前产品限制;
如何重算帖子和媒体容量?
- 假设: 总用户 10 亿,DAU 为 2 亿,每人每天发布 5 条,10% 附带一个平均 50 KB 媒体,记录按 100 B;
| 项目 | 计算 | 结果 |
|---|---|---|
| 新帖子 | 2 亿 × 5 | 10 亿/日 |
| 平均发布 | 10 亿 / 86400 | 11574/s,约 12K/s |
| 媒体数量 | 10 亿 × 10% | 1 亿/日 |
| 文本与元数据 | 10 亿 × 100 B | 100 GB/日 |
| 媒体新增 | 1 亿 × 50 KB | 5 TB/日 |
| 十年逻辑容量 | 5.1 TB × 3650 | 18.615 PB,约 19 PB |
| 平均入口 | 5.1 TB / 86400 | 59 MB/s,约 60 MB/s |
- 原文修正: 汇总表的 1 亿 DAU 与正文及计算不符,此处统一采用正文的 2 亿;
- 读流量缺口: 12K/s 只是发帖平均速率,尚未包含信息流刷新、搜索、扇出和媒体下载;
- 字节假设: 100 B 只是一项演示均值,完整多语言帖子、索引和属性可能更大,应重新采样;
哪些数据构成社交关系和信息流?
| 表 | 关键字段 | 责任 |
|---|---|---|
| users | id、name、email、dob、createdAt | 用户资料 |
| tweets | id、userID、type、content、createdAt | 帖子事实 |
| followers | followerID、followeeID | 有方向的关注关系 |
| favorites | userID、tweetID、createdAt | 收藏关系 |
| feeds | id、userID、updatedAt | 用户信息流元数据 |
| feeds_tweets | feedID、tweetID | 信息流中的候选帖子 |
- 唯一性: 关注和收藏关系应有复合唯一约束,使重复请求不生成多份关系;
- 事实与投影: 帖子是权威内容,feed 中可只存帖子 ID 与排序信息,避免复制所有正文;
- 存储选择: 关系与帖子可按各自访问模式选择关系库或分布式存储,不必所有表共用一个物理库;
API 怎样表达发布、关注和信息流读取?
postTweet(userID, content, mediaURL?, requestID?) → tweetID
follow(followerID, followeeID) → result
unfollow(followerID, followeeID) → result
getNewsfeed(userID, cursor?, limit?) → TweetPage
- 身份: userID 与 followerID 必须由认证上下文验证,防止替别人发帖或关注;
- 幂等: 重复发帖请求应返回同一结果,关注和取消关注按目标关系状态处理;
- 分页补充: 原文返回全部 Tweet[],实际需要稳定游标,避免新帖插入导致翻页漏项或重复;
- 媒体引用: 上传与发布分开,发帖时校验引用的媒体确实属于当前用户且已可用;
服务怎样围绕内容与信息流分工?
- 用户服务: 管理身份与关系数据,按设计可进一步拆分关注图;
- 帖子服务: 负责发布、删除、收藏等内容操作,是帖子状态的权威入口;
- 信息流服务: 负责候选生成、排序和读取优化;
- 搜索服务: 建立文本索引并处理检索与热点查询;
- 媒体服务: 上传和处理媒体,使用对象存储与 CDN,通用路径见 即时通信;
- 通知与分析服务: 消费业务事件,分别承担设备通知和指标处理,避免阻塞发帖;
拉取模型如何在读取时生成信息流?
用户刷新 → 获取关注对象 → 读取各对象近期帖子
→ 合并候选 → 排序 → 分页返回
- Fan-out on read: 发布时只保存帖子,读取时再把关注对象的内容组合起来;
- 收益: 减少预计算与无效写入,不为从不活跃的粉丝持续生成 feed;
- 代价: 关注对象很多时读取放大和合并成本高,延迟更难稳定;
- 优化: 可缓存候选或预先生成部分结果,但需要明确新内容进入视图的时效;
推送模型如何在发布时更新粉丝信息流?
帖子提交 → 扇出任务 → 获取粉丝列表
→ 将 tweetID 写入各粉丝 feed
- Fan-out on write: 把生成成本移到写侧,用户读取时只需访问已经准备好的 feed;
- 收益: 普通用户的信息流读取更快,减少每次请求临时联接;
- 代价: 粉丝数越大写放大越严重,一条名人帖子可能生成海量更新;
- 语义: 这里的 push 是写入服务端 feed,不等于一定实时向每个手机推送;
- 故障处理: 扇出任务需要幂等和进度记录,重试不能重复插入同一帖子,积压时可显示较旧视图;
混合模型怎样处理拥有海量粉丝的作者?
- 混合策略: 普通作者使用写时扇出,名人等大粉丝作者只保存源帖子,读取时再合并;
- 决策依据: 依据粉丝数量、活跃比例和发布频率估算写放大,阈值应通过实际负载确定;
- 读取路径: 合并预生成 feed 与名人近期内容,再去重并统一排序;
- 代价: 同时维护两种候选来源,作者跨过阈值时要设计迁移以免漏帖或重复;
排序如何让候选内容更符合用户兴趣?
- 排序目标: 在候选集合中根据相关性、时间和互动信号决定展示顺序;
- 教学公式: 原文用历史 EdgeRank 思想示意
分数 = 亲密度 × 内容权重 × 时间衰减,不是当前平台算法声明; - 亲密度: 用户与作者越常互动,可能越相关;
- 内容权重: 不同互动或内容类型赋予不同重要性,需防止单纯追求互动量带来偏差;
- 时间衰减: 旧内容的权重逐渐降低,使新内容有机会进入视图;
- 复杂模型: 可以使用机器学习组合更多特征,但必须有质量、延迟和可解释的评估目标;
转发怎样关联原始帖子?
- 最小模型: 新建帖子记录,把 type 标为转发,并保存 originalTweetID 或原文示例中的 content 引用;
- 独立事件: 转发有自己的作者与时间,原帖内容仍由原帖记录管理;
- 扩展: 也可建立专门转发表,方便独立约束重复转发和查询关系;
- 删除与权限: 原帖删除或变为不可见后,转发渲染必须重新检查,不能借冗余内容绕过原帖权限;
搜索怎样从帖子数据派生?
- 文本搜索: 把可搜索帖子异步索引到专用搜索引擎,原文示例为基于 Lucene 的 Elasticsearch;
- 索引延迟: 发布成功不代表立即可搜索,应定义刷新时效,删除和权限变更也需要同步;
热点怎样从近期搜索数据生成?
- 热点计算: 统计最近 N 秒内搜索词、标签和主题频率,每 M 秒刷新候选热点;
- 个性化: 可以把用户兴趣用于热点排序,但热点统计窗口与推荐权重应明确区分;
共同关注如何辅助用户推荐?
- 社交图: 用户为节点,关注为有向边,比较邻接集合可发现共同关注或潜在关系;
- 候选筛选: 从图关系找候选,再结合互动、兴趣或反馈排序;
- 工具选择: 图数据库如 Neo4j、ArangoDB 可辅助遍历,但简单共同邻居计算不必强制引入图数据库;
- 概念边界: 单向关注不等于双向朋友关系,推荐规则需要明确比较入边还是出边;
缓存与分区怎样支持海量读取?
- 分区: 用户关系可按 userID,帖子可按作者或帖子键及时间组织,feed 按接收用户组织以适配读取;
- 缓存: 热帖对象和 feed 页可采用有明确 TTL 的缓存,所谓前 20% 热帖应由真实访问分布验证;
- 新鲜度: 允许 feed 排序短暂滞后,不应允许已删除或无权访问的内容长期泄露;
- 媒体: 大对象由对象存储和 CDN 承担,API 只返回必要的元数据及受控引用;
- 分析: 原文用 Kafka 收集事件、Spark 做规模化分析,业务和统计应有独立失败边界;
故障和热点怎样影响最终体验?
- 服务与入口: 多实例和健康路由承接节点故障,服务间超时、重试和熔断需有总体预算;
- 数据与缓存: 副本提高读取容量,缓存失效时限制回源并发,避免热点帖子压垮数据库;
- 通知: 重试、去重和设备平台分发采用 即时通信的离线通知流程,不能把代理能力等同于用户必然收到;
- 媒体成本: 压缩与派生格式降低存储和传输,但要维护原件及派生文件的生命周期;
- 验证: 覆盖名人发帖、扇出中断、重复事件、原帖删除、关注变化和游标翻页时的新帖插入;