From 9803d3a92ccb64c12251853bff663b303fdac672 Mon Sep 17 00:00:00 2001 From: wizardforcel <562826179@qq.com> Date: Sat, 19 Nov 2022 17:15:20 +0800 Subject: [PATCH] 2022-11-19 17:15:20 --- docs/sys-design-inter/00.md | 2 + docs/sys-design-inter/01.md | 6 +- docs/sys-design-inter/02.md | 2 + docs/sys-design-inter/03-1.md | 511 +++++++++++++++++++++++++++++++++ docs/sys-design-inter/03.md | 514 +--------------------------------- docs/sys-design-inter/04.md | 28 +- docs/sys-design-inter/05.md | 14 +- docs/sys-design-inter/06.md | 2 + docs/sys-design-inter/07.md | 14 +- docs/sys-design-inter/08.md | 6 +- docs/sys-design-inter/09.md | 2 + docs/sys-design-inter/10.md | 4 +- docs/sys-design-inter/11.md | 2 + docs/sys-design-inter/12.md | 4 +- docs/sys-design-inter/13.md | 2 + docs/sys-design-inter/14.md | 2 + docs/sys-design-inter/15.md | 2 + docs/sys-design-inter/16.md | 2 + 18 files changed, 577 insertions(+), 542 deletions(-) create mode 100644 docs/sys-design-inter/03-1.md diff --git a/docs/sys-design-inter/00.md b/docs/sys-design-inter/00.md index ba4e8ba..864c852 100644 --- a/docs/sys-design-inter/00.md +++ b/docs/sys-design-inter/00.md @@ -1,3 +1,5 @@ +# 零、序言 + 我们很高兴你决定加入我们学习系统设计面试。系统设计面试问题是所有技术面试中最难解决的。这些问题要求受访者为一个软件系统设计一个架构,这个软件系统可以是新闻提要、谷歌搜索、聊天系统等。这些问题令人生畏,没有一定的模式可循。这些问题通常范围很广,也很模糊。这些过程是开放式的,没有标准或正确的答案是不清楚的。 公司广泛采用系统设计面试,因为这些面试中测试的沟通和解决问题的技能与软件工程师日常工作所需的技能相似。受访者的评估基于她如何分析一个模糊的问题,以及她如何一步一步地解决问题。测试的能力还包括她如何解释想法,与他人讨论,以及评估和优化系统。在英语中,使用“她”比使用“他或她”或在两者之间跳跃更流畅。为了便于阅读,我们在整本书中使用了阴性代词。没有不尊重男性工程师的意思。 diff --git a/docs/sys-design-inter/01.md b/docs/sys-design-inter/01.md index 1819602..9958fc6 100644 --- a/docs/sys-design-inter/01.md +++ b/docs/sys-design-inter/01.md @@ -1,3 +1,5 @@ +# 一、从零扩展到数百万用户 + 设计一个支持数百万用户的系统极具挑战性,这是一个需要不断改进和完善的旅程。在这一章中,我们构建了一个支持单个用户的系统,并逐步将其扩展到服务数百万用户。读完这一章,你将掌握一些技巧,帮助你解决系统设计面试问题。 ## 单服务器设置 @@ -320,7 +322,7 @@ CDN 回退:你应该考虑你的网站/应用如何应对 CDN 故障。如果有 分片将大型数据库分成更小、更容易管理的部分,称为分片。每个分片共享相同的模式,尽管每个分片上的实际数据对于该分片是唯一的。 -图 1-21 显示了一个分片数据库的例子。用户数据根据用户 id 分配给数据库服务器。每当您访问数据时,都会使用哈希函数来查找相应的碎片。在我们的例子中, user_id % 4 被用作散列函数。如果结果等于 0,shard 0 用于存储和获取数据。如果结果等于 1,则使用碎片 1。同样的逻辑也适用于其他碎片。 +图 1-21 显示了一个分片数据库的例子。用户数据根据用户 id 分配给数据库服务器。每当您访问数据时,都会使用哈希函数来查找相应的碎片。在我们的例子中, user_id % 4 被用作哈希函数。如果结果等于 0,shard 0 用于存储和获取数据。如果结果等于 1,则使用碎片 1。同样的逻辑也适用于其他碎片。 ![A close up of a logo Description automatically generated](../images/00024.jpeg) @@ -332,7 +334,7 @@ CDN 回退:你应该考虑你的网站/应用如何应对 CDN 故障。如果有 分片是扩展数据库的一项伟大技术,但它远非完美的解决方案。它给系统带来了复杂性和新的挑战: -重新共享数据 :当 1)由于快速增长,单个碎片无法再容纳更多数据时,需要重新共享数据。2)由于数据分布不均匀,某些碎片可能比其他碎片更快耗尽。当分片耗尽时,需要更新分片函数并四处移动数据。将在第五章讨论的一致散列法是解决这个问题的常用技术。 +重新共享数据 :当 1)由于快速增长,单个碎片无法再容纳更多数据时,需要重新共享数据。2)由于数据分布不均匀,某些碎片可能比其他碎片更快耗尽。当分片耗尽时,需要更新分片函数并四处移动数据。将在第五章讨论的一致哈希法是解决这个问题的常用技术。 名人问题 :这也叫热点关键问题。对特定碎片的过度访问可能会导致服务器过载。想象一下凯蒂·佩里、贾斯汀比伯和 Lady Gaga 的数据都出现在同一个碎片上。对于社交应用来说,这个碎片将会被读操作淹没。为了解决这个问题,我们可能需要为每个名人分配一个碎片。每个碎片甚至可能需要进一步的划分。 diff --git a/docs/sys-design-inter/02.md b/docs/sys-design-inter/02.md index 440a06c..8575ee1 100644 --- a/docs/sys-design-inter/02.md +++ b/docs/sys-design-inter/02.md @@ -1,3 +1,5 @@ +# 二、信封背面估计 + 在系统设计面试中,有时你会被要求使用粗略估计来估计系统容量或性能需求。根据谷歌高级研究员 Jeff Dean 的说法,“信封背面的计算是你使用思维实验和常见性能数字的组合来创建的估计,以获得满足你要求的设计的良好感觉”[1]。 您需要对可伸缩性基础有一个很好的认识,以便有效地进行预估。应该很好地理解以下概念:2 的幂,每个程序员都应该知道的延迟数字,以及可用性数字。 diff --git a/docs/sys-design-inter/03-1.md b/docs/sys-design-inter/03-1.md new file mode 100644 index 0000000..177c611 --- /dev/null +++ b/docs/sys-design-inter/03-1.md @@ -0,0 +1,511 @@ +# 四、设计速率限制器 + +在网络系统中,速率限制器用于控制客户端或服务发送流量的速率。在 HTTP 世界中,速率限制器限制了在特定时间段内允许发送的客户端请求的数量。如果 API 请求计数超过速率限制器定义的阈值,所有超出的调用都会被阻塞。下面举几个例子: + +一个用户每秒最多只能写 2 篇文章。 + +您每天最多可以从同一个 IP 地址创建 10 个账户。 + +同一台设备每周可申领奖励不超过 5 次。 + +本章要求你设计一个限速器。在开始设计之前,我们首先来看看使用 API 速率限制器的好处: + +防止拒绝服务(DoS)攻击造成的资源饥饿[1]。几乎所有大型科技公司发布的 API 都实施了某种形式的速率限制。例如,Twitter 将推文数量限制为每 3 小时 300 条[2]。Google docs APIs 有如下默认限制:每用户每 60 秒 300 个读取请求[3]。速率限制器通过阻止过量呼叫来防止有意或无意的 DoS 攻击。 + +降低成本。限制过多的请求意味着更少的服务器和分配更多的资源给高优先级的 API。速率限制对于使用付费第三方 API 的公司来说极其重要。例如,您需要为以下外部 API 的每次调用付费:检查信用、付款、检索健康记录等。限制通话次数对降低成本至关重要。 + +防止服务器过载。为了减少服务器负载,速率限制器用于过滤掉由机器人或用户不当行为引起的过量请求。 + +## 第一步——了解问题并确定设计范围 + +速率限制可以使用不同的算法来实现,每种算法都有其优缺点。面试官和候选人之间的互动有助于澄清我们试图建立的限速器的类型。 + +候选 : 我们要设计什么样的限速器?是客户端速率限制器还是服务器端 API 速率限制器? + +采访者 :好问题。我们关注服务器端 API 速率限制器。 + +候选人 : 速率限制器是否基于 IP、用户 ID 或其他属性来限制 API 请求? + +采访者 :限速器应该足够灵活,以支持不同的节流规则。 + +候选 : 系统的规模是多少?是为创业公司打造,还是为用户基数大的大公司打造? + +面试官 :系统必须能够处理大量的请求。 + +候选 : 系统会在分布式环境下工作吗? + +面试官 :是的。 + +候选 : 速率限制器是一个独立的服务还是应该在应用程序代码中实现? + +采访者 :这是你的设计决定。 + +候选 : 我们需要通知被节流的用户吗? + +面试官 :是的。 + +要求 + +以下是对系统要求的总结: + +准确限制过分的要求。 + +低潜伏期。速率限制器不应该减慢 HTTP 响应时间。 + +尽量少用内存。 + +分布式限速。速率限制器可以在多个服务器或进程之间共享。 + +异常处理。当用户的请求被限制时,向用户显示明确的异常。 + +容错性高。如果速率限制器出现任何问题(例如,缓存服务器离线),不会影响整个系统。 + +## 第二步——提出高水平的设计并获得认同 + +让我们保持简单,使用基本的客户端和服务器模型进行通信。 + +### 限速器放哪里? + +直观地说,你可以在客户端或服务器端实现速率限制器。 + +客户端实现。一般来说,客户端是一个不可靠的实施速率限制的地方,因为客户端请求很容易被恶意参与者伪造。此外,我们可能无法控制客户端的实现。 + +服务器端实现。图 4-1 显示了放置在服务器端的速率限制器。 + +![A picture containing clock Description automatically generated](../images/00035.jpeg) + +除了客户端和服务器端的实现,还有另一种方法。我们没有在 API 服务器上设置速率限制器,而是创建了一个速率限制器中间件,来抑制对 API 的请求,如图 4-2 所示。 + +![A screenshot of a cell phone Description automatically generated](../images/00036.jpeg) + +让我们用图 4-3 中的例子来说明速率限制在这个设计中是如何工作的。假设我们的 API 允许每秒 2 个请求,一个客户机在一秒钟内向服务器发送 3 个请求。前两个请求被路由到 API 服务器。然而,速率限制器中间件抑制了第三个请求,并返回一个 HTTP 状态代码 429。HTTP 429 响应状态代码表示用户发送了太多请求。 + +![A screenshot of a cell phone Description automatically generated](../images/00037.jpeg) + +云微服务[4]已经变得非常流行,速率限制通常在一个名为 API gateway 的组件中实现。API gateway 是一种完全托管的服务,支持速率限制、SSL 终止、身份验证、IP 白名单、静态内容服务等。现在,我们只需要知道 API 网关是一个支持速率限制的中间件。 + +在设计速率限制器时,我们要问自己的一个重要问题是:应该在哪里实现速率限制器,在服务器端还是在网关中?没有绝对的答案。这取决于您公司当前的技术堆栈、工程资源、优先级、目标等。这里有几个通用的准则: + +评估你目前的技术栈,比如编程语言、缓存服务等。确保您当前的编程语言能够有效地在服务器端实现速率限制。 + +确定适合您业务需求的速率限制算法。当你在服务器端实现一切时,你就完全控制了算法。但是,如果您使用第三方网关,您的选择可能会受到限制。 + +如果你已经使用了微服务架构,并且在设计中包含了 API 网关来执行认证、IP 白名单等。,您可以向 API 网关添加速率限制器。 + +自建费率 限制服务需要时间。如果您没有足够的工程资源来实现速率限制器,商业 API 网关是一个更好的选择。 + +### 限速算法 + +速率限制可以使用不同的算法来实现,每种算法都有明显的优缺点。尽管本章并不关注算法,但从高层次理解它们有助于选择合适的算法或算法组合来适应我们的用例。下面是流行算法的列表: + +令牌桶 + +漏桶 + +固定窗口计数器 + +滑动窗口日志 + +推拉窗计数器 + +#### 令牌桶算法 + +令牌桶算法广泛用于速率限制。简单易懂,互联网公司常用。Amazon [5]和 Stripe [6]都使用这种算法来抑制他们的 API 请求。 + +令牌桶算法工作如下: + +令牌桶是具有预定义容量的容器。令牌以预设的速率定期放入桶中。一旦桶满了,就不再添加令牌。如图 4-4 所示,令牌桶容量为 4。加油员每秒钟往桶里放 2 个代币。一旦桶满了,额外的代币就会溢出。 + +![A picture containing game, table Description automatically generated](../images/00038.jpeg) + +每个请求消耗一个令牌。当请求到达时,我们检查桶中是否有足够的令牌。图 4-5 解释了它是如何工作的。 + +如果有足够的令牌,我们为每个请求取出一个令牌,请求通过。 + +如果没有足够的令牌,请求被丢弃。 + +![A close up of a map Description automatically generated](../images/00039.jpeg) + +图 4-6 说明了代币消费、 、 和速率限制逻辑是如何工作的。在此示例中,令牌桶大小为 4,再填充速率为每 1 分钟 4 次。 + +![A close up of text on a white background Description automatically generated](../images/00040.jpeg) + +令牌桶算法采用两个参数: + +桶大小:桶中允许的最大令牌数 + +充值率:每秒钟投入桶中的代币数量 + +我们需要多少桶?这是不同的,它取决于限速规则。这里有几个例子。 + +对于不同的 API 端点,通常需要有不同的桶。例如,如果允许用户每秒发 1 篇帖子,每天添加 150 个朋友,每秒 5 篇帖子,则每个用户需要 3 个桶。 + +如果我们需要根据 IP 地址限制请求,每个 IP 地址都需要一个桶。 + +如果系统允许每秒最多 10,000 个请求,那么让所有请求共享一个全局桶是有意义的。 + +优点: + +该算法易于实现。 + +记忆高效。 + +令牌桶允许短时间的流量爆发。只要还有令牌,请求就可以通过。 + +缺点: + +算法中的两个参数是桶大小和令牌再填充率。然而,对它们进行适当的调优可能是一个挑战。 + +#### 漏桶算法 + +泄漏桶算法类似于令牌桶,只是请求以固定速率处理。它通常用先进先出(FIFO)队列来实现。算法工作如下: + +当请求到达时,系统检查队列是否已满。如果未满,请求将被添加到队列中。 + +否则,请求被放弃。 + +请求被从队列中取出并定期处理。 + +图 4-7 解释了算法的工作原理。 + +![A close up of a map Description automatically generated](../images/00041.jpeg) + +漏桶算法取以下两个参数: + +桶大小:等于队列大小。该队列以固定的速率保存要处理的请求。 + +流出率:它定义了以固定的速率可以处理多少个请求,通常以秒为单位。 + +电子商务公司 Shopify 使用漏桶进行限速[7]。 + +优点: + +给定有限队列大小的内存效率。 + +请求以固定速率处理,因此适用于需要稳定流出速率的用例。 + +缺点: + +突发流量用旧请求填满队列,如果不及时处理,最近的请求将受到速率限制。 + +算法中有两个参数。正确地调整它们可能不容易。 + +#### 固定窗口计数器算法 + +固定窗口计数器算法工作如下: + +该算法将时间轴分为固定大小的时间窗口,并为每个窗口分配一个计数器。 + +每个请求都使计数器加 1。 + +一旦计数器达到预定义的阈值,新的请求就会被丢弃,直到新的时间窗口开始。 + +让我们用一个具体的例子来看看它是如何工作的。在图 4-8 中,时间单位是 1 秒,系统允许每秒最多 3 次请求。在每个第二个窗口中,如果收到 3 个以上的请求,多余的请求将被丢弃,如图 4-8 所示。 + +![A screenshot of a cell phone Description automatically generated](../images/00042.jpeg) + +此算法的一个主要问题是,时间窗口边缘的流量突发可能会导致超过允许配额的请求通过。考虑以下情况: + +![A screenshot of a social media post Description automatically generated](../images/00043.jpeg) + +在图 4-9 中,系统允许每分钟最多 5 个请求,可用配额在人性化的整数分钟重置。如图所示,在 2:00:00 和 2:01:00 之间有五个请求,在 2:01:00 和 2:02:00 之间还有五个请求。对于 2:00:30 到 2:01:30 之间的一分钟窗口,有 10 个请求通过。这是允许请求的两倍。 + +优点: + +记忆高效。 + +通俗易懂。 + +在单位时间窗口结束时重置可用配额适合某些用例。 + +缺点: + +窗口边缘的流量峰值可能会导致超过允许配额的请求通过。 + +#### 滑动窗口日志算法 + +如前所述,固定窗口计数器算法有一个主要问题:它允许更多的请求在窗口边缘通过。滑动窗口日志算法解决了这个问题。其工作原理如下: + +该算法跟踪请求时间戳。时间戳数据通常保存在缓存中,例如 Redis 的排序集[8]。 + +当一个新的请求进来时,删除所有过时的时间戳。过时的时间戳被定义为比当前时间窗口的开始时间更早的时间戳。 + +将新请求的时间戳添加到日志中。 + +如果日志大小等于或小于允许的计数,则接受请求。否则,它被拒绝。 + +我们用一个如图 4-10 所示的例子来解释这个算法。 + +![A close up of text on a white background Description automatically generated](../images/00044.jpeg) + +在本例中,速率限制器允许每分钟 2 个请求。通常,Linux 时间戳存储在日志中。然而,为了更好的可读性,在我们的例子中使用了人类可读的时间表示。 + +新请求在 1:00:01 到达时,日志为空。因此,请求被允许。 + +一个新请求在 1:00:30 到达,时间戳 1:00:30 被插入到日志中。插入后,日志大小为 2,不超过允许的计数。因此,请求被允许。 + +一个新请求在 1:00:50 到达,时间戳被插入到日志中。插入后,日志大小为 3,大于允许的大小 2。因此,即使时间戳保留在日志中,该请求也会被拒绝。 + +一个新的请求到达 1:01:40。范围[1:00:40,1:01:40]内的请求在最新的时间范围内,但是在 1:00:40 之前发送的请求已经过时。从日志中删除了两个过期的时间戳:1:00:01 和 1:00:30。删除操作之后,日志大小变为 2;因此,请求被接受。 + +优点: + +该算法实现的速率限制非常精确。在任何滚动窗口中,请求都不会超过速率限制。 + +缺点: + +该算法消耗大量内存,因为即使请求被拒绝,其时间戳仍可能存储在内存中。 + +#### 滑动窗口计数器算法 + +滑动窗口计数器算法是一种结合了固定窗口计数器和滑动窗口日志的混合方法。该算法可以通过两种不同的方法来实现。我们将在本节中解释一个实现,并在本节的最后为另一个实现提供参考。图 4-11 说明了这种算法是如何工作的。 + +![A picture containing clock Description automatically generated](../images/00045.jpeg) + +假设速率限制器每分钟最多允许 7 个请求,前一分钟有 5 个请求,当前分钟有 3 个请求。对于在当前分钟到达 30%位置的新请求,使用以下公式计算滚动窗口中的请求数: + +请求当前窗口 + 请求前一窗口 * 滚动窗口与前一窗口的重叠百分比 + +利用这个公式,我们得到 3 + 5 * 0.7% = 6.5 的请求。根据使用情况,该数字可以向上或向下取整。在我们的例子中,它被向下舍入到 6。 + +由于速率限制器允许每分钟最多 7 个请求,当前请求可以通过。但是,在再收到一个请求后,将达到限制。 + +由于篇幅限制,我们在这里不讨论其他实现。感兴趣的读者应该参考参考资料[9]。这个算法并不完美。它有利有弊。 + +优点 + +它平滑流量峰值,因为速率是基于前一窗口的平均速率。 + +记忆高效。 + +缺点 + +只对不那么严格的回望窗有效。这是实际速率的近似值,因为它假设前一个窗口中的请求是均匀分布的。然而,这个问题可能没有看起来那么糟糕。根据 Cloudflare [10]所做的实验,在 4 亿个请求中,只有 0.003%的请求被错误地允许或速率受限。 + +### 高层建筑 + +速率限制算法的基本思想很简单。在高层,我们需要一个计数器来跟踪从同一个用户、IP 地址等发出了多少请求。如果计数器大于限制值,则不允许请求。 + +我们应该把柜台放在哪里?由于磁盘访问缓慢,使用数据库不是一个好主意。选择内存缓存是因为它速度快,并且支持基于时间的过期策略。例如,Redis [11]是实现利率限制的一个流行选项。它是一个内存存储,提供两个命令:INCR 和过期。 + +INCR:存储的计数器加 1。 + +到期:设置计数器超时。如果超时,计数器将自动删除。 + +图 4-12 显示了速率限制的高级架构,其工作原理如下: + +![A close up of a map Description automatically generated](../images/00046.jpeg) + +客户端向限速中间件发送请求。 + +限速中间件从 Redis 中相应的桶中取出计数器,检查是否达到限额。 + +如果达到限制,请求被拒绝。 + +如果没有达到限制,请求被发送到 API 服务器。同时,系统递增计数器并将其保存回 Redis。 + +## 第三步——设计深潜 + +图 4-12 中的高层设计没有回答以下问题: + +限速规则是如何创建的?规则存储在哪里? + +如何处理速率受限的请求? + +在本节中,我们将首先回答有关速率限制规则的问题,然后讨论处理速率限制请求的策略。最后,我们将讨论分布式环境中的速率限制、详细设计、性能优化和监控。 + +### 限速规则 + +Lyft 开源了他们的限速组件[12]。我们将查看组件内部,并查看一些速率限制规则的示例: + +域 : 消息传递 + +描述符 : + +--按键-:-消息 _ 类型 + +价值 :营销 + +利率 _ 限额 : + +单位 : 日 + +请求 _ 每单位 : 5 + +在上述示例中,系统被配置为每天最多允许 5 条营销消息。下面是另一个例子: + +域名 : 认证 + +描述符 : + +--键-:-auth _ type + +值 :登录 + +利率 _ 限额 : + +单位 :分钟 + +请求 _ 每单位 : 5 + +该规则显示,客户端不允许在 1 分钟内登录 5 次以上。规则通常写在配置文件中并保存在磁盘上。 + +### 超过速率限制 + +如果请求是速率受限的,API 会向客户端返回一个 HTTP 响应代码 429(请求太多)。根据用例的不同,我们可能会将速率受限的请求排入队列,以便稍后处理。例如,如果一些订单由于系统过载而受到费率限制,我们可能会将这些订单留待以后处理。 + +#### 限速器标题 + +客户端如何知道它是否被节流?客户端如何知道在被节流之前允许的剩余请求的数量?答案就在 HTTP 响应头中。速率限制器向客户端返回以下 HTTP 报头: + +X-rate limit-Remaining:窗口内允许请求的剩余数量。 + +X-Ratelimit-Limit: 表示客户在每个时间窗口内可以拨打多少个电话。 + +X-rate limit-Retry-After:等待的秒数,直到可以再次发出请求而不被限制。 + +当用户发送过多请求时,向客户端返回 429 过多请求错误和X-rate limit-Retry-After报头。 + +### 详细设计 + +图 4-13 展示了系统的详细设计。 + +![A close up of a map Description automatically generated](../images/00047.jpeg) + +规则存储在磁盘上。工作人员经常从磁盘中提取规则,并将它们存储在缓存中。 + +当客户端向服务器发送请求时,请求首先被发送到限速器中间件。 + +限速器中间件从缓存中加载规则。它从 Redis 缓存中获取计数器和上次请求时间戳。基于该响应,速率限制器决定: + +如果请求没有速率限制,则转发给 API 服务器。 + +如果请求是速率受限的,速率限制器向客户端返回 429 过多请求错误。与此同时,请求要么被丢弃,要么被转发到队列。 + +### 分布式环境中的限速器 + +构建一个在单一服务器环境中工作的速率限制器并不困难。然而,扩展系统以支持多个服务器和并发线程是另一回事。有两个挑战: + +比赛条件 + +同步发布 + +#### 比赛状态 + +如前所述,速率限制器在高层工作如下: + +从 Redis 中读取 计数器 的值。 + +检查 ( 计数器+1)是否超过阈值。 + +如果不是,Redis 中的计数器值加 1。 + +如图 4-14 所示,竞争条件可能发生在高度并发的环境中。 + +![A picture containing screenshot Description automatically generated](../images/00048.jpeg) + +假设 Redis 中的 计数器 的值为 3。如果两个请求同时读取 计数器 的值,在它们中的任何一个写回该值之前,每个请求都将把 计数器 加 1,并写回该值,而不检查另一个线程。两个请求(线程)都认为它们拥有正确的 计数器 值 4。然而,正确的 计数器的 值应该是 5。 + +锁是解决竞争状况的最明显的解决方案。但是,锁会显著降低系统速度。有两种策略常用来解决这个问题:Lua 脚本[13]和 Redis 中的有序集数据结构[8]。对这些策略感兴趣的读者,可以参考相应的参考资料[8] [13]。 + +#### 同步发布 + +同步是分布式环境中需要考虑的另一个重要因素。为了支持数百万用户,一台限速服务器可能不足以处理流量。当使用多个速率限制器服务器时,需要同步。例如,在图 4-15 的左侧,客户端 1 向速率限制器 1 发送请求,客户端 2 向速率限制器 2 发送请求。由于 web 层是无状态的,客户端可以向不同的速率限制器发送请求,如图 4-15 右侧所示。如果没有同步发生,速率限制器 1 不包含关于客户端 2 的任何数据。因此,限速器不能正常工作。 + +![A picture containing text, map Description automatically generated](../images/00049.jpeg) + +一种可能的解决方案是使用粘性会话,允许客户端向同一个速率限制器发送流量。这种解决方案是不可取的,因为它既不可伸缩也不灵活。更好的方法是使用像 Redis 这样的集中式数据存储。设计如图 4-16 所示。 + +![A close up of a map Description automatically generated](../images/00050.jpeg) + +### 性能优化 + +性能优化是系统设计面试中的常见话题。我们将从两个方面进行改进。 + +首先,多数据中心设置对于速率限制器至关重要,因为对于远离数据中心的用户来说,延迟很高。大多数云服务提供商在世界各地建立了许多边缘服务器。例如,截至 20 20 年 5 月 20 日,Cloudflare 拥有 194 台地理位置分散的边缘服务器[14]。流量被自动路由到最近的边缘服务器,以减少延迟。 + +![A close up of a logo Description automatically generated](../images/00051.jpeg) + +第二,用最终的一致性模型同步数据。如果您不清楚最终的一致性模型,请参考“第 6 章:设计键值存储”中的“一致性”一节 + +### 监控 + +实施限速器后,收集分析数据以检查限速器是否有效非常重要。首先,我们要确保: + +限速算法有效。 + +限速规则有效。 + +例如,如果速率限制规则太严格,许多有效请求会被丢弃。在这种情况下,我们希望稍微放宽一些规则。在另一个例子中,我们注意到当流量突然增加时,如闪购,我们的限速器变得无效。在这种情况下,我们可以替换算法来支持突发流量。令牌桶很适合这里。 + +## 第四步——总结 + +在本章中,我们讨论了不同的速率限制算法及其优缺点。讨论的算法包括: + +令牌桶 + +漏桶 + +固定窗口 + +滑动窗口日志 + +推拉窗计数器 + +然后,我们讨论了系统架构、分布式环境中的速率限制器、性能优化和监控。与任何系统设计面试问题类似,如果时间允许,您还可以提及其他话题: + +硬 vs 软限速。 + +硬:请求数量不能超过阈值。 + +软:请求可以在短时间内超过阈值。 + +分级限速。在本章中,我们只讨论了应用层(HTTP:第 7 层)的速率限制。有可能在其他层应用速率限制。例如,您可以使用 Iptables [15] (IP:第 3 层)通过 IP 地址应用速率限制。注:开放系统互连模型(OSI 模型)有 7 层[16]:第 1 层:物理层,第 2 层:数据链路层,第 3 层:网络层,第 4 层:传输层,第 5 层:会话层,第 6 层:表示层,第 7 层:应用层。 + +避免被费率限制。用最佳实践设计您的客户端: + +使用客户端缓存避免频繁的 API 调用。 + +了解限制,不要在短时间内发送太多请求。 + +包含捕捉异常或错误的代码,以便您的客户端能够从容地从异常中恢复。 + +添加足够的回退时间以重试逻辑。 + +祝贺你走到这一步!现在给自己一个鼓励。干得好! + +参考资料 + +【1】限速策略与技巧:[https://cloud . Google . com/solutions/Rate-limiting-strategies-techniques](https://cloud.google.com/solutions/rate-limiting-strategies-techniques) + +【2】推特速率限制:[](https://developer.twitter.com/en/docs/basics/rate-limits) + +【3】谷歌文档使用限制:[【https://developers.google.com/docs/api/limits】](https://developers.google.com/docs/api/limits) + +[4] IBM 微服务:[https://www.ibm.com/cloud/learn/microservices](https://www.ibm.com/cloud/learn/microservices) + +[5]抑制 API 请求以提高吞吐量: + +[https://docs . AWS . Amazon . com/API gateway/latest/developer guide/API-gateway-request-throttling . htmlT3】](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-request-throttling.html) + +【6】条纹限速器:[](https://stripe.com/blog/rate-limiters) + +【7】Shopify REST Admin API 费率限制:[https://help . Shopify . com/en/API/reference/REST-Admin-API-rate-limits](https://help.shopify.com/en/api/reference/rest-admin-api-rate-limits) + +【8】Redis 排序集合更好的限速:[https://engineering . classdojo . com/blog/2015/02/06/rolling-Rate-limiter/](https://engineering.classdojo.com/blog/2015/02/06/rolling-rate-limiter/) + +【9】系统设计—速率限制器及数据建模:[https://medium . com/@ saisandeepmopuri/System-Design-Rate-limiter-and-Data-modeling-9304 b0d 18250](https://medium.com/@saisandeepmopuri/system-design-rate-limiter-and-data-modelling-9304b0d18250) + +[10]我们如何构建能够扩展到数百万个域的速率限制:[https://blog . cloud flare . com/counting-things-a-lot-of-different-things/](https://blog.cloudflare.com/counting-things-a-lot-of-different-things/) + +[11] Redis 网站:[https://redis.io/](https://redis.io/) + +【12】Lyft 限速:[](https://github.com/lyft/ratelimit) + +[13]使用速率限制器扩展您的 API:[https://gist . github . com/ptar Jan/e 38 f 45 F2 dfe 601419 ca 3 af 937 fff 574d #请求速率限制器](https://gist.github.com/ptarjan/e38f45f2dfe601419ca3af937fff574d#request-rate-limiter) + +【14】什么是边缘计算:[https://www . cloud flare . com/learning/server less/glossary/What-is-edge-computing/](https://www.cloudflare.com/learning/serverless/glossary/what-is-edge-computing/) + +【15】带 Iptables 的速率限制请求:[https://blog . programster . org/Rate-Limit-Requests-with-Iptables](https://blog.programster.org/rate-limit-requests-with-iptables) + +【16】OSI 模型:[https://en.wikipedia.org/wiki/OSI_model#Layer_architecture](https://en.wikipedia.org/wiki/OSI_model#Layer_architecture) \ No newline at end of file diff --git a/docs/sys-design-inter/03.md b/docs/sys-design-inter/03.md index a52fbdc..f9cdb8f 100644 --- a/docs/sys-design-inter/03.md +++ b/docs/sys-design-inter/03.md @@ -1,3 +1,5 @@ +# 三、系统设计面试的框架 + 你刚刚获得了梦寐以求的理想公司的现场面试机会。招聘协调员会给你发一份当天的日程表。浏览列表,你会感觉很好,直到你的目光落在这个面试环节——系统设计面试。 系统设计面试通常令人生畏。它可以像“设计一个知名产品 X?”。这些问题模棱两可,而且似乎过于宽泛。你的疲倦是可以理解的。毕竟,怎么可能有人在一个小时内设计出一款流行的产品,而这需要数百甚至数千名工程师来完成? @@ -126,7 +128,7 @@ Feed 发布:当用户发布帖子时,相应的数据被写入缓存/数据库 根据她的反馈,对深度探索中需要关注的领域有了一些初步想法 -你应该和面试官一起确定架构中组件的优先级。值得强调的是,每次面试都是不同的。有时候,面试官可能会暗示她喜欢关注高层次的设计。有时,对于高级候选人面试,讨论可能是关于系统性能特征,可能集中在瓶颈和资源估计上。在大多数情况下,面试官可能希望你深入了解一些系统组件的细节。对于 URL shortener,深入研究将长 URL 转换成短 URL 的散列函数设计是很有趣的。对于一个聊天系统来说,如何减少延迟和如何支持在线/离线状态是两个有趣的话题。 +你应该和面试官一起确定架构中组件的优先级。值得强调的是,每次面试都是不同的。有时候,面试官可能会暗示她喜欢关注高层次的设计。有时,对于高级候选人面试,讨论可能是关于系统性能特征,可能集中在瓶颈和资源估计上。在大多数情况下,面试官可能希望你深入了解一些系统组件的细节。对于 URL shortener,深入研究将长 URL 转换成短 URL 的哈希函数设计是很有趣的。对于一个聊天系统来说,如何减少延迟和如何支持在线/离线状态是两个有趣的话题。 时间管理是必不可少的,因为人们很容易被无法展示你能力的微小细节冲昏头脑。你必须准备好向面试官展示的信号。尽量不要陷入不必要的细节。例如,在系统设计面试中详细讨论脸书 feed 排名的 EdgeRank 算法是不理想的,因为这会花费很多宝贵的时间,并且不能证明你设计可扩展系统的能力。 @@ -205,513 +207,3 @@ Dos 第三步设计深潜:10 - 25 分钟 第四步包装:3 - 5 分钟 - -在网络系统中,速率限制器用于控制客户端或服务发送流量的速率。在 HTTP 世界中,速率限制器限制了在特定时间段内允许发送的客户端请求的数量。如果 API 请求计数超过速率限制器定义的阈值,所有超出的调用都会被阻塞。下面举几个例子: - -一个用户每秒最多只能写 2 篇文章。 - -您每天最多可以从同一个 IP 地址创建 10 个账户。 - -同一台设备每周可申领奖励不超过 5 次。 - -本章要求你设计一个限速器。在开始设计之前,我们首先来看看使用 API 速率限制器的好处: - -防止拒绝服务(DoS)攻击造成的资源饥饿[1]。几乎所有大型科技公司发布的 API 都实施了某种形式的速率限制。例如,Twitter 将推文数量限制为每 3 小时 300 条[2]。Google docs APIs 有如下默认限制:每用户每 60 秒 300 个读取请求[3]。速率限制器通过阻止过量呼叫来防止有意或无意的 DoS 攻击。 - -降低成本。限制过多的请求意味着更少的服务器和分配更多的资源给高优先级的 API。速率限制对于使用付费第三方 API 的公司来说极其重要。例如,您需要为以下外部 API 的每次调用付费:检查信用、付款、检索健康记录等。限制通话次数对降低成本至关重要。 - -防止服务器过载。为了减少服务器负载,速率限制器用于过滤掉由机器人或用户不当行为引起的过量请求。 - -## 第一步——了解问题并确定设计范围 - -速率限制可以使用不同的算法来实现,每种算法都有其优缺点。面试官和候选人之间的互动有助于澄清我们试图建立的限速器的类型。 - -候选 : 我们要设计什么样的限速器?是客户端速率限制器还是服务器端 API 速率限制器? - -采访者 :好问题。我们关注服务器端 API 速率限制器。 - -候选人 : 速率限制器是否基于 IP、用户 ID 或其他属性来限制 API 请求? - -采访者 :限速器应该足够灵活,以支持不同的节流规则。 - -候选 : 系统的规模是多少?是为创业公司打造,还是为用户基数大的大公司打造? - -面试官 :系统必须能够处理大量的请求。 - -候选 : 系统会在分布式环境下工作吗? - -面试官 :是的。 - -候选 : 速率限制器是一个独立的服务还是应该在应用程序代码中实现? - -采访者 :这是你的设计决定。 - -候选 : 我们需要通知被节流的用户吗? - -面试官 :是的。 - -要求 - -以下是对系统要求的总结: - -准确限制过分的要求。 - -低潜伏期。速率限制器不应该减慢 HTTP 响应时间。 - -尽量少用内存。 - -分布式限速。速率限制器可以在多个服务器或进程之间共享。 - -异常处理。当用户的请求被限制时,向用户显示明确的异常。 - -容错性高。如果速率限制器出现任何问题(例如,缓存服务器离线),不会影响整个系统。 - -## 第二步——提出高水平的设计并获得认同 - -让我们保持简单,使用基本的客户端和服务器模型进行通信。 - -### 限速器放哪里? - -直观地说,你可以在客户端或服务器端实现速率限制器。 - -客户端实现。一般来说,客户端是一个不可靠的实施速率限制的地方,因为客户端请求很容易被恶意参与者伪造。此外,我们可能无法控制客户端的实现。 - -服务器端实现。图 4-1 显示了放置在服务器端的速率限制器。 - -![A picture containing clock Description automatically generated](../images/00035.jpeg) - -除了客户端和服务器端的实现,还有另一种方法。我们没有在 API 服务器上设置速率限制器,而是创建了一个速率限制器中间件,来抑制对 API 的请求,如图 4-2 所示。 - -![A screenshot of a cell phone Description automatically generated](../images/00036.jpeg) - -让我们用图 4-3 中的例子来说明速率限制在这个设计中是如何工作的。假设我们的 API 允许每秒 2 个请求,一个客户机在一秒钟内向服务器发送 3 个请求。前两个请求被路由到 API 服务器。然而,速率限制器中间件抑制了第三个请求,并返回一个 HTTP 状态代码 429。HTTP 429 响应状态代码表示用户发送了太多请求。 - -![A screenshot of a cell phone Description automatically generated](../images/00037.jpeg) - -云微服务[4]已经变得非常流行,速率限制通常在一个名为 API gateway 的组件中实现。API gateway 是一种完全托管的服务,支持速率限制、SSL 终止、身份验证、IP 白名单、静态内容服务等。现在,我们只需要知道 API 网关是一个支持速率限制的中间件。 - -在设计速率限制器时,我们要问自己的一个重要问题是:应该在哪里实现速率限制器,在服务器端还是在网关中?没有绝对的答案。这取决于您公司当前的技术堆栈、工程资源、优先级、目标等。这里有几个通用的准则: - -评估你目前的技术栈,比如编程语言、缓存服务等。确保您当前的编程语言能够有效地在服务器端实现速率限制。 - -确定适合您业务需求的速率限制算法。当你在服务器端实现一切时,你就完全控制了算法。但是,如果您使用第三方网关,您的选择可能会受到限制。 - -如果你已经使用了微服务架构,并且在设计中包含了 API 网关来执行认证、IP 白名单等。,您可以向 API 网关添加速率限制器。 - -自建费率 限制服务需要时间。如果您没有足够的工程资源来实现速率限制器,商业 API 网关是一个更好的选择。 - -### 限速算法 - -速率限制可以使用不同的算法来实现,每种算法都有明显的优缺点。尽管本章并不关注算法,但从高层次理解它们有助于选择合适的算法或算法组合来适应我们的用例。下面是流行算法的列表: - -令牌桶 - -漏桶 - -固定窗口计数器 - -滑动窗口日志 - -推拉窗计数器 - -#### 令牌桶算法 - -令牌桶算法广泛用于速率限制。简单易懂,互联网公司常用。Amazon [5]和 Stripe [6]都使用这种算法来抑制他们的 API 请求。 - -令牌桶算法工作如下: - -令牌桶是具有预定义容量的容器。令牌以预设的速率定期放入桶中。一旦桶满了,就不再添加令牌。如图 4-4 所示,令牌桶容量为 4。加油员每秒钟往桶里放 2 个代币。一旦桶满了,额外的代币就会溢出。 - -![A picture containing game, table Description automatically generated](../images/00038.jpeg) - -每个请求消耗一个令牌。当请求到达时,我们检查桶中是否有足够的令牌。图 4-5 解释了它是如何工作的。 - -如果有足够的令牌,我们为每个请求取出一个令牌,请求通过。 - -如果没有足够的令牌,请求被丢弃。 - -![A close up of a map Description automatically generated](../images/00039.jpeg) - -图 4-6 说明了代币消费、 、 和速率限制逻辑是如何工作的。在此示例中,令牌桶大小为 4,再填充速率为每 1 分钟 4 次。 - -![A close up of text on a white background Description automatically generated](../images/00040.jpeg) - -令牌桶算法采用两个参数: - -桶大小:桶中允许的最大令牌数 - -充值率:每秒钟投入桶中的代币数量 - -我们需要多少桶?这是不同的,它取决于限速规则。这里有几个例子。 - -对于不同的 API 端点,通常需要有不同的桶。例如,如果允许用户每秒发 1 篇帖子,每天添加 150 个朋友,每秒 5 篇帖子,则每个用户需要 3 个桶。 - -如果我们需要根据 IP 地址限制请求,每个 IP 地址都需要一个桶。 - -如果系统允许每秒最多 10,000 个请求,那么让所有请求共享一个全局桶是有意义的。 - -优点: - -该算法易于实现。 - -记忆高效。 - -令牌桶允许短时间的流量爆发。只要还有令牌,请求就可以通过。 - -缺点: - -算法中的两个参数是桶大小和令牌再填充率。然而,对它们进行适当的调优可能是一个挑战。 - -#### 漏桶算法 - -泄漏桶算法类似于令牌桶,只是请求以固定速率处理。它通常用先进先出(FIFO)队列来实现。算法工作如下: - -当请求到达时,系统检查队列是否已满。如果未满,请求将被添加到队列中。 - -否则,请求被放弃。 - -请求被从队列中取出并定期处理。 - -图 4-7 解释了算法的工作原理。 - -![A close up of a map Description automatically generated](../images/00041.jpeg) - -漏桶算法取以下两个参数: - -桶大小:等于队列大小。该队列以固定的速率保存要处理的请求。 - -流出率:它定义了以固定的速率可以处理多少个请求,通常以秒为单位。 - -电子商务公司 Shopify 使用漏桶进行限速[7]。 - -优点: - -给定有限队列大小的内存效率。 - -请求以固定速率处理,因此适用于需要稳定流出速率的用例。 - -缺点: - -突发流量用旧请求填满队列,如果不及时处理,最近的请求将受到速率限制。 - -算法中有两个参数。正确地调整它们可能不容易。 - -#### 固定窗口计数器算法 - -固定窗口计数器算法工作如下: - -该算法将时间轴分为固定大小的时间窗口,并为每个窗口分配一个计数器。 - -每个请求都使计数器加 1。 - -一旦计数器达到预定义的阈值,新的请求就会被丢弃,直到新的时间窗口开始。 - -让我们用一个具体的例子来看看它是如何工作的。在图 4-8 中,时间单位是 1 秒,系统允许每秒最多 3 次请求。在每个第二个窗口中,如果收到 3 个以上的请求,多余的请求将被丢弃,如图 4-8 所示。 - -![A screenshot of a cell phone Description automatically generated](../images/00042.jpeg) - -此算法的一个主要问题是,时间窗口边缘的流量突发可能会导致超过允许配额的请求通过。考虑以下情况: - -![A screenshot of a social media post Description automatically generated](../images/00043.jpeg) - -在图 4-9 中,系统允许每分钟最多 5 个请求,可用配额在人性化的整数分钟重置。如图所示,在 2:00:00 和 2:01:00 之间有五个请求,在 2:01:00 和 2:02:00 之间还有五个请求。对于 2:00:30 到 2:01:30 之间的一分钟窗口,有 10 个请求通过。这是允许请求的两倍。 - -优点: - -记忆高效。 - -通俗易懂。 - -在单位时间窗口结束时重置可用配额适合某些用例。 - -缺点: - -窗口边缘的流量峰值可能会导致超过允许配额的请求通过。 - -#### 滑动窗口日志算法 - -如前所述,固定窗口计数器算法有一个主要问题:它允许更多的请求在窗口边缘通过。滑动窗口日志算法解决了这个问题。其工作原理如下: - -该算法跟踪请求时间戳。时间戳数据通常保存在缓存中,例如 Redis 的排序集[8]。 - -当一个新的请求进来时,删除所有过时的时间戳。过时的时间戳被定义为比当前时间窗口的开始时间更早的时间戳。 - -将新请求的时间戳添加到日志中。 - -如果日志大小等于或小于允许的计数,则接受请求。否则,它被拒绝。 - -我们用一个如图 4-10 所示的例子来解释这个算法。 - -![A close up of text on a white background Description automatically generated](../images/00044.jpeg) - -在本例中,速率限制器允许每分钟 2 个请求。通常,Linux 时间戳存储在日志中。然而,为了更好的可读性,在我们的例子中使用了人类可读的时间表示。 - -新请求在 1:00:01 到达时,日志为空。因此,请求被允许。 - -一个新请求在 1:00:30 到达,时间戳 1:00:30 被插入到日志中。插入后,日志大小为 2,不超过允许的计数。因此,请求被允许。 - -一个新请求在 1:00:50 到达,时间戳被插入到日志中。插入后,日志大小为 3,大于允许的大小 2。因此,即使时间戳保留在日志中,该请求也会被拒绝。 - -一个新的请求到达 1:01:40。范围[1:00:40,1:01:40]内的请求在最新的时间范围内,但是在 1:00:40 之前发送的请求已经过时。从日志中删除了两个过期的时间戳:1:00:01 和 1:00:30。删除操作之后,日志大小变为 2;因此,请求被接受。 - -优点: - -该算法实现的速率限制非常精确。在任何滚动窗口中,请求都不会超过速率限制。 - -缺点: - -该算法消耗大量内存,因为即使请求被拒绝,其时间戳仍可能存储在内存中。 - -#### 滑动窗口计数器算法 - -滑动窗口计数器算法是一种结合了固定窗口计数器和滑动窗口日志的混合方法。该算法可以通过两种不同的方法来实现。我们将在本节中解释一个实现,并在本节的最后为另一个实现提供参考。图 4-11 说明了这种算法是如何工作的。 - -![A picture containing clock Description automatically generated](../images/00045.jpeg) - -假设速率限制器每分钟最多允许 7 个请求,前一分钟有 5 个请求,当前分钟有 3 个请求。对于在当前分钟到达 30%位置的新请求,使用以下公式计算滚动窗口中的请求数: - -请求当前窗口 + 请求前一窗口 * 滚动窗口与前一窗口的重叠百分比 - -利用这个公式,我们得到 3 + 5 * 0.7% = 6.5 的请求。根据使用情况,该数字可以向上或向下取整。在我们的例子中,它被向下舍入到 6。 - -由于速率限制器允许每分钟最多 7 个请求,当前请求可以通过。但是,在再收到一个请求后,将达到限制。 - -由于篇幅限制,我们在这里不讨论其他实现。感兴趣的读者应该参考参考资料[9]。这个算法并不完美。它有利有弊。 - -优点 - -它平滑流量峰值,因为速率是基于前一窗口的平均速率。 - -记忆高效。 - -缺点 - -只对不那么严格的回望窗有效。这是实际速率的近似值,因为它假设前一个窗口中的请求是均匀分布的。然而,这个问题可能没有看起来那么糟糕。根据 Cloudflare [10]所做的实验,在 4 亿个请求中,只有 0.003%的请求被错误地允许或速率受限。 - -### 高层建筑 - -速率限制算法的基本思想很简单。在高层,我们需要一个计数器来跟踪从同一个用户、IP 地址等发出了多少请求。如果计数器大于限制值,则不允许请求。 - -我们应该把柜台放在哪里?由于磁盘访问缓慢,使用数据库不是一个好主意。选择内存缓存是因为它速度快,并且支持基于时间的过期策略。例如,Redis [11]是实现利率限制的一个流行选项。它是一个内存存储,提供两个命令:INCR 和过期。 - -INCR:存储的计数器加 1。 - -到期:设置计数器超时。如果超时,计数器将自动删除。 - -图 4-12 显示了速率限制的高级架构,其工作原理如下: - -![A close up of a map Description automatically generated](../images/00046.jpeg) - -客户端向限速中间件发送请求。 - -限速中间件从 Redis 中相应的桶中取出计数器,检查是否达到限额。 - -如果达到限制,请求被拒绝。 - -如果没有达到限制,请求被发送到 API 服务器。同时,系统递增计数器并将其保存回 Redis。 - -## 第三步——设计深潜 - -图 4-12 中的高层设计没有回答以下问题: - -限速规则是如何创建的?规则存储在哪里? - -如何处理速率受限的请求? - -在本节中,我们将首先回答有关速率限制规则的问题,然后讨论处理速率限制请求的策略。最后,我们将讨论分布式环境中的速率限制、详细设计、性能优化和监控。 - -### 限速规则 - -Lyft 开源了他们的限速组件[12]。我们将查看组件内部,并查看一些速率限制规则的示例: - -域 : 消息传递 - -描述符 : - ---按键-:-消息 _ 类型 - -价值 :营销 - -利率 _ 限额 : - -单位 : 日 - -请求 _ 每单位 : 5 - -在上述示例中,系统被配置为每天最多允许 5 条营销消息。下面是另一个例子: - -域名 : 认证 - -描述符 : - ---键-:-auth _ type - -值 :登录 - -利率 _ 限额 : - -单位 :分钟 - -请求 _ 每单位 : 5 - -该规则显示,客户端不允许在 1 分钟内登录 5 次以上。规则通常写在配置文件中并保存在磁盘上。 - -### 超过速率限制 - -如果请求是速率受限的,API 会向客户端返回一个 HTTP 响应代码 429(请求太多)。根据用例的不同,我们可能会将速率受限的请求排入队列,以便稍后处理。例如,如果一些订单由于系统过载而受到费率限制,我们可能会将这些订单留待以后处理。 - -#### 限速器标题 - -客户端如何知道它是否被节流?客户端如何知道在被节流之前允许的剩余请求的数量?答案就在 HTTP 响应头中。速率限制器向客户端返回以下 HTTP 报头: - -X-rate limit-Remaining:窗口内允许请求的剩余数量。 - -X-Ratelimit-Limit: 表示客户在每个时间窗口内可以拨打多少个电话。 - -X-rate limit-Retry-After:等待的秒数,直到可以再次发出请求而不被限制。 - -当用户发送过多请求时,向客户端返回 429 过多请求错误和X-rate limit-Retry-After报头。 - -### 详细设计 - -图 4-13 展示了系统的详细设计。 - -![A close up of a map Description automatically generated](../images/00047.jpeg) - -规则存储在磁盘上。工作人员经常从磁盘中提取规则,并将它们存储在缓存中。 - -当客户端向服务器发送请求时,请求首先被发送到限速器中间件。 - -限速器中间件从缓存中加载规则。它从 Redis 缓存中获取计数器和上次请求时间戳。基于该响应,速率限制器决定: - -如果请求没有速率限制,则转发给 API 服务器。 - -如果请求是速率受限的,速率限制器向客户端返回 429 过多请求错误。与此同时,请求要么被丢弃,要么被转发到队列。 - -### 分布式环境中的限速器 - -构建一个在单一服务器环境中工作的速率限制器并不困难。然而,扩展系统以支持多个服务器和并发线程是另一回事。有两个挑战: - -比赛条件 - -同步发布 - -#### 比赛状态 - -如前所述,速率限制器在高层工作如下: - -从 Redis 中读取 计数器 的值。 - -检查 ( 计数器+1)是否超过阈值。 - -如果不是,Redis 中的计数器值加 1。 - -如图 4-14 所示,竞争条件可能发生在高度并发的环境中。 - -![A picture containing screenshot Description automatically generated](../images/00048.jpeg) - -假设 Redis 中的 计数器 的值为 3。如果两个请求同时读取 计数器 的值,在它们中的任何一个写回该值之前,每个请求都将把 计数器 加 1,并写回该值,而不检查另一个线程。两个请求(线程)都认为它们拥有正确的 计数器 值 4。然而,正确的 计数器的 值应该是 5。 - -锁是解决竞争状况的最明显的解决方案。但是,锁会显著降低系统速度。有两种策略常用来解决这个问题:Lua 脚本[13]和 Redis 中的有序集数据结构[8]。对这些策略感兴趣的读者,可以参考相应的参考资料[8] [13]。 - -#### 同步发布 - -同步是分布式环境中需要考虑的另一个重要因素。为了支持数百万用户,一台限速服务器可能不足以处理流量。当使用多个速率限制器服务器时,需要同步。例如,在图 4-15 的左侧,客户端 1 向速率限制器 1 发送请求,客户端 2 向速率限制器 2 发送请求。由于 web 层是无状态的,客户端可以向不同的速率限制器发送请求,如图 4-15 右侧所示。如果没有同步发生,速率限制器 1 不包含关于客户端 2 的任何数据。因此,限速器不能正常工作。 - -![A picture containing text, map Description automatically generated](../images/00049.jpeg) - -一种可能的解决方案是使用粘性会话,允许客户端向同一个速率限制器发送流量。这种解决方案是不可取的,因为它既不可伸缩也不灵活。更好的方法是使用像 Redis 这样的集中式数据存储。设计如图 4-16 所示。 - -![A close up of a map Description automatically generated](../images/00050.jpeg) - -### 性能优化 - -性能优化是系统设计面试中的常见话题。我们将从两个方面进行改进。 - -首先,多数据中心设置对于速率限制器至关重要,因为对于远离数据中心的用户来说,延迟很高。大多数云服务提供商在世界各地建立了许多边缘服务器。例如,截至 20 20 年 5 月 20 日,Cloudflare 拥有 194 台地理位置分散的边缘服务器[14]。流量被自动路由到最近的边缘服务器,以减少延迟。 - -![A close up of a logo Description automatically generated](../images/00051.jpeg) - -第二,用最终的一致性模型同步数据。如果您不清楚最终的一致性模型,请参考“第 6 章:设计键值存储”中的“一致性”一节 - -### 监控 - -实施限速器后,收集分析数据以检查限速器是否有效非常重要。首先,我们要确保: - -限速算法有效。 - -限速规则有效。 - -例如,如果速率限制规则太严格,许多有效请求会被丢弃。在这种情况下,我们希望稍微放宽一些规则。在另一个例子中,我们注意到当流量突然增加时,如闪购,我们的限速器变得无效。在这种情况下,我们可以替换算法来支持突发流量。令牌桶很适合这里。 - -## 第四步——总结 - -在本章中,我们讨论了不同的速率限制算法及其优缺点。讨论的算法包括: - -令牌桶 - -漏桶 - -固定窗口 - -滑动窗口日志 - -推拉窗计数器 - -然后,我们讨论了系统架构、分布式环境中的速率限制器、性能优化和监控。与任何系统设计面试问题类似,如果时间允许,您还可以提及其他话题: - -硬 vs 软限速。 - -硬:请求数量不能超过阈值。 - -软:请求可以在短时间内超过阈值。 - -分级限速。在本章中,我们只讨论了应用层(HTTP:第 7 层)的速率限制。有可能在其他层应用速率限制。例如,您可以使用 Iptables [15] (IP:第 3 层)通过 IP 地址应用速率限制。注:开放系统互连模型(OSI 模型)有 7 层[16]:第 1 层:物理层,第 2 层:数据链路层,第 3 层:网络层,第 4 层:传输层,第 5 层:会话层,第 6 层:表示层,第 7 层:应用层。 - -避免被费率限制。用最佳实践设计您的客户端: - -使用客户端缓存避免频繁的 API 调用。 - -了解限制,不要在短时间内发送太多请求。 - -包含捕捉异常或错误的代码,以便您的客户端能够从容地从异常中恢复。 - -添加足够的回退时间以重试逻辑。 - -祝贺你走到这一步!现在给自己一个鼓励。干得好! - -参考资料 - -【1】限速策略与技巧:[https://cloud . Google . com/solutions/Rate-limiting-strategies-techniques](https://cloud.google.com/solutions/rate-limiting-strategies-techniques) - -【2】推特速率限制:[](https://developer.twitter.com/en/docs/basics/rate-limits) - -【3】谷歌文档使用限制:[【https://developers.google.com/docs/api/limits】](https://developers.google.com/docs/api/limits) - -[4] IBM 微服务:[https://www.ibm.com/cloud/learn/microservices](https://www.ibm.com/cloud/learn/microservices) - -[5]抑制 API 请求以提高吞吐量: - -[https://docs . AWS . Amazon . com/API gateway/latest/developer guide/API-gateway-request-throttling . htmlT3】](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-request-throttling.html) - -【6】条纹限速器:[](https://stripe.com/blog/rate-limiters) - -【7】Shopify REST Admin API 费率限制:[https://help . Shopify . com/en/API/reference/REST-Admin-API-rate-limits](https://help.shopify.com/en/api/reference/rest-admin-api-rate-limits) - -【8】Redis 排序集合更好的限速:[https://engineering . classdojo . com/blog/2015/02/06/rolling-Rate-limiter/](https://engineering.classdojo.com/blog/2015/02/06/rolling-rate-limiter/) - -【9】系统设计—速率限制器及数据建模:[https://medium . com/@ saisandeepmopuri/System-Design-Rate-limiter-and-Data-modeling-9304 b0d 18250](https://medium.com/@saisandeepmopuri/system-design-rate-limiter-and-data-modelling-9304b0d18250) - -[10]我们如何构建能够扩展到数百万个域的速率限制:[https://blog . cloud flare . com/counting-things-a-lot-of-different-things/](https://blog.cloudflare.com/counting-things-a-lot-of-different-things/) - -[11] Redis 网站:[https://redis.io/](https://redis.io/) - -【12】Lyft 限速:[](https://github.com/lyft/ratelimit) - -[13]使用速率限制器扩展您的 API:[https://gist . github . com/ptar Jan/e 38 f 45 F2 dfe 601419 ca 3 af 937 fff 574d #请求速率限制器](https://gist.github.com/ptarjan/e38f45f2dfe601419ca3af937fff574d#request-rate-limiter) - -【14】什么是边缘计算:[https://www . cloud flare . com/learning/server less/glossary/What-is-edge-computing/](https://www.cloudflare.com/learning/serverless/glossary/what-is-edge-computing/) - -【15】带 Iptables 的速率限制请求:[https://blog . programster . org/Rate-Limit-Requests-with-Iptables](https://blog.programster.org/rate-limit-requests-with-iptables) - -【16】OSI 模型:[https://en.wikipedia.org/wiki/OSI_model#Layer_architecture](https://en.wikipedia.org/wiki/OSI_model#Layer_architecture) \ No newline at end of file diff --git a/docs/sys-design-inter/04.md b/docs/sys-design-inter/04.md index 867dc08..adef454 100644 --- a/docs/sys-design-inter/04.md +++ b/docs/sys-design-inter/04.md @@ -1,4 +1,6 @@ -为了实现水平扩展,在服务器之间高效、均匀地分配请求/数据非常重要。一致散列是实现这一目标的常用技术。但首先,让我们深入研究一下这个问题。 +# 五、设计一致哈希 + +为了实现水平扩展,在服务器之间高效、均匀地分配请求/数据非常重要。一致哈希是实现这一目标的常用技术。但首先,让我们深入研究一下这个问题。 ## 老调重弹问题 @@ -6,7 +8,7 @@ server index = hash(key)% N,其中 N 是服务器池的大小。 -让我们用一个例子来说明它是如何工作的。如表 5-1 所示,我们有 4 台服务器和 8 个带散列的字符串键。 +让我们用一个例子来说明它是如何工作的。如表 5-1 所示,我们有 4 台服务器和 8 个带哈希的字符串键。 ![A screenshot of a cell phone Description automatically generated](../images/00052.jpeg) @@ -14,7 +16,7 @@ server index = hash(key)% N,其中 N 是服务器池的大小。 ![A close up of text on a white background Description automatically generated](../images/00053.jpeg) -当服务器池的大小固定且数据分布均匀时,这种方法非常有效。然而,当添加新的服务器或者移除现有的服务器时,问题 出现 。例如,如果服务器 1 脱机,服务器池的大小将变为 3。 使用相同的散列函数,我们得到一个键的相同散列值。但是应用模运算给了我们不同的服务器索引,因为服务器的数量减少了 1。 我们应用 hash % 3 : 得到如表 5-2 所示的结果 +当服务器池的大小固定且数据分布均匀时,这种方法非常有效。然而,当添加新的服务器或者移除现有的服务器时,问题 出现 。例如,如果服务器 1 脱机,服务器池的大小将变为 3。 使用相同的哈希函数,我们得到一个键的相同哈希值。但是应用模运算给了我们不同的服务器索引,因为服务器的数量减少了 1。 我们应用 hash % 3 : 得到如表 5-2 所示的结果 ![A screenshot of a cell phone Description automatically generated](../images/00054.jpeg) @@ -22,7 +24,7 @@ server index = hash(key)% N,其中 N 是服务器池的大小。 ![A screenshot of a cell phone Description automatically generated](../images/00055.jpeg) -如图 5-2 所示,大部分的键都是重新分配的,而不仅仅是原来存储在离线服务器(服务器 1)中的那些。 这意味着当服务器 1 离线时,大多数缓存客户端将连接到错误的服务器来获取数据。这导致了高速缓存未命中的风暴。一致散列是缓解这一问题的有效技术。 +如图 5-2 所示,大部分的键都是重新分配的,而不仅仅是原来存储在离线服务器(服务器 1)中的那些。 这意味着当服务器 1 离线时,大多数缓存客户端将连接到错误的服务器来获取数据。这导致了高速缓存未命中的风暴。一致哈希是缓解这一问题的有效技术。 ## 一致哈希 @@ -30,7 +32,7 @@ server index = hash(key)% N,其中 N 是服务器池的大小。 ### 哈希空间和哈希环 -现在我们了解了一致性哈希的定义,让我们看看它是如何工作的。假设 SHA-1 作为哈希函数 f, 哈希函数的输出 范围为 : x0,x1,x2,x3,…,xn 。在密码学中,SHA-1 的散列空间从 0 到 2^160 - 1。这意味着 x0 对应于 0, xn 对应于 2^160 - 1,中间的所有其他哈希值落在 0 和 2^160-1 之间。图 5-3 显示了散列空间。 +现在我们了解了一致性哈希的定义,让我们看看它是如何工作的。假设 SHA-1 作为哈希函数 f, 哈希函数的输出 范围为 : x0,x1,x2,x3,…,xn 。在密码学中,SHA-1 的哈希空间从 0 到 2^160 - 1。这意味着 x0 对应于 0, xn 对应于 2^160 - 1,中间的所有其他哈希值落在 0 和 2^160-1 之间。图 5-3 显示了哈希空间。 ![A close up of a logo Description automatically generated](../images/00056.jpeg) @@ -40,13 +42,13 @@ server index = hash(key)% N,其中 N 是服务器池的大小。 ### 哈希服务器 -使用相同的哈希函数 f ,我们基于服务器 IP 或名称将服务器映射到环上。图 5-5 显示了 4 个服务器被映射到散列环上。 +使用相同的哈希函数 f ,我们基于服务器 IP 或名称将服务器映射到环上。图 5-5 显示了 4 个服务器被映射到哈希环上。 ![A necklace on a table Description automatically generated](../images/00058.jpeg) ### 哈希键 -值得一提的是,这里使用的 hash 函数与“重散列问题”中的不同,没有模运算。如图 5-6 所示,4 个缓存键(key0、key1、key2 和 key3)被散列到散列环上 +值得一提的是,这里使用的 hash 函数与“重哈希问题”中的不同,没有模运算。如图 5-6 所示,4 个缓存键(key0、key1、key2 和 key3)被哈希到哈希环上 ![A close up of a necklace Description automatically generated](../images/00059.jpeg) @@ -66,19 +68,19 @@ server index = hash(key)% N,其中 N 是服务器池的大小。 ### 移除一个服务器 -当服务器被移除时,只有一小部分密钥需要用一致的散列法重新分配。在图 5-9 中,当 服务器 1 被移除后,只有 key1 必须重新映射到 服务器 2 。其余的键不受影响。 +当服务器被移除时,只有一小部分密钥需要用一致的哈希法重新分配。在图 5-9 中,当 服务器 1 被移除后,只有 key1 必须重新映射到 服务器 2 。其余的键不受影响。 ![A picture containing necklace Description automatically generated](../images/00062.jpeg) ### 两个问题的基本处理方法 -一致性散列算法是由 Karger 等人在麻省理工学院提出的[1]。基本步骤是: +一致性哈希算法是由 Karger 等人在麻省理工学院提出的[1]。基本步骤是: -使用均匀分布的散列函数将服务器和密钥映射到环上。 +使用均匀分布的哈希函数将服务器和密钥映射到环上。 要找出一个密钥映射到哪个服务器,从密钥位置开始顺时针方向走,直到找到环上的第一个服务器。 -这种方法发现了两个问题。首先,考虑到可以添加或移除服务器,不可能为环上的所有服务器保持相同大小的分区。分区是相邻服务器之间的散列空间。分配给每个服务器的环上的分区的大小可能非常小或相当大。在图 5-10 中,如果去掉 s1 , s2 的 分区(用双向箭头突出显示)是 s0 和 s3 的 分区的两倍。 +这种方法发现了两个问题。首先,考虑到可以添加或移除服务器,不可能为环上的所有服务器保持相同大小的分区。分区是相邻服务器之间的哈希空间。分配给每个服务器的环上的分区的大小可能非常小或相当大。在图 5-10 中,如果去掉 s1 , s2 的 分区(用双向箭头突出显示)是 s0 和 s3 的 分区的两倍。 ![A close up of a necklace Description automatically generated](../images/00063.jpeg) @@ -114,13 +116,13 @@ server index = hash(key)% N,其中 N 是服务器池的大小。 ## 总结起来 -在本章中,我们深入讨论了一致性哈希,包括为什么需要它以及它是如何工作的。一致散列的好处包括: +在本章中,我们深入讨论了一致性哈希,包括为什么需要它以及它是如何工作的。一致哈希的好处包括: 添加或删除服务器时,最小化的密钥会重新分配。 因为数据分布更均匀,所以水平缩放更容易。 -缓解热点关键问题。对特定碎片的过度访问可能会导致服务器过载。想象一下凯蒂·佩里、贾斯汀比伯和 Lady Gaga 的数据都在同一个碎片上。一致散列通过更均匀地分布数据来帮助缓解这个问题。 +缓解热点关键问题。对特定碎片的过度访问可能会导致服务器过载。想象一下凯蒂·佩里、贾斯汀比伯和 Lady Gaga 的数据都在同一个碎片上。一致哈希通过更均匀地分布数据来帮助缓解这个问题。 一致性哈希在现实世界的系统中被广泛使用,包括一些著名的: diff --git a/docs/sys-design-inter/05.md b/docs/sys-design-inter/05.md index c11ae59..5476a4f 100644 --- a/docs/sys-design-inter/05.md +++ b/docs/sys-design-inter/05.md @@ -1,6 +1,8 @@ +# 六、设计键值存储 + 键值存储,也称为键值数据库,是一种非关系数据库。每个唯一标识符都存储为一个键及其相关值。这种数据配对被称为“键-值”对。 -在键-值对中,键必须是唯一的,与键相关联的值可以通过键来访问。键可以是纯文本或散列值。出于性能原因,短键效果更好。钥匙长什么样?下面举几个例子: +在键-值对中,键必须是唯一的,与键相关联的值可以通过键来访问。键可以是纯文本或哈希值。出于性能原因,短键效果更好。钥匙长什么样?下面举几个例子: 纯文本密钥:“上次登录时间” @@ -122,11 +124,11 @@ CA(一致性和可用性)系统:CA 键值存储支持一致性和可用性,同 添加或删除节点时,尽量减少数据移动。 -第五章中讨论的一致散列法是解决这些问题的一个很好的技术。让我们从高层次上重新审视一下一致性散列是如何工作的。 +第五章中讨论的一致哈希法是解决这些问题的一个很好的技术。让我们从高层次上重新审视一下一致性哈希是如何工作的。 首先,服务器被放置在一个哈希环上。在图 6-4 中,八个服务器,分别用 s0,s1,…,s7 表示,放置在哈希环上。 -接下来,一个密钥被散列到同一个环上,并存储在顺时针方向移动时遇到的第一个服务器上。例如, key0 使用此逻辑存储在 s1 中。 +接下来,一个密钥被哈希到同一个环上,并存储在顺时针方向移动时遇到的第一个服务器上。例如, key0 使用此逻辑存储在 s1 中。 ![A necklace with a piece of paper Description automatically generated](../images/00073.jpeg) @@ -282,7 +284,7 @@ If W + R > N 强一致性得到保证,因为必须至少有一个重叠节点 提示切换用于处理临时故障。如果副本永久不可用怎么办?为了处理这种情况,我们实现了一个反熵协议来保持副本同步。反熵包括比较副本上的每一条数据,并将每个副本更新到最新版本。Merkle 树用于不一致性检测和最小化传输的数据量。 -引自维基百科[7]:“hash 树或 Merkle 树是 中的树,其中每个非叶节点都用其子节点的标签或值(在叶的情况下)的散列来标记。散列树允许高效和安全地验证大型数据结构的内容”。 +引自维基百科[7]:“hash 树或 Merkle 树是 中的树,其中每个非叶节点都用其子节点的标签或值(在叶的情况下)的哈希来标记。哈希树允许高效和安全地验证大型数据结构的内容”。 假设密钥空间从 1 到 12,下面的步骤展示了如何构建 Merkle 树。突出显示的方框表示不一致。 @@ -290,7 +292,7 @@ If W + R > N 强一致性得到保证,因为必须至少有一个重叠节点 ![A close up of a keyboard Description automatically generated](../images/00082.jpeg) -步骤 2:一旦创建了存储桶,使用统一的散列方法对存储桶中的每个关键字进行散列(图 6-14)。 +步骤 2:一旦创建了存储桶,使用统一的哈希方法对存储桶中的每个关键字进行哈希(图 6-14)。 ![A screenshot of a cell phone Description automatically generated](../images/00083.jpeg) @@ -322,7 +324,7 @@ If W + R > N 强一致性得到保证,因为必须至少有一个重叠节点 协调器是充当客户端和键值存储之间的代理的节点。 -使用一致散列法将节点分布在环上。 +使用一致哈希法将节点分布在环上。 系统是完全分散的,因此添加和移动节点可以是自动的。 diff --git a/docs/sys-design-inter/06.md b/docs/sys-design-inter/06.md index 14069e2..9d5bfdc 100644 --- a/docs/sys-design-inter/06.md +++ b/docs/sys-design-inter/06.md @@ -1,3 +1,5 @@ +# 七、设计分布式系统中的唯一 ID 生成器 + 在这一章中,你被要求在分布式系统中设计一个唯一的 ID 生成器。您首先想到的可能是在传统数据库中使用带有 auto_increment 属性的主键。然而, auto_increment 在分布式环境中不起作用,因为单个数据库服务器不够大,并且在最小延迟的情况下跨多个数据库生成唯一的 id 具有挑战性。 这里有几个唯一 id 的例子: diff --git a/docs/sys-design-inter/07.md b/docs/sys-design-inter/07.md index eb66765..49ff39e 100644 --- a/docs/sys-design-inter/07.md +++ b/docs/sys-design-inter/07.md @@ -1,3 +1,5 @@ +# 八、设计 URL 缩短器 + 在这一章中,我们将解决一个有趣而经典的系统设计面试问题:设计一个像 tinyurl 这样的 URL 缩短服务。 ## 第一步——了解问题并确定设计范围 @@ -106,11 +108,11 @@ API 端点促进了客户端和服务器之间的通信。我们将设计 REST 每个 的哈希值 都可以映射回 longURL 。 -深入讨论散列函数的详细设计。 +深入讨论哈希函数的详细设计。 ## 步骤 3 -设计深度潜水 -到目前为止,我们已经讨论了 URL 缩短和 URL 重定向的高级设计 - 。在本节中,我们将深入探讨以下内容:数据模型、散列函数、URL 缩短和 URL 重定向。 +到目前为止,我们已经讨论了 URL 缩短和 URL 重定向的高级设计 - 。在本节中,我们将深入探讨以下内容:数据模型、哈希函数、URL 缩短和 URL 重定向。 ### 数据模型 @@ -120,7 +122,7 @@ API 端点促进了客户端和服务器之间的通信。我们将设计 REST ### 哈希函数 -Hash 函数用于将一个长 URL 散列成一个短 URL,也称为 hashValue 。 +Hash 函数用于将一个长 URL 哈希成一个短 URL,也称为 hashValue 。 #### 哈希值长度 @@ -130,11 +132,11 @@ Hash 函数用于将一个长 URL 散列成一个短 URL,也称为 hashValue 当 n = 7,62 ^ n = ~3.5 万亿 时,3.5 万亿容纳 3650 亿个 URL 绰绰有余,所以 的长度 hashValue 为 7。 -我们将探讨两种类型的 URL 缩写散列函数。第一个是“哈希+冲突解决”,第二个是“base 62 转换”。让我们一个一个来看。 +我们将探讨两种类型的 URL 缩写哈希函数。第一个是“哈希+冲突解决”,第二个是“base 62 转换”。让我们一个一个来看。 #### 哈希+冲突解决 -为了缩短一个长 URL,我们应该实现一个散列函数,将一个长 URL 散列成一个 7 个字符的字符串。一个简单的解决方案是使用众所周知的散列函数,如 CRC32、MD5 或 SHA-1。下表比较了对该 URL 应用不同哈希函数后的哈希结果:https://en.wikipedia.org/wiki/Systems_design. +为了缩短一个长 URL,我们应该实现一个哈希函数,将一个长 URL 哈希成一个 7 个字符的字符串。一个简单的解决方案是使用众所周知的哈希函数,如 CRC32、MD5 或 SHA-1。下表比较了对该 URL 应用不同哈希函数后的哈希结果:https://en.wikipedia.org/wiki/Systems_design. ![A screenshot of a cell phone Description automatically generated](../images/00104.jpeg) @@ -216,7 +218,7 @@ URL 重定向的流程总结如下: ## 步骤 4 -总结 -在本章中,我们讨论了 API 设计、数据模型、散列函数、URL 缩短和 URL 重定向。 +在本章中,我们讨论了 API 设计、数据模型、哈希函数、URL 缩短和 URL 重定向。 如果在面试结束时有额外的时间,这里有一些额外的谈话要点。 diff --git a/docs/sys-design-inter/08.md b/docs/sys-design-inter/08.md index 995fee7..f222a34 100644 --- a/docs/sys-design-inter/08.md +++ b/docs/sys-design-inter/08.md @@ -1,3 +1,5 @@ +# 九、设计网络爬虫 + 在这一章中,我们关注网络爬虫设计:一个有趣而经典的系统设计面试问题。 网络爬虫被称为机器人或蜘蛛。搜索引擎广泛使用它来发现 web 上新的或更新的内容。内容可以是网页、图像、视频、PDF 文件等。网络爬虫从收集一些网页开始,然后跟随这些网页上的链接来收集新的内容。图 9-1 显示了爬行过程的一个可视化例子。 @@ -316,7 +318,7 @@ DNS 解析器是爬虫的瓶颈,因为由于许多 DNS 接口的同步性质 除了性能优化,健壮性也是一个重要的考虑因素。我们提出了几种提高系统鲁棒性的方法: -一致散列:这有助于在下载者之间分配负载。可以使用一致散列来添加或删除新的下载器服务器。更多细节请参考第 5 章:设计一致散列。 +一致哈希:这有助于在下载者之间分配负载。可以使用一致哈希来添加或删除新的下载器服务器。更多细节请参考第 5 章:设计一致哈希。 保存抓取状态和数据:为了防止失败,抓取状态和数据被写入存储系统。通过加载保存的状态和数据,可以轻松重启中断的爬网。 @@ -400,7 +402,7 @@ DNS 解析器是爬虫的瓶颈,因为由于许多 DNS 接口的同步性质 1998 年。 -【11】伯顿·布鲁姆。具有容许误差的散列编码中的空间/时间权衡。美国计算机学会通讯,第 13 卷第 7 期,第 422 - 426 页,1970 年 7 月。 +【11】伯顿·布鲁姆。具有容许误差的哈希编码中的空间/时间权衡。美国计算机学会通讯,第 13 卷第 7 期,第 422 - 426 页,1970 年 7 月。 【12】谷歌动态渲染:[https://developers . Google . com/search/docs/guides/Dynamic-Rendering](https://developers.google.com/search/docs/guides/dynamic-rendering) diff --git a/docs/sys-design-inter/09.md b/docs/sys-design-inter/09.md index c21f61b..bfc3509 100644 --- a/docs/sys-design-inter/09.md +++ b/docs/sys-design-inter/09.md @@ -1,3 +1,5 @@ +# 十、设计通知系统 + 近年来,通知系统已经成为许多应用程序中非常流行的功能。通知提醒用户重要信息,如突发新闻、产品更新、事件、产品等。它已经成为我们日常生活中不可或缺的一部分。在这一章中,你被要求设计一个通知系统。 通知不仅仅是移动推送通知。三种通知格式是:移动推送通知、SMS 消息和电子邮件。图 10-1 显示了这些通知的一个例子。 diff --git a/docs/sys-design-inter/10.md b/docs/sys-design-inter/10.md index 2f0fb25..f3ff2f7 100644 --- a/docs/sys-design-inter/10.md +++ b/docs/sys-design-inter/10.md @@ -1,3 +1,5 @@ +# 十一、设计新闻订阅系统 + 在这一章中,你被要求设计一个新闻订阅系统。什么是新闻订阅源?根据脸书的帮助页面,“新闻提要是在你的主页中间不断更新的故事列表。新闻源包括状态更新、照片、视频、链接、应用活动 、 ,以及你在脸书上关注的人、页面和群组的赞。这是一个常见的面试问题。常见的类似问题有:设计脸书新闻提要、Instagram 提要、Twitter 时间线等。 ![A screenshot of a cell phone Description automatically generated](../images/00138.jpeg) @@ -146,7 +148,7 @@ Web 服务器:Web 服务器将请求路由到新闻订阅服务。 获取新闻提要很慢,因为新闻提要不是预先计算的。 -我们采用了一种混合方法,以获得两种方法的优点并避免其中的缺陷。因为快速获取新闻提要至关重要,所以我们对大多数用户使用推送模型。对于有很多朋友/关注者的名人或用户,我们让关注者按需拉新闻内容,以避免系统过载。一致散列是一种缓解热键问题的有用技术,因为它有助于更均匀地分布请求/数据。 +我们采用了一种混合方法,以获得两种方法的优点并避免其中的缺陷。因为快速获取新闻提要至关重要,所以我们对大多数用户使用推送模型。对于有很多朋友/关注者的名人或用户,我们让关注者按需拉新闻内容,以避免系统过载。一致哈希是一种缓解热键问题的有用技术,因为它有助于更均匀地分布请求/数据。 让我们仔细看看扇出服务,如图 11-5 所示。 diff --git a/docs/sys-design-inter/11.md b/docs/sys-design-inter/11.md index e2fa31d..7c09879 100644 --- a/docs/sys-design-inter/11.md +++ b/docs/sys-design-inter/11.md @@ -1,3 +1,5 @@ +# 十二、设计聊天系统 + 在这一章中,我们将探索一个聊天系统的设计。几乎每个人都使用聊天应用。图 12-1 显示了市场上一些最流行的应用程序。 ![A picture containing flower Description automatically generated](../images/00146.jpeg) diff --git a/docs/sys-design-inter/12.md b/docs/sys-design-inter/12.md index cdb2166..e188cfc 100644 --- a/docs/sys-design-inter/12.md +++ b/docs/sys-design-inter/12.md @@ -1,3 +1,5 @@ +# 十三、设计搜索自动补全系统 + 在谷歌上搜索或在亚马逊购物时,当你在搜索框中输入时,一个或多个匹配的搜索词就会呈现在你面前。此功能被称为自动完成、提前键入、随键入搜索或增量搜索。图 13-1 展示了一个谷歌搜索的例子,当在搜索框中输入“晚餐”时,显示了一个自动完成的结果列表。搜索自动完成是许多产品的重要功能。这就引出了面试问题:设计一个搜索自动完成系统,也叫“设计 top k”或“设计 top k 最常搜索的查询”。 ![A screenshot of a cell phone Description automatically generated](../images/00165.jpeg) @@ -256,7 +258,7 @@ Trie 缓存。 Trie Cache 是一个分布式缓存系统,将 Trie 保存在内 1。文档存储:由于每周都会构建一个新的 trie,所以我们可以定期对其进行快照、序列化,并将序列化后的数据存储在数据库中。像 MongoDB [4]这样的文档存储非常适合序列化数据。 -2。键值存储:通过应用以下逻辑,可以用散列表的形式[4]来表示 trie: +2。键值存储:通过应用以下逻辑,可以用哈希表的形式[4]来表示 trie: trie 中的每个前缀都映射到哈希表中的一个键。 diff --git a/docs/sys-design-inter/13.md b/docs/sys-design-inter/13.md index 0b14cdd..f69200a 100644 --- a/docs/sys-design-inter/13.md +++ b/docs/sys-design-inter/13.md @@ -1,3 +1,5 @@ +# 十四、设计 YouTube + 在这一章中,你被要求设计 YouTube。这个问题的解决方案可以应用于其他面试问题,比如设计一个视频分享平台,如网飞和 Hulu。图 14-1 显示了 YouTube 主页。 ![A screenshot of a social media post Description automatically generated](../images/00184.jpeg) diff --git a/docs/sys-design-inter/14.md b/docs/sys-design-inter/14.md index 87f05dd..a8901c6 100644 --- a/docs/sys-design-inter/14.md +++ b/docs/sys-design-inter/14.md @@ -1,3 +1,5 @@ +# 十五、设计谷歌云盘 + 近年来,Google Drive、Dropbox、微软 OneDrive、苹果 iCloud 等云存储服务变得非常流行。在这一章中,你被要求设计 Google Drive。 在开始设计之前,让我们花点时间了解一下 Google Drive。Google Drive 是一种文件存储和同步服务,可以帮助您在云端存储文档、照片、视频和其他文件。您可以从任何电脑、智能手机和平板电脑上访问您的文件。您可以轻松地与朋友、家人和同事分享这些文件[1]。图 15-1 和 15-2 分别显示了 Google drive 在浏览器和移动应用上的样子。 diff --git a/docs/sys-design-inter/15.md b/docs/sys-design-inter/15.md index e01c3bf..fb45e87 100644 --- a/docs/sys-design-inter/15.md +++ b/docs/sys-design-inter/15.md @@ -1,3 +1,5 @@ +# 十六、继续学习 + 设计好的系统需要多年的知识积累。一个捷径是深入现实世界的系统架构。以下是一些有用的阅读材料。我们强烈建议您关注共享原则和底层技术。研究每项技术并了解它能解决什么问题,这是巩固您的知识基础和优化设计流程的一个好方法。 ## 现实世界的系统 diff --git a/docs/sys-design-inter/16.md b/docs/sys-design-inter/16.md index 16a1c00..6a944a3 100644 --- a/docs/sys-design-inter/16.md +++ b/docs/sys-design-inter/16.md @@ -1,3 +1,5 @@ +# 十七、后记 + 恭喜你!您已到达本面试指南的末尾。你已经积累了设计系统的技能和知识。不是每个人都有学习你所学的东西的纪律。花点时间拍拍自己的背。你的努力会有回报的。 找到一份理想的工作是一个漫长的旅程,需要大量的时间和努力。熟能生巧。祝你好运! -- GitLab