Skip to content

Commit

Permalink
feat: chapter 11
Browse files Browse the repository at this point in the history
  • Loading branch information
honkinglin committed Oct 18, 2024
1 parent 6bc2b78 commit 870d2f9
Show file tree
Hide file tree
Showing 12 changed files with 257 additions and 58 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

- 第10章:设计一个通知系统

- 第11章:设计一个新闻推送系统
- 第11章:设计一个新闻订阅系统

- 第12章:设计一个聊天系统

Expand Down
6 changes: 3 additions & 3 deletions docs/CHAPTER-1-SCALE-FROM-ZERO-TO-MILLIONS-OF-USERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ GET /users/12 – Retrieve user object for id = 12

- 如果主数据库离线,则将提升一个从数据库成为新的主数据库。所有数据库操作将暂时在新的主数据库上执行。将立即用一个新的从数据库替换旧的,从而实现数据复制。

在生产系统中,提升新的主数据库更为复杂,因为从数据库中的数据可能不是最新的。缺失的数据需要通过运行数据恢复脚本来更新。虽然一些其他的复制方法,如多主复制和循环复制,可以提供帮助,但这些设置更加复杂,讨论超出了本书的范围。有兴趣的读者应参考所列的参考资料[[4]](https://en.wikipedia.org/wiki/Multi-master_replication)[[5]](https://dev.mysql.com/doc/refman/5.7/en/mysql-cluster-replication-multi-master.html)
在生产系统中,提升新的主数据库更为复杂,因为从数据库中的数据可能不是最新的。缺失的数据需要通过运行数据恢复脚本来更新。虽然一些其他的复制方法,如多主复制和循环复制,可以提供帮助,但这些设置更加复杂,讨论超出了本书的范围。有兴趣的读者应参考所列的参考文献[[4]](https://en.wikipedia.org/wiki/Multi-master_replication)[[5]](https://dev.mysql.com/doc/refman/5.7/en/mysql-cluster-replication-multi-master.html)

图1-6展示了在添加负载均衡器和数据库复制后系统的设计。

Expand Down Expand Up @@ -158,7 +158,7 @@ cache.get('myKey')
## 内容分发网络 - CDN (Content delivery network - CDN)
CDN 是一个地理分散的服务器网络,用于交付静态内容。CDN 服务器缓存静态内容,如图像、视频、CSS、JavaScript 文件等。

动态内容缓存是一个相对较新的概念,超出了本书的范围。它使基于请求路径、查询字符串、Cookie 和请求头的 HTML 页面能够被缓存。有关更多信息,请参考参考资料[[9]](https://aws.amazon.com/cloudfront/dynamic-content/)中提到的文章。本书主要集中在如何使用 CDN 缓存静态内容。
动态内容缓存是一个相对较新的概念,超出了本书的范围。它使基于请求路径、查询字符串、Cookie 和请求头的 HTML 页面能够被缓存。有关更多信息,请参考参考文献[[9]](https://aws.amazon.com/cloudfront/dynamic-content/)中提到的文章。本书主要集中在如何使用 CDN 缓存静态内容。

CDN 的高层工作原理如下:当用户访问网站时,离用户最近的 CDN 服务器将提供静态内容。直观地说,用户离 CDN 服务器越远,网站加载的速度就越慢。例如,如果 CDN 服务器位于旧金山,那么洛杉矶的用户获取内容的速度会比欧洲的用户快。图 1-9 是一个很好的例子,展示了 CDN 如何改善加载时间。

Expand Down Expand Up @@ -340,7 +340,7 @@ CDN 的高层工作原理如下:当用户访问网站时,离用户最近的

恭喜您走到这里!现在给自己一个鼓励,干得好!

## 参考资料
## 参考文献

[1] Hypertext Transfer Protocol: https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol

Expand Down
14 changes: 7 additions & 7 deletions docs/CHAPTER-10-DESIGN-A-NOTIFICATION-SYSTEM.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

![图10-1](/f10-1.png)

## 步骤 1 - 理解问题并确定设计范围
## 第 1 步 - 理解问题并确定设计范围
构建一个每天发送数百万条通知的可扩展系统并非易事。这需要对通知生态系统有深入的理解。这个面试问题有意设置得开放且模糊,需要你主动提问以澄清需求。

**候选人**:系统支持哪种类型的通知?
Expand All @@ -27,7 +27,7 @@
**候选人**:每天发送多少条通知?
**面试官**:每天发送 1000 万条移动推送通知、100 万条短信和 500 万封电子邮件。

## 步骤 2 - 提出高层设计并达成共识
## 第 2 步 - 提出高层设计并达成共识
本节展示了支持多种通知类型的高层设计:iOS 推送通知、Android 推送通知、短信消息和电子邮件。其结构如下:

- 不同类型的通知
Expand Down Expand Up @@ -179,7 +179,7 @@ POST https://api.example.com/v/sms/send
5. 工作节点将通知发送到第三方服务。
6. 第三方服务将通知发送到用户设备。

## 步骤 3 - 设计深入探讨
## 第 3 步 - 设计深入探讨

在高层设计中,我们讨论了不同类型的通知、联系信息的收集流程以及通知发送/接收的流程。我们将在以下几个方面进行深入剖析:
- 可靠性
Expand All @@ -197,7 +197,7 @@ POST https://api.example.com/v/sms/send
#### 接收者会收到通知恰好一次吗?

简短的回答是不会。虽然大多数情况下通知是恰好一次送达,但分布式的特性可能导致重复通知。为了减少重复发生的情况,我们引入了去重机制,并仔细处理每个失败案例。以下是一个简单的去重逻辑:
当通知事件首次到达时,我们通过检查事件ID来判断是否之前已见过。如果之前见过,则将其丢弃。否则,我们将发送该通知。有关为什么我们无法实现恰好一次传递的原因,感兴趣的读者可以参考参考资料 [[5]](https://bravenewgeek.com/you-cannot-haveexactly-once-delivery/)
当通知事件首次到达时,我们通过检查事件ID来判断是否之前已见过。如果之前见过,则将其丢弃。否则,我们将发送该通知。有关为什么我们无法实现恰好一次传递的原因,感兴趣的读者可以参考参考文献 [[5]](https://bravenewgeek.com/you-cannot-haveexactly-once-delivery/)

### 附加组件和考虑因素
我们已经讨论了如何收集用户联系信息、发送和接收通知。一个通知系统远不止于此。这里我们讨论额外的组件,包括模板重用、通知设置、事件跟踪、系统监控、限流等。
Expand Down Expand Up @@ -228,7 +228,7 @@ POST https://api.example.com/v/sms/send
当第三方服务未能成功发送通知时,该通知将被添加到消息队列中进行重试。如果问题持续存在,则会向开发人员发送警报。

#### 推送通知的安全性 (Security in push notifications)
对于iOS或Android应用,`appKey``appSecret`用于保护推送通知API的安全 [[6]](https://cloud.ibm.com/docs/services/mobilepush?topic=mobile-pushnotification-security-in-push-notifications)。只有经过身份验证或验证的客户端才能使用我们的API发送推送通知。感兴趣的用户可以参考参考资料 [[6]](https://cloud.ibm.com/docs/services/mobilepush?topic=mobile-pushnotification-security-in-push-notifications)
对于iOS或Android应用,`appKey``appSecret`用于保护推送通知API的安全 [[6]](https://cloud.ibm.com/docs/services/mobilepush?topic=mobile-pushnotification-security-in-push-notifications)。只有经过身份验证或验证的客户端才能使用我们的API发送推送通知。感兴趣的用户可以参考参考文献 [[6]](https://cloud.ibm.com/docs/services/mobilepush?topic=mobile-pushnotification-security-in-push-notifications)

#### 监控排队通知 (Monitor queued notifications)
一个关键的监控指标是排队通知的总数。如果数量过多,说明通知事件处理速度不够快。为了避免通知延迟,需要增加更多的工作线程。图10-12(感谢 [[7]](https://bit.ly/2sotIa6))展示了排队消息待处理的示例。
Expand All @@ -251,7 +251,7 @@ POST https://api.example.com/v/sms/send
- 此外,通知模板提供了一种一致且高效的通知创建过程。
- 最后,增加了监控和跟踪系统,以进行系统健康检查和未来改进。

## 步骤 4 - 总结
## 第 4 步 - 总结

通知是不可或缺的,因为它们让我们了解重要信息。这可以是关于您最喜欢的Netflix电影的推送通知、有关新产品折扣的电子邮件,或是关于您在线购物付款确认的消息。

Expand All @@ -266,7 +266,7 @@ POST https://api.example.com/v/sms/send

恭喜你走到这一步!现在给自己一个鼓励。做得好!

## 参考资料
## 参考文献

[1] Twilio SMS: https://www.twilio.com/sms

Expand Down
201 changes: 200 additions & 1 deletion docs/CHAPTER-11-DESIGN-A-NEWS-FEED-SYSTEM.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,205 @@
# 第十一章:设计一个新闻订阅系统

在本章中,您被要求设计一个新闻订阅系统。什么是新闻订阅?根据Facebook帮助页面的定义:“新闻订阅是您主页中不断更新的故事列表。新闻订阅包括您在Facebook上关注的人、页面和群组的状态更新、照片、视频、链接、应用活动和点赞” [1]。这是一个常见的面试问题。类似的常见问题有:设计Facebook新闻订阅、Instagram动态、Twitter时间线等。
在本章中,您被要求设计一个新闻订阅系统。什么是新闻订阅?根据Facebook帮助页面的定义:“新闻订阅是您主页中不断更新的故事列表。新闻订阅包括您在Facebook上关注的人、页面和群组的状态更新、照片、视频、链接、应用活动和点赞” [[1]](https://www.facebook.com/help/327131014036297/)。这是一个常见的面试问题。类似的常见问题有:设计Facebook新闻订阅、Instagram动态、Twitter时间线等。

![图11-1](/f11-1.png)

## 第 1 步 - 理解问题并确定设计范围

第一个问题集旨在弄清面试官在让你设计一个新闻订阅系统时的意图。至少,你需要弄清楚需要支持哪些功能。以下是一个候选人与面试官的对话示例:

**候选人**:这是一个移动应用吗?还是网页应用?或者两者兼有?
**面试官**:两者兼有。

**候选人**:有哪些重要功能?
**面试官**:用户可以发布帖子,并在新闻订阅页面看到好友的帖子。

**候选人**:新闻订阅是按时间倒序排列,还是按照其他特定顺序,例如按主题分数?比如,来自密友的帖子得分更高。
**面试官**:为了简化问题,我们假设订阅内容按时间倒序排列。

**候选人**:用户最多可以有多少好友?
**面试官**:5000个。

**候选人**:流量有多大?
**面试官**:每日活跃用户为1000万。

**候选人**:订阅内容可以包含图片、视频,还是仅限文字?
**面试官**:可以包含媒体文件,包括图片和视频。

现在你已经收集了需求,我们接下来专注于系统设计。

## 第 2 步 - 提出高层设计并获得认可

设计分为两个流程:**订阅发布****新闻订阅构建**
- **订阅发布**:当用户发布帖子时,相关数据会写入缓存和数据库,帖子会被推送到好友的新闻订阅中。
- **新闻订阅构建**:为了简化,我们假设新闻订阅通过聚合好友的帖子并按时间倒序排列构建。

### 新闻订阅API
新闻订阅API是客户端与服务器通信的主要方式。这些API基于HTTP协议,允许客户端执行各种操作,包括发布状态、获取新闻订阅、添加好友等。我们讨论两个最重要的API:**订阅发布API****新闻订阅获取API**

#### 订阅发布API
发布帖子时,客户端将发送一个HTTP POST请求到服务器。API如下所示:
```
POST /v1/me/feed
参数:
- content: 帖子的文本内容。
- auth_token: 用于验证API请求的身份。
```

#### 新闻订阅获取API
获取新闻订阅的API如下所示:
```
GET /v1/me/feed
参数:
- auth_token: 用于验证API请求的身份。
```

### 订阅发布流程
图11-2展示了订阅发布流程的高层设计。

![图11-2](/f11-2.png)

- **用户**:用户可以通过浏览器或移动应用查看新闻订阅。用户通过API发送内容为“Hello”的帖子:
```
/v1/me/feed?content=Hello&auth_token={auth_token}
```

- **负载均衡器**:分配流量到各个Web服务器。

- **Web服务器**:Web服务器将流量重定向到不同的内部服务。

- **帖子服务**:将帖子持久化存储到数据库和缓存中。

- **广播服务**:将新内容推送到好友的新闻订阅中。新闻订阅数据存储在缓存中,以便快速检索。

- **通知服务**:通知好友有新内容可用,并发送推送通知。

### 新闻订阅构建
本节讨论新闻订阅在后台是如何构建的。图11-3展示了高层设计。

![图11-3](/f11-3.png)

- **用户**:用户发送请求以获取她的新闻订阅。请求如下所示:
```
/v1/me/feed
```

- **负载均衡器**:负载均衡器将流量重定向到Web服务器。

- **Web服务器**:Web服务器将请求路由到新闻订阅服务。

- **新闻订阅服务**:新闻订阅服务从缓存中获取新闻订阅。

- **新闻订阅缓存**:存储渲染新闻订阅所需的新闻订阅ID。

## 第 3 步 - 深入设计
高层设计简要介绍了两个流程:**订阅发布****新闻订阅构建**。在这里,我们将深入讨论这些主题。

### 订阅发布的深入分析

图11-4概述了订阅发布的详细设计。我们在高层设计中已经讨论了大多数组件,这里我们将重点关注两个组件:**Web服务器****广播服务**

![图11-4](/f11-4.png)

#### Web服务器

除了与客户端通信外,Web服务器还负责执行身份验证和速率限制。只有使用有效的`auth_token`登录的用户才被允许发布帖子。系统会限制用户在一定时间内发布帖子的数量,这是防止垃圾信息和滥用内容的关键措施。

#### 广播服务 (Fanout service)

广播是将帖子分发给所有好友的过程。广播有两种模型:**写时广播**(也称为推送模型)和**读时广播**(也称为拉取模型)。这两种模型各有优缺点。我们将解释它们的工作流程,并探索支持我们系统的最佳方法。

- **写时广播**:在这种方法中,新闻订阅在写入时预先计算。新发布的帖子会在发布后立即推送到好友的缓存中。

**优点**
- 新闻订阅是实时生成的,可以立即推送给好友。
- 获取新闻订阅的速度快,因为新闻订阅在写入时已经预先计算好。

**缺点**
- 如果用户有很多好友,获取好友列表并为所有好友生成新闻订阅将会很慢且耗时,这被称为**热键问题**
- 对于不活跃或很少登录的用户,预先计算的新闻订阅会浪费计算资源。

- **读时广播**
在这种方法中,新闻订阅是在读取时生成的。这是一种按需模型,当用户加载其主页时,会拉取最近的帖子。

**优点**
- 对于不活跃或很少登录的用户,读时广播更为有效,因为它不会在这些用户上浪费计算资源。
- 数据不会被推送给好友,因此不存在热键问题。

**缺点**
- 获取新闻订阅的速度较慢,因为新闻订阅未预先计算。

我们采用**混合方法**,以获取两种方法的优势并避免它们的缺陷。由于快速获取新闻订阅至关重要,我们对大多数用户采用**推送模型**。对于名人或拥有大量好友/粉丝的用户,我们让其粉丝按需拉取新闻内容,以避免系统过载。**一致性哈希**是一种有效的技术,能够帮助减轻热键问题,因为它有助于更均匀地分布请求和数据。

图11-5展示了广播服务的详细设计,让我们深入了解这一服务。

![图11-5](/f11-5.png)

**广播服务的工作流程如下:**

1. **从图数据库获取好友ID**:图数据库非常适合用于管理好友关系和好友推荐。如果读者想深入了解这一概念,可以参考文献 [[2]](http://geekswithblogs.net/brendonpage/archive/2015/10/26/friend-of-friendrecommendations-with-neo4j.aspx)

2. **从用户缓存中获取好友信息**:系统会根据用户设置过滤掉部分好友。例如,如果你将某人静音,即便你们还是好友,她的帖子也不会出现在你的新闻订阅中。另一种情况是,用户可以选择性地向某些好友分享信息,或对其他人隐藏。

3. **将好友列表和新帖子的ID发送到消息队列**

4. **广播工作器从消息队列中获取数据,并将新闻订阅数据存储到新闻订阅缓存中**:你可以将新闻订阅缓存视为一个`<post_id, user_id>`的映射表。每当有新帖子发布时,它会附加到新闻订阅表中,如图11-6所示。如果在缓存中存储完整的用户和帖子对象,内存消耗将非常大,因此我们只存储ID。为了控制内存大小,我们设置了一个可配置的限制。用户滚动浏览数千条新闻订阅的可能性不大,大多数用户只对最新内容感兴趣,因此缓存未命中率较低。

5. **`<post_id, user_id>`存储到新闻订阅缓存中**:图11-6展示了缓存中新闻订阅的一个示例。

![图11-6](/f11-6.png)

### 新闻订阅检索深入分析

图11-7展示了新闻订阅检索的详细设计。

![图11-7](/f11-7.png)

正如图11-7所示,媒体内容(如图片、视频等)存储在**CDN**中以实现快速检索。下面我们来看看客户端如何检索新闻订阅。

1. 用户发送请求以检索她的新闻动态。请求格式为:`/v1/me/feed`
2. 负载均衡器将请求重新分配到 Web 服务器。
3. Web 服务器调用新闻动态服务以获取新闻动态。
4. 新闻动态服务从新闻动态缓存中获取帖子 ID 列表。
5. 用户的新闻动态不仅仅是一个帖子 ID 列表。它还包含用户名、个人资料图片、帖子内容、帖子图片等。因此,新闻动态服务从缓存(用户缓存和帖子缓存)中获取完整的用户和帖子对象,以构建完整的新闻动态。
6. 完整的新闻动态以 JSON 格式返回给客户端进行渲染。

### 缓存架构

缓存对于新闻动态系统至关重要。我们将缓存层分为五层,如图 11-8 所示。

![图11-8](/f11-8.png)

- 新闻动态:存储新闻动态的 ID。
- 内容:存储每个帖子的数据信息。热门内容存储在热缓存中。
- 社交图谱:存储用户关系数据。
- 操作:存储用户是否点赞、回复帖子或对帖子采取其他操作的信息。
- 计数器:存储点赞、回复、关注者、正在关注等的计数器。

## 第 4 步 - 总结

在本章中,我们设计了一个新闻动态系统。我们的设计包含两个流程:动态发布和新闻动态检索。
与任何系统设计面试问题一样,没有完美的系统设计方法。每个公司都有其独特的限制,您必须设计一个适应这些限制的系统。理解设计和技术选择的权衡是重要的。如果还有几分钟时间,您可以讨论扩展性问题。为避免重复讨论,以下仅列出高层次的讨论要点。

**扩展数据库:**
- 垂直扩展与水平扩展
- SQL 与 NoSQL
- 主从复制
- 读取副本
- 一致性模型
- 数据库分片

**其他讨论要点:**
- 保持 Web 层无状态
- 尽可能缓存数据
- 支持多个数据中心
- 使用消息队列减少组件耦合
- 监控关键指标。例如,监测高峰时段的 QPS 和用户刷新新闻动态时的延迟是很有意义的。

恭喜您走到这一步!现在给自己一个赞。干得不错!

## 参考文献

[1] How News Feed Works: https://www.facebook.com/help/327131014036297/

[2] Friend of Friend recommendations Neo4j and SQL Sever: http://geekswithblogs.net/brendonpage/archive/2015/10/26/friend-of-friendrecommendations-with-neo4j.aspx
Loading

0 comments on commit 870d2f9

Please sign in to comment.