在这一章中,你被要求设计一个新闻推送系统。什么是动态消息?根据Facebook的帮助页面,“新闻动态是在你的主页中间不断更新的故事列表。News Feed包括状态更新、照片、视频、链接、应用活动,以及你在Facebook上关注的人、页面和群组的“赞”[1]。这是一个很常见的面试问题。类似的问题有:设计Facebook的消息推送、Instagram的动态推送、Twitter的时间轴等等。
第一步-理解需求并且建立设计范围
第一组澄清问题是为了理解面试官让你设计新闻推送系统时的想法。至少,您应该弄清楚要支持哪些特性。这里有一个面试官和面试官互动的例子:
应聘者:这是一个移动应用程序吗?还是网页应用?还是两个? 面试官:二者 求职者:什么是重要的特点? 面试:用户可以发布一篇文章,并在新闻提要页面上看到她的朋友的文章。
候选人:新闻提要是按时间倒序排序,还是按主题分数等特定顺序排序?例如,来自你亲密朋友的帖子得分更高。
采访者:为了简单起见,让我们假设提要是按时间倒序排序的。
候选人:一个用户可以有多少朋友?
面试官:5000
应聘者:流量是多少?
面试官:1000万DAU
候选人:feed是否可以包含图像、视频或文本?
采访者:它可以包含媒体文件,包括图像和视频。
现在您已经收集了需求,我们将集中精力进行系统设计。
第二步-顶层设计
设计分为两个流程:提要发布和新闻提要构建。
-
Feed发布:当用户发布文章时,相应的数据被写入缓存和数据库。一个帖子被添加到她朋友的新闻动态中。
-
信息流构建:为了简单起见,让我们假设信息流是通过按时间倒序聚合好友的帖子来构建的。
消息推送API
新闻提要api是客户机与服务器通信的主要方式。这些api是基于HTTP的,允许客户端执行操作,包括发布状态、检索新闻提要、添加好友等。我们将讨论两个最重要的API:消息发布API和消息检索API。
消息发布API
要发布一个帖子,将向服务器发送一个HTTP post请求。API如下所示:POST /v1/me/feed参数:
-
content: content是文章的文本。
-
auth_token:用于认证API请求。
消息检索API
获取新闻提要的API如下所示:GET /v1/me/feed参数:
- auth_token:用于对API请求进行身份验证。
提要发布
提要发布流程的高级设计如图11-2所示。
-
用户:用户可以在浏览器或移动应用上查看新闻提要。用户通过API: /v1/me/feed?content=Hello&auth_token={auth_token}发布童谣
-
负载均衡器:将流量分配到web服务器。
-
Web服务器:Web服务器将流量重定向到不同的内部服务。
-
Post服务:在数据库和缓存中持久化Post。
-
Fanout服务:向好友推送新内容。新闻提要数据存储在缓存中以便快速检索。
-
通知服务:通知好友有新内容可用,并发送推送通知。
消息提要 建立
在本节中,我们将讨论如何在幕后构建新闻提要。高层设计如图11-3所示:
-
用户:用户发送请求来检索她的新闻提要。请求看起来像这样:/ v1/me/feed。
-
负载均衡器:负载均衡器重定向流量到web服务器。
-
Web服务器:Web服务器将请求路由到newsfeed服务。
-
消息提要服务:消息提要服务从缓存中获取消息提要。
-
消息提要缓存:存储呈现消息提要所需的消息提要id。
底层实现
高级设计简要介绍了两个流程:提要发布和新闻提要构建。
在这里,我们将更深入地讨论这些主题。
提要发布底层实现
提要发布的详细设计如图11-4所示。我们已经讨论了高层设计中的大部分组件,我们将重点讨论两个组件:web服务器和fanout服务。
web 服务器
除了与客户端通信外,web服务器还强制执行身份验证和速率限制。
只有使用有效auth_token登录的用户才允许发布帖子。该系统限制了用户在一定时间内可以发布的帖子数量,这对防止垃圾邮件和滥用内容至关重要。
广播服务
Fanout是向所有朋友发送帖子的过程。两种类型的fanout模型是:写时的fanout(也称为推型)和读时的fanout(也称为拉型)。这两种模型各有优缺点。我们解释了它们的工作流程,并探索了支持我们系统的最佳方法。
写Fanout。使用这种方法,新闻提要是在写入时预先计算的。新帖子发布后,会立即发送到好友缓存中。
优点:
-
动态消息是实时生成的,可以立即推送给朋友。
-
获取消息提要是快速的,因为消息提要是在写入时预先计算的。
缺点:
-
如果用户有很多好友,那么获取好友列表并为所有好友生成消息就会变得缓慢且耗时。这被称为热键问题。
-
对于不活跃的用户或很少登录的用户,预计算消息源会浪费计算资源。
读Fanout。新闻提要是在阅读时间生成的。这是一个按需模式。
当用户加载她的主页时,会拉出最近的帖子。
优点:
-
对于不活跃的用户或很少登录的用户,阅读时的扇出效果更好,因为它不会在他们身上浪费计算资源。
-
数据不会推送给朋友,所以没有热键问题。 缺点:
-
获取消息提要很慢,因为消息提要没有预先计算。
我们采用混合方法来获得两种方法的优点并避免它们中的缺陷。
因为快速获取新闻是至关重要的,所以我们为大多数用户使用了推送模式。
对于名人或有很多朋友/追随者的用户,我们让追随者按需拉新闻内容,以避免系统过载。一致性哈希是一种缓解热键问题的有用技术,因为它有助于更均匀地分发请求/数据。
让我们仔细看看fanout服务,如图11-5所示。
fanout服务的工作原理如下: 1.单击“确定”。从图形数据库中获取好友id。图形数据库适合于管理朋友关系和朋友推荐。有兴趣了解这一概念的读者可以参考参考资料[2]。
-
从用户缓存中获取好友信息。然后系统会根据用户设置过滤掉好友。例如,如果你把某人设为静音,即使你们还是朋友,她的帖子也不会出现在你的动态消息中。帖子不显示的另一个原因是,用户可以有选择地与特定的朋友分享信息,或者对其他人隐藏信息。
-
发送好友列表和新帖子ID到消息队列。
-
Fanout工作程序从消息队列中获取数据,并将新闻提要数据存储在新闻提要缓存中。您可以将新闻提要缓存看作一个映射表。
每当有新的帖子发布时,它将被添加到新闻提要表中,如图11-6所示。如果我们将整个用户和post对象存储在缓存中,内存消耗会变得非常大。因此,只存储id。为了保持较小的内存大小,我们设置了一个可配置的限制。用户在新闻推送中滚动浏览数千篇帖子的可能性很小。大多数用户只对最新的内容感兴趣,所以缓存缺失率很低。
- 在新闻提要缓存中存储。图11-6展示了一个在缓存中新闻提要的示例。
消息提要检索实现
图11-7展示了新闻提要检索的详细设计。
如图11-7所示,媒体内容(图片、视频等)存储在CDN中,便于快速检索。让我们看看客户端是如何检索新闻提要的。
-
用户发送一个请求来检索她的新闻提要。请求看起来像这样:/v1/me/feed 2。负载均衡器将请求重新分配到web服务器。
-
Web服务器调用新闻提要服务来获取新闻提要。
-
新闻提要服务从新闻提要缓存中获取列表帖子id。
-
用户的新闻提要不仅仅是一个提要id列表。它包含用户名,头像,帖子内容,帖子图像等。因此,新闻提要服务从缓存(用户缓存和帖子缓存)中获取完整的用户和帖子对象,以构造完整的新闻提要。
-
完整的新闻提要以JSON格式返回给客户端进行呈现。
Cache 架构
缓存对于新闻提要系统是极其重要的。如图11-8所示,我们将缓存层划分为5层。
-
News Feed:它存储新闻Feed的id。
-
内容:它存储每一个帖子的数据。热门内容存储在热缓存中。
-
Social Graph:它存储用户关系数据。
-
动作:它存储有关用户是否喜欢帖子,回复帖子或对帖子采取其他操作的信息。
-
计数器:它存储计数器喜欢,回复,追随者,以下等。
第四步-总结或打包
在本章中,我们设计了一个新闻推送系统。我们的设计包含两个流程:提要发布和新闻提要检索。
就像任何系统设计面试问题一样,没有完美的系统设计方法。每个公司都有其独特的限制,你必须设计一个系统来适应这些限制。
理解设计和技术选择之间的权衡是很重要的。如果还有几分钟,您可以讨论可伸缩性问题。为了避免重复讨论,下面只列出了高级别的谈话要点。 扩展数据库:
- 垂直扩展与水平扩展
- SQL与NoSQL
- 主从复制
- 读取副本
- 一致性模型
- 数据库分片 其他要点:
- 保持web层无状态
- 尽可能多地缓存数据
- 支持多个数据中心
- 丢失消息队列的几个组件
- 监控关键指标。
- 例如,高峰时段的QPS和用户刷新新闻feed时的延迟都值得关注。