From 870d2f977e25d6a9a077eb17102e9e9c6fb6d87c Mon Sep 17 00:00:00 2001 From: Kyrie Lin Date: Fri, 18 Oct 2024 11:49:56 -0700 Subject: [PATCH] feat: chapter 11 --- README.md | 2 +- ...-1-SCALE-FROM-ZERO-TO-MILLIONS-OF-USERS.md | 6 +- ...CHAPTER-10-DESIGN-A-NOTIFICATION-SYSTEM.md | 14 +- docs/CHAPTER-11-DESIGN-A-NEWS-FEED-SYSTEM.md | 201 +++++++++++++++++- ...APTER-2-BACK-OF-THE-ENVELOPE-ESTIMATION.md | 4 +- ...-FRAMEWORK-FOR-SYSTEM-DESIGN-INTERVIEWS.md | 8 +- docs/CHAPTER-4-DESIGN-A-RATE-LIMITER.md | 12 +- docs/CHAPTER-5-DESIGN-CONSISTENT-HASHING.md | 2 +- docs/CHAPTER-6-DESIGN-A-KEY-VALUE-STORE.md | 12 +- ...QUE-ID-GENERATOR-IN-DISTRIBUTED-SYSTEMS.md | 10 +- docs/CHAPTER-8-DESIGN-A-URL-SHORTENER.md | 10 +- docs/CHAPTER-9-DESIGN-A-WEB-CRAWLER.md | 34 +-- 12 files changed, 257 insertions(+), 58 deletions(-) diff --git a/README.md b/README.md index e5f4c5b..58edb01 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ - 第10章:设计一个通知系统 -- 第11章:设计一个新闻推送系统 +- 第11章:设计一个新闻订阅系统 - 第12章:设计一个聊天系统 diff --git a/docs/CHAPTER-1-SCALE-FROM-ZERO-TO-MILLIONS-OF-USERS.md b/docs/CHAPTER-1-SCALE-FROM-ZERO-TO-MILLIONS-OF-USERS.md index 03338c9..9d11d61 100644 --- a/docs/CHAPTER-1-SCALE-FROM-ZERO-TO-MILLIONS-OF-USERS.md +++ b/docs/CHAPTER-1-SCALE-FROM-ZERO-TO-MILLIONS-OF-USERS.md @@ -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展示了在添加负载均衡器和数据库复制后系统的设计。 @@ -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 如何改善加载时间。 @@ -340,7 +340,7 @@ CDN 的高层工作原理如下:当用户访问网站时,离用户最近的 恭喜您走到这里!现在给自己一个鼓励,干得好! -## 参考资料 +## 参考文献 [1] Hypertext Transfer Protocol: https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol diff --git a/docs/CHAPTER-10-DESIGN-A-NOTIFICATION-SYSTEM.md b/docs/CHAPTER-10-DESIGN-A-NOTIFICATION-SYSTEM.md index 41a2687..9eb5e98 100644 --- a/docs/CHAPTER-10-DESIGN-A-NOTIFICATION-SYSTEM.md +++ b/docs/CHAPTER-10-DESIGN-A-NOTIFICATION-SYSTEM.md @@ -6,7 +6,7 @@ ![图10-1](/f10-1.png) -## 步骤 1 - 理解问题并确定设计范围 +## 第 1 步 - 理解问题并确定设计范围 构建一个每天发送数百万条通知的可扩展系统并非易事。这需要对通知生态系统有深入的理解。这个面试问题有意设置得开放且模糊,需要你主动提问以澄清需求。 **候选人**:系统支持哪种类型的通知? @@ -27,7 +27,7 @@ **候选人**:每天发送多少条通知? **面试官**:每天发送 1000 万条移动推送通知、100 万条短信和 500 万封电子邮件。 -## 步骤 2 - 提出高层设计并达成共识 +## 第 2 步 - 提出高层设计并达成共识 本节展示了支持多种通知类型的高层设计:iOS 推送通知、Android 推送通知、短信消息和电子邮件。其结构如下: - 不同类型的通知 @@ -179,7 +179,7 @@ POST https://api.example.com/v/sms/send 5. 工作节点将通知发送到第三方服务。 6. 第三方服务将通知发送到用户设备。 -## 步骤 3 - 设计深入探讨 +## 第 3 步 - 设计深入探讨 在高层设计中,我们讨论了不同类型的通知、联系信息的收集流程以及通知发送/接收的流程。我们将在以下几个方面进行深入剖析: - 可靠性 @@ -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/)。 ### 附加组件和考虑因素 我们已经讨论了如何收集用户联系信息、发送和接收通知。一个通知系统远不止于此。这里我们讨论额外的组件,包括模板重用、通知设置、事件跟踪、系统监控、限流等。 @@ -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))展示了排队消息待处理的示例。 @@ -251,7 +251,7 @@ POST https://api.example.com/v/sms/send - 此外,通知模板提供了一种一致且高效的通知创建过程。 - 最后,增加了监控和跟踪系统,以进行系统健康检查和未来改进。 -## 步骤 4 - 总结 +## 第 4 步 - 总结 通知是不可或缺的,因为它们让我们了解重要信息。这可以是关于您最喜欢的Netflix电影的推送通知、有关新产品折扣的电子邮件,或是关于您在线购物付款确认的消息。 @@ -266,7 +266,7 @@ POST https://api.example.com/v/sms/send 恭喜你走到这一步!现在给自己一个鼓励。做得好! -## 参考资料 +## 参考文献 [1] Twilio SMS: https://www.twilio.com/sms diff --git a/docs/CHAPTER-11-DESIGN-A-NEWS-FEED-SYSTEM.md b/docs/CHAPTER-11-DESIGN-A-NEWS-FEED-SYSTEM.md index 6851b55..6182570 100644 --- a/docs/CHAPTER-11-DESIGN-A-NEWS-FEED-SYSTEM.md +++ b/docs/CHAPTER-11-DESIGN-A-NEWS-FEED-SYSTEM.md @@ -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. **广播工作器从消息队列中获取数据,并将新闻订阅数据存储到新闻订阅缓存中**:你可以将新闻订阅缓存视为一个``的映射表。每当有新帖子发布时,它会附加到新闻订阅表中,如图11-6所示。如果在缓存中存储完整的用户和帖子对象,内存消耗将非常大,因此我们只存储ID。为了控制内存大小,我们设置了一个可配置的限制。用户滚动浏览数千条新闻订阅的可能性不大,大多数用户只对最新内容感兴趣,因此缓存未命中率较低。 + +5. **将``存储到新闻订阅缓存中**:图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 diff --git a/docs/CHAPTER-2-BACK-OF-THE-ENVELOPE-ESTIMATION.md b/docs/CHAPTER-2-BACK-OF-THE-ENVELOPE-ESTIMATION.md index 8676a47..714b5e3 100644 --- a/docs/CHAPTER-2-BACK-OF-THE-ENVELOPE-ESTIMATION.md +++ b/docs/CHAPTER-2-BACK-OF-THE-ENVELOPE-ESTIMATION.md @@ -22,7 +22,7 @@ > - 1 µs= 10^-6 seconds = 1,000 ns > - 1 ms = 10^-3 seconds = 1,000 µs = 1,000,000 ns -谷歌的一位软件工程师开发了一款工具来可视化Dean博士的延迟数据。该工具还考虑了时间因素。图2-1展示了截至2020年的可视化延迟数值(图表来源:参考资料[[3]](https://colin-scott.github.io/personal_website/research/interactive_latency.html))。 +谷歌的一位软件工程师开发了一款工具来可视化Dean博士的延迟数据。该工具还考虑了时间因素。图2-1展示了截至2020年的可视化延迟数值(图表来源:参考文献[[3]](https://colin-scott.github.io/personal_website/research/interactive_latency.html))。 ![图2-1](/f2-1.png) @@ -83,7 +83,7 @@ 恭喜你走到了这一步!现在请给自己一个鼓励。做得很好! -## 参考资料 +## 参考文献 [1] J. Dean.Google Pro Tip: Use Back-Of-The-Envelope-Calculations To Choose The Best Design: http://highscalability.com/blog/2011/1/26/google-pro-tip-use-back-of-the-envelopecalculations-to-choo.html diff --git a/docs/CHAPTER-3-A-FRAMEWORK-FOR-SYSTEM-DESIGN-INTERVIEWS.md b/docs/CHAPTER-3-A-FRAMEWORK-FOR-SYSTEM-DESIGN-INTERVIEWS.md index ec40e60..9ea749c 100644 --- a/docs/CHAPTER-3-A-FRAMEWORK-FOR-SYSTEM-DESIGN-INTERVIEWS.md +++ b/docs/CHAPTER-3-A-FRAMEWORK-FOR-SYSTEM-DESIGN-INTERVIEWS.md @@ -20,7 +20,7 @@ 每次系统设计面试都是不同的。一场优秀的系统设计面试是开放式的,并且没有通用的解决方案。然而,任何系统设计面试都有一些共同的步骤和核心内容需要涵盖。 -## 步骤 1:理解问题并确定设计范围 +## 第 1 步:理解问题并确定设计范围 **“为什么老虎会咆哮?”** 教室后排有一只手举了起来。 @@ -72,7 +72,7 @@ Jimmy是个尖子生,他以快速答题为傲。在考试中,他通常是第 以上是一些你可以向面试官提出的示例问题。通过提问来理解需求并澄清模糊点是非常重要的。 -## 步骤 2:提出高层设计并获得认可 +## 第 2 步:提出高层设计并获得认可 在这一步,我们的目标是开发出一个高层设计,并与面试官达成一致。在这个过程中,与面试官合作是个不错的主意。 @@ -99,7 +99,7 @@ Jimmy是个尖子生,他以快速答题为傲。在考试中,他通常是第 ![图3-2](/f3-2.png) -## 步骤 3:深入设计 +## 第 3 步:深入设计 到了这一步,你和面试官应该已经完成了以下目标: - 就整体目标和功能范围达成共识 @@ -119,7 +119,7 @@ Jimmy是个尖子生,他以快速答题为傲。在考试中,他通常是第 图3-3和图3-4分别展示了这两个用例的详细设计,具体细节将在第11章中解释。 -## 步骤 4:总结 +## 第 4 步:总结 在最后一步中,面试官可能会问你一些后续问题,或者让你自由讨论其他补充点。以下是几个可能的方向: diff --git a/docs/CHAPTER-4-DESIGN-A-RATE-LIMITER.md b/docs/CHAPTER-4-DESIGN-A-RATE-LIMITER.md index d75c66c..d373b5e 100644 --- a/docs/CHAPTER-4-DESIGN-A-RATE-LIMITER.md +++ b/docs/CHAPTER-4-DESIGN-A-RATE-LIMITER.md @@ -14,7 +14,7 @@ - **防止服务器过载**。为了减少服务器负载,速率限制器用于过滤掉由机器人或用户不当行为导致的多余请求。 -## 步骤 1 - 理解问题并确定设计范围 +## 第 1 步 - 理解问题并确定设计范围 速率限制可以通过不同的算法实现,每种算法都有其优缺点。面试官和候选人之间的互动有助于澄清我们要构建的速率限制器的类型。 @@ -55,7 +55,7 @@ 理解这些要求是系统设计的第一步,确保设计的系统满足实际业务需求并具有良好的性能表现。 -## 步骤 2 - 提出高层设计并获得认同 +## 第 2 步 - 提出高层设计并获得认同 让我们保持简单,使用基本的客户端和服务器模型进行通信。 @@ -218,7 +218,7 @@ 使用这个公式,我们得到 `3 + 5 x 0.7 = 6.5` 个请求。根据具体用例,这个数字可以向上或向下舍入。在我们的示例中,向下舍入为 6。由于速率限制器允许每分钟最多 7 个请求,因此当前请求可以通过。然而,在接收到一个额外请求后,限制将会达到。 -由于空间限制,这里不讨论其他实现。有兴趣的读者可以参考参考资料 [[9]](https://medium.com/@saisandeepmopuri/system-design-rate-limiter-and-data-modelling9304b0d18250)。这个算法并不完美,具有优缺点。 +由于空间限制,这里不讨论其他实现。有兴趣的读者可以参考参考文献 [[9]](https://medium.com/@saisandeepmopuri/system-design-rate-limiter-and-data-modelling9304b0d18250)。这个算法并不完美,具有优缺点。 **优点** - 由于速率基于上一个窗口的平均速率,它能够平滑流量峰值。 @@ -245,7 +245,7 @@ - 如果达到了限制,请求被拒绝。 - 如果没有达到限制,请求将发送到 API 服务器。与此同时,系统增加计数器并将其保存回 Redis。 -## 步骤 3 - 深入设计 +## 第 3 步 - 深入设计 高层设计在图 4-12 中并没有回答以下问题: - 如何创建速率限制规则?规则存储在哪里? @@ -359,7 +359,7 @@ rate_limit: 例如,如果速率限制规则过于严格,许多有效请求会被丢弃。在这种情况下,我们希望稍微放宽规则。另一个例子是,当流量突然增加(例如闪购)时,我们注意到速率限制器变得无效。在这种情况下,我们可以更换算法以支持突发流量。令牌桶算法在这里是一个不错的选择。 -## 步骤 4 - 总结 +## 第 4 步 - 总结 在本章中,我们讨论了不同的速率限制算法及其优缺点。讨论的算法包括: @@ -387,7 +387,7 @@ rate_limit: 恭喜你走到这里!现在可以给自己一个鼓励,干得好! -## 参考资料 +## 参考文献 [1] Rate-limiting strategies and techniques: https://cloud.google.com/solutions/rate-limitingstrategies-techniques diff --git a/docs/CHAPTER-5-DESIGN-CONSISTENT-HASHING.md b/docs/CHAPTER-5-DESIGN-CONSISTENT-HASHING.md index dbfced8..3d623dc 100644 --- a/docs/CHAPTER-5-DESIGN-CONSISTENT-HASHING.md +++ b/docs/CHAPTER-5-DESIGN-CONSISTENT-HASHING.md @@ -136,7 +136,7 @@ serverIndex = hash(key) % N 恭喜你读到这里!现在给自己一个赞,做得很好! -## 参考资料 +## 参考文献 [1] Consistent hashing: https://en.wikipedia.org/wiki/Consistent_hashing diff --git a/docs/CHAPTER-6-DESIGN-A-KEY-VALUE-STORE.md b/docs/CHAPTER-6-DESIGN-A-KEY-VALUE-STORE.md index fa4b973..e01ad4a 100644 --- a/docs/CHAPTER-6-DESIGN-A-KEY-VALUE-STORE.md +++ b/docs/CHAPTER-6-DESIGN-A-KEY-VALUE-STORE.md @@ -248,19 +248,19 @@ W、R 和 N 的配置是延迟(latency)与一致性(consistency)之间 假设键空间从 1 到 12,以下步骤展示如何构建一个 Merkle 树。高亮的框表示不一致性。 -**步骤 1**:将键空间分为桶(在本例中为 4 个),如图 6-13 所示。每个桶用作根级节点,以保持树的深度有限。 +**第 1 步**:将键空间分为桶(在本例中为 4 个),如图 6-13 所示。每个桶用作根级节点,以保持树的深度有限。 ![图6-13](/f6-13.png) -**步骤 2**:一旦桶创建完成,使用均匀哈希方法对每个桶中的键进行哈希处理(如图 6-14 所示)。 +**第 2 步**:一旦桶创建完成,使用均匀哈希方法对每个桶中的键进行哈希处理(如图 6-14 所示)。 ![图6-14](/f6-14.png) -**步骤 3**:为每个桶创建一个单独的哈希节点(如图 6-15 所示)。 +**第 3 步**:为每个桶创建一个单独的哈希节点(如图 6-15 所示)。 ![图6-15](/f6-15.png) -**步骤 4**:通过计算子节点的哈希值向上构建树,直到根节点(如图 6-16 所示)。 +**第 4 步**:通过计算子节点的哈希值向上构建树,直到根节点(如图 6-16 所示)。 ![图6-16](/f6-16.png) @@ -311,7 +311,7 @@ W、R 和 N 的配置是延迟(latency)与一致性(consistency)之间 ![图6-21](/f6-21.png) -1. 系统首先检查数据是否在内存中。如果不在,则进入步骤 2。 +1. 系统首先检查数据是否在内存中。如果不在,则进入第 2 步。 2. 如果数据不在内存中,系统检查布隆过滤器。 3. 布隆过滤器用于确定哪些 SSTable 可能包含该键。 4. SSTable 返回数据集的结果。 @@ -337,7 +337,7 @@ W、R 和 N 的配置是延迟(latency)与一致性(consistency)之间 ![表6-2](/t6-2.png) -## 参考资料 +## 参考文献 [1] Amazon DynamoDB: https://aws.amazon.com/dynamodb/ diff --git a/docs/CHAPTER-7-DESIGN-A-UNIQUE-ID-GENERATOR-IN-DISTRIBUTED-SYSTEMS.md b/docs/CHAPTER-7-DESIGN-A-UNIQUE-ID-GENERATOR-IN-DISTRIBUTED-SYSTEMS.md index 3bf92d1..1eb8c39 100644 --- a/docs/CHAPTER-7-DESIGN-A-UNIQUE-ID-GENERATOR-IN-DISTRIBUTED-SYSTEMS.md +++ b/docs/CHAPTER-7-DESIGN-A-UNIQUE-ID-GENERATOR-IN-DISTRIBUTED-SYSTEMS.md @@ -6,7 +6,7 @@ ![图7-1](/f7-1.png) -## 步骤 1 - 理解问题并确定设计范围 +## 第 1 步 - 理解问题并确定设计范围 提出澄清性问题是应对任何系统设计面试问题的第一步。以下是候选人与面试官的对话示例: @@ -29,7 +29,7 @@ - ID 按日期排序。 - 系统需要能够每秒生成超过 10,000 个唯一 ID。 -## 步骤 2 - 提出高层设计并获得认同 +## 第 2 步 - 提出高层设计并获得认同 在分布式系统中有多种方案可以用来生成唯一 ID。我们考虑的选项有: @@ -104,7 +104,7 @@ - **机器 ID**:5 位,可以表示 `2^5 = 32` 台每个数据中心的机器。 - **序列号**:12 位。对于在该机器/进程上生成的每个 ID,序列号递增 1。该数字每毫秒重置为 0。 -## 步骤 3 - 深入设计 +## 第 3 步 - 深入设计 在高层设计中,我们讨论了各种在分布式系统中设计唯一 ID 生成器的选项。我们最终选择了一种基于 Twitter 雪花 ID 生成器的方法。让我们深入探讨这个设计。为了回忆设计图,下面重新列出设计图。 @@ -125,7 +125,7 @@ 序列号为 12 位,这意味着可以生成 `2^12 = 4096` 种组合。该字段默认为 0,除非在同一毫秒内在同一服务器上生成超过一个 ID。理论上,一台机器可以支持每毫秒最多生成 4096 个新 ID。 -## 步骤 4 - 总结 +## 第 4 步 - 总结 在本章中,我们讨论了设计唯一 ID 生成器的不同方法:多主复制、UUID、票据服务器,以及类似于 Twitter 雪花算法的唯一 ID 生成器。我们最终选择了雪花算法,因为它支持我们所有的用例,并且在分布式环境中具有可扩展性。 @@ -136,7 +136,7 @@ 恭喜你读到这里!给自己一个赞,做得很好! -## 参考资料 +## 参考文献 [1] Universally unique identifier: https://en.wikipedia.org/wiki/Universally_unique_identifier diff --git a/docs/CHAPTER-8-DESIGN-A-URL-SHORTENER.md b/docs/CHAPTER-8-DESIGN-A-URL-SHORTENER.md index 09b8c2c..68b4cf2 100644 --- a/docs/CHAPTER-8-DESIGN-A-URL-SHORTENER.md +++ b/docs/CHAPTER-8-DESIGN-A-URL-SHORTENER.md @@ -2,7 +2,7 @@ 在本章中,我们将解决一个有趣且经典的系统设计面试问题:设计一个像 TinyURL 这样的 URL 缩短服务。 -## 步骤 1 - 理解问题并确定设计范围 +## 第 1 步 - 理解问题并确定设计范围 系统设计面试问题故意留有开放性。要设计一个精心构建的系统,提出澄清问题至关重要。 @@ -36,7 +36,7 @@ 与面试官一起走过假设和计算过程非常重要,以确保你们双方在同一页面上。 -## 步骤 2 - 提出高层设计并获得认同 +## 第 2 步 - 提出高层设计并获得认同 在本节中,我们讨论 **API 端点**、**URL 重定向**和 **URL 短缩流程**。 @@ -95,7 +95,7 @@ API 端点促进客户端与服务器之间的通信。我们将设计 REST 风 关于哈希函数的详细设计将在深入讨论中进行。 -## 步骤 3 - 设计深入探讨 +## 第 3 步 - 设计深入探讨 到目前为止,我们已经讨论了 URL 短缩和 URL 重定向的高层设计。在本节中,我们将深入探讨以下内容:**数据模型**、**哈希函数**、**URL 短缩**和 **URL 重定向**。 @@ -199,7 +199,7 @@ URL 重定向的流程总结如下: 5. 将 **longURL** 返回给用户。 -## 步骤 4 - 总结 +## 第 4 步 - 总结 在本章中,我们讨论了 API 设计、数据模型、哈希函数、URL 缩短和 URL 重定向。 @@ -217,7 +217,7 @@ URL 重定向的流程总结如下: 恭喜你能走到这一步!现在给自己一个赞吧。干得好! -## 参考资料 +## 参考文献 [1] A RESTful Tutorial: https://www.restapitutorial.com/index.html diff --git a/docs/CHAPTER-9-DESIGN-A-WEB-CRAWLER.md b/docs/CHAPTER-9-DESIGN-A-WEB-CRAWLER.md index 18e179b..1b76939 100644 --- a/docs/CHAPTER-9-DESIGN-A-WEB-CRAWLER.md +++ b/docs/CHAPTER-9-DESIGN-A-WEB-CRAWLER.md @@ -14,7 +14,7 @@ 开发一个网页爬虫的复杂性取决于我们想要支持的规模。它可以是一个小型的学校项目,只需几个小时即可完成;也可以是一个庞大的项目,需要专门的工程团队不断改进。因此,我们将探讨爬虫支持的规模和功能。 -## 步骤 1 - 理解问题并确定设计范围 +## 第 1 步 - 理解问题并确定设计范围 网页爬虫的基本算法非常简单: 1. 给定一组URL,下载这些URL指向的所有网页。 @@ -55,7 +55,7 @@ - **存储需求**:10亿页 x 500KB = 500TB 存储需求每月。如果你对数字存储单位不太清楚,可以参考第2章的“2的幂”部分。 - 假设数据存储5年,那么 500TB * 12个月 * 5年 = 30PB。需要30PB的存储空间来保存5年的内容。 -## 步骤 2 - 提出高层设计并获得认同 +## 第 2 步 - 提出高层设计并获得认同 在明确需求之后,我们可以开始构建高层设计。借鉴以往关于网页爬虫的研究 [4] [[5]](http://infolab.stanford.edu/~olston/publications/crawling_survey.pdf),我们提出了一个高层设计,如图9-2所示。首先,我们将逐一探讨各个设计组件的功能,然后再逐步分析爬虫的工作流程。 @@ -109,7 +109,7 @@ **“URL已访问”** 是一个数据结构,用于跟踪之前已访问的URL或已经在前沿队列中的URL。“URL已访问”有助于避免多次添加相同的URL,因为这会增加服务器负载并可能导致无限循环。 -布隆过滤器和哈希表是实现“URL已访问”组件的常用技术。此处不详细介绍布隆过滤器和哈希表的具体实现,详情可参考参考资料 [4] [8]。 +布隆过滤器和哈希表是实现“URL已访问”组件的常用技术。此处不详细介绍布隆过滤器和哈希表的具体实现,详情可参考参考文献 [4] [8]。 ### URL 存储 (URL Storage) @@ -123,22 +123,22 @@ ![图9-4](/f9-4.png) -**步骤 1**:将种子 URL 添加到 URL 前沿 -**步骤 2**:HTML 下载器从 URL 前沿获取 URL 列表。 -**步骤 3**:HTML 下载器从 DNS 解析器获取 URL 的 IP 地址并开始下载。 -**步骤 4**:内容解析器解析 HTML 页面并检查页面是否格式错误。 -**步骤 5**:在内容解析和验证后,将其传递给“内容是否已见?”组件。 -**步骤 6**:“内容是否已见?”组件检查 HTML 页面是否已存在于存储中。 +**第 1 步**:将种子 URL 添加到 URL 前沿 +**第 2 步**:HTML 下载器从 URL 前沿获取 URL 列表。 +**第 3 步**:HTML 下载器从 DNS 解析器获取 URL 的 IP 地址并开始下载。 +**第 4 步**:内容解析器解析 HTML 页面并检查页面是否格式错误。 +**第 5 步**:在内容解析和验证后,将其传递给“内容是否已见?”组件。 +**第 6 步**:“内容是否已见?”组件检查 HTML 页面是否已存在于存储中。 - 如果已存在于存储中,意味着在不同的 URL 中已经处理过相同的内容。在这种情况下,该 HTML 页面将被丢弃。 - 如果不在存储中,系统之前未处理过相同的内容。内容将被传递给链接提取器。 -**步骤 7**:链接提取器从 HTML 页面中提取链接。 -**步骤 8**:提取的链接将传递给 URL 过滤器。 -**步骤 9**:经过过滤的链接将传递给“URL 是否已见?”组件。 -**步骤 10**:“URL 是否已见?”组件检查 URL 是否已存在于存储中。如果是,说明之前已处理过,无需进行任何操作。 -**步骤 11**:如果 URL 之前未被处理,将其添加到 URL 前沿。 +**第 7 步**:链接提取器从 HTML 页面中提取链接。 +**第 8 步**:提取的链接将传递给 URL 过滤器。 +**第 9 步**:经过过滤的链接将传递给“URL 是否已见?”组件。 +**第 1 步0**:“URL 是否已见?”组件检查 URL 是否已存在于存储中。如果是,说明之前已处理过,无需进行任何操作。 +**第 1 步1**:如果 URL 之前未被处理,将其添加到 URL 前沿。 -## 步骤 3 - 设计深入探讨 +## 第 3 步 - 设计深入探讨 到目前为止,我们已经讨论了高层设计。接下来,我们将深入讨论一些最重要的构建组件和技术: - 深度优先搜索(DFS)与广度优先搜索(BFS) - URL 前沿 @@ -270,7 +270,7 @@ www.spidertrapexample.com/foo/bar/foo/bar/foo/bar/… 3. **数据噪声** 某些内容的价值很小或没有价值,例如广告、代码片段、垃圾 URL 等。这些内容对爬虫没有用处,应该尽可能排除。 -## 步骤 4 - 总结 +## 第 4 步 - 总结 在本章中,我们首先讨论了一个优秀爬虫的特点:可扩展性、礼貌性、可扩展性和鲁棒性。接着,我们提出了设计方案并讨论了关键组件。构建一个可扩展的网络爬虫并非易事,因为网络庞大且充满陷阱。尽管我们已覆盖了许多主题,但仍然遗漏了一些相关的重要讨论点: - **服务器端渲染**:许多网站使用 JavaScript、AJAX 等脚本动态生成链接。如果我们直接下载和解析网页,将无法获取动态生成的链接。为了解决这个问题,我们在解析页面之前首先执行服务器端渲染(也称为动态渲染)[[12]](https://developers.google.com/search/docs/guides/dynamic-rendering)。 @@ -287,7 +287,7 @@ www.spidertrapexample.com/foo/bar/foo/bar/foo/bar/… 恭喜你走到这里!现在给自己拍拍背,做得好! -## 参考资料 +## 参考文献 [1] US Library of Congress: https://www.loc.gov/websites/ [2] EU Web Archive: http://data.europa.eu/webarchive