系统设计面试问题–您应该知道的概念(转载)
您可能已经听说过“架构”或“系统设计”这两个术语。在开发人员工作面试中,尤其是在大型科技公司中,这些问题很多。
通过指导您基本的软件体系结构概念,本深入指南将帮助您为系统设计面试做准备。
这不是详尽的方法,因为系统设计是一个广泛的主题。但是,如果您是初级或中级开发人员,这应该为您奠定坚实的基础。
从那里,您可以深入挖掘其他资源。我在本文的底部列出了一些我最喜欢的资源。
我已按主题将本指南分成几小部分,因此建议您将其添加为书签。我发现间隔学习和重复学习是学习和保留信息的极有价值的工具。而且我已将本指南设计为易于分解的片段。
- 第1节:网络和协议(IP,DNS,HTTP,TCP等)
- 第2节:存储,延迟和吞吐量
- 第3节:可用性
- 第4节:缓存
- 第5节:代理
- 第6节:负载平衡
- 第7节:一致的哈希
- 第8节:数据库
- 第9节:领导人选举
- 第10节:轮询,流式传输,套接字
- 第11节:端点保护
- 第12节:消息和发布订阅
- 第13节:较小的必需品
让我们开始吧!
第1节:网络和协议
“协议”是一个花哨的单词,在英语中的含义完全独立于计算机科学。这意味着可以管理某些事物的规章制度。一种“官方程序”或“必须完成某些事情的官方方式”。
为了使人们连接到彼此通信的机器和代码,他们需要一个可以在其上进行通信的网络。但是沟通也需要一些规则,结构和商定的程序。
因此,网络协议是控制机器和软件如何通过给定网络进行通信的协议。网络的一个例子是我们钟爱的万维网。
您可能听说过互联网时代最常见的网络协议-例如HTTP,TCP / IP等。让我们将它们分解为基本概念。
IP-互联网协议
将此视为协议的基本层。这是基本协议,可指导我们如何实现几乎所有跨Internet网络的通信。
IP上的消息通常以“数据包”形式进行通信,这些信息包是一小束信息(2 ^ 16字节)。每个数据包都有一个由两个部分组成的基本结构:标头和数据。
标头包含有关数据包及其数据的“元”数据。该元数据包括诸如源(数据包来自何处)的IP地址和目标IP地址(数据包的目的地)之类的信息。显然,这是能够将信息从一个点发送到另一点的基础-您需要“发件人”和“收件人”地址。
和一个IP地址是分配给连接到每个设备的数值标签计算机网络使用因特网协议进行通信。有公用和专用IP地址,当前有两个版本。新版本称为IPv6,由于IPv4的数字地址用完了,因此越来越多地被采用。
我们将在本文中考虑的其他协议是基于IP构建的,就像您最喜欢的软件语言在其之上构建的库和框架一样。
TCP-传输控制协议
TCP是建立在IP之上的实用程序。正如您可以从阅读我的文章知道,我坚信你需要理解为什么东西才能被发明真正了解什么它。
创建TCP是为了解决IP问题。IP上的数据通常以多个数据包发送,因为每个数据包都非常小(2 ^ 16字节)。多个数据包可能导致(A)丢失或丢失的数据包,以及(B)无序的数据包,从而损坏传输的数据。TCP通过保证以有序方式传输数据包来解决这两种情况。
数据包基于IP构建,除了IP报头之外,还具有称为TCP报头的报头。此TCP头包含有关数据包顺序和数据包数量等信息。这样可以确保在另一端可靠地接收数据。由于它建立在IP之上,因此通常称为TCP / IP。
TCP需要先在源和目标之间建立连接,然后再通过“握手”进行连接。此连接本身是使用数据包建立的,其中源将其通知目的地它想打开连接,并且目的地说“确定”,然后打开连接。
实际上,这就是服务器在端口“侦听”时发生的情况-在它开始侦听之前,先握手,然后打开连接(侦听开始)。类似地,一个向另一个发送一条消息,即将关闭连接,并结束连接。
HTTP-超文本传输协议
HTTP是一种协议,是基于TCP / IP构建的抽象。它引入了一个非常重要的模式,称为请求-响应模式,专门用于客户端-服务器交互。
客户端只是请求信息的机器或系统,而服务器是响应信息的机器或系统。浏览器是客户端,而网络服务器是服务器。当服务器从另一台服务器请求数据时,第一台服务器也是客户端,第二台服务器是服务器(我知道是重言式)。
因此,此请求-响应周期在HTTP下具有其自己的规则,这标准化了信息在Internet上的传输方式。
在这种抽象级别上,我们通常不需要太担心IP和TCP。但是,在HTTP中,请求和响应也具有标头和正文,并且标头和正文包含开发人员可以设置的数据。
HTTP请求和响应可以被视为具有键-值对的消息,与JavaScript中的对象和Python中的字典非常相似,但不相同。
下图说明了HTTP请求和响应消息中的内容和键值对。
来源:https : //developer.mozilla.org/en-US/docs/Web/HTTP/Messages
HTTP还带有一些“动词”或“方法”,这些命令是使您了解打算执行哪种操作的命令。例如,常见的HTTP方法是“ GET”,“ POST”,“ PUT”,“ DELETE”和“ PATCH”,但还有更多方法。在上图中,在起始行中查找HTTP动词。
第2节:存储,延迟和吞吐量
存储
存储是关于保存信息的。您编程的任何应用程序,系统或服务都将需要存储和检索数据,而这是存储的两个基本目的。
但这不只是存储数据,还在于获取数据。我们使用数据库来实现这一目标。数据库是帮助我们存储和检索数据的软件层。
这两种主要的操作类型,即存储和检索,也分别称为“设置,获取”,“存储,获取”,“写入,读取”等。要与存储进行交互,您将需要遍历数据库,该数据库充当您执行这些基本操作的中介。
“存储”一词有时会使我们从物理角度上去思考它。如果我将自行车“存放”在棚子里,那么我下次打开棚子时可以期望它在那里。
但这并不总是在计算世界中发生。存储大致可以分为两种类型:“内存”存储和“磁盘”存储。
在这两个磁盘中,磁盘存储趋向于更健壮和“永久”(不是真正的永久,因此我们经常使用“永久”存储一词)。磁盘存储是持久性存储。这意味着,当您将某些内容保存到磁盘,然后关闭电源或重新启动服务器时,该数据将“持续存在”。它不会丢失。
但是,如果将数据保留在“内存”中,那么通常在关闭或重新启动时这些数据会消失,否则会断电。
您每天使用的计算机都具有这两种存储类型。您的硬盘是
“永久性”磁盘存储,而您的RAM是暂时性的内存存储。
在服务器上,如果您跟踪的数据仅在该服务器的会话期间有用,则将其保留在内存中是有意义的。与将内容写入持久数据库相比,这要快得多且成本更低。
例如,单个会话可能意味着用户登录并使用您的网站。他们注销后,您可能不需要保留在会话期间收集的部分数据。
但是,无论您想保留什么(例如购物车历史记录),您都将放入永久性磁盘存储中。这样,您可以在用户下次登录时访问该数据,他们将获得无缝的体验。
好的,这看起来非常简单和基本,而且确实如此。这是入门。存储会变得非常复杂。如果您看一下存储产品和解决方案的范围,您的头会旋转。
这是因为不同的用例需要不同的存储类型。它们为系统选择正确的存储类型的关键取决于许多因素,应用程序的需求以及用户如何与之交互。其他因素包括:
- 数据的形状(结构),或
- 它需要什么样的可用性(哪种水平的停机时间适合您的存储),或者
- 可伸缩性(您需要多快读写数据,这些读写将同时(同时)或顺序进行)等,或
- 一致性-如果您使用分布式存储来防止停机,那么整个存储中的数据的一致性如何?
这些问题和结论要求您仔细考虑您的取舍。一致性比速度重要吗?您需要该数据库每分钟执行数百万次操作还是仅用于每晚更新?稍后我将在各节中处理这些概念,所以如果您不知道它们是什么,请不要担心。
潜伏
当您开始在设计系统以支持应用程序的前端方面获得更多经验时,您会经常听到“延迟”和“吞吐量”这两个术语。它们对于您的应用程序和整个系统的体验和性能至关重要。通常倾向于以比预期更广泛的含义使用这些术语,或者超出上下文使用,但让我们对其进行修复。
延迟只是持续时间的度量。什么时间 完成某件事或产生结果的动作的持续时间。例如:数据从系统中的一个位置移动到另一个位置。您可能会认为这是一个滞后,或者仅仅是完成操作所花费的时间。
最常见的延迟是“往返”网络请求-前端网站(客户端)向服务器发送查询并从服务器返回响应需要多长时间。
加载网站时,您希望它尽可能快和流畅。换句话说,你需要低时延。快速查找意味着低延迟。因此,在元素数组中查找值比在哈希表中查找值要慢(延迟较高,因为您需要遍历数组中的每个元素以找到所需的元素)(延迟较低,因为您只需看一下即可)通过使用密钥在“固定”时间内更新数据(无需迭代)。
同样,从内存中读取要比从磁盘中读取快得多(在此处了解更多)。但是两者都有延迟,您的需求将决定您选择哪种数据的存储类型。
从这个意义上讲,延迟是速度的倒数。您想要更高的速度,并且想要更低的延迟。速度(尤其是通过HTTP进行的网络呼叫)的速度也取决于距离。因此,从伦敦到另一个城市的延迟时间将受到与伦敦的距离的影响。
可以想象,您希望设计一个系统来避免对远程服务器执行ping操作,但是将内容存储在内存中对于您的系统可能不可行。这些折衷使系统设计变得复杂,具有挑战性并且非常有趣!
例如,显示新闻报道的网站可能更喜欢正常运行时间和可用性,而不是加载速度,而在线多人游戏可能需要可用性和超低延迟。这些要求将确定支持系统特殊要求的基础结构的设计和投资。
通量
这可以理解为机器或系统的最大容量。它通常在工厂中用于计算装配线在一小时或一天之内可以完成的工作量,或其他时间单位。
例如,一条装配线每小时可以装配20辆汽车,这就是它的吞吐量。在计算中,它是单位时间内可以传递的数据量。因此512 Mbps互联网连接是吞吐量的度量-每秒512 Mb(兆位)。
现在想象一下freeCodeCamp的Web服务器。如果它每秒接收一百万个请求,并且只能处理800,000个请求,则其吞吐量为每秒80万。您可能最终以比特而不是请求来衡量吞吐量,因此它将是每秒N位。
在此示例中,存在瓶颈,因为服务器每秒处理的位数不能超过N个,但是请求的位数不止于此。因此,瓶颈是对系统的约束。一个系统的速度只有最慢的瓶颈。
如果一台服务器每秒可以处理100位,另一台服务器每秒可以处理120位,而第三台服务器只能处理50位,那么整个系统将以50bps的速度运行,因为这是一个限制-它可以保持其他服务器的速度在给定的系统中。
因此,增加瓶颈以外的任何地方的吞吐量可能都是浪费–您可能只想首先在最低瓶颈处增加吞吐量。
您可以通过购买更多硬件(水平缩放)或增加现有硬件的容量和性能(垂直缩放)或其他几种方式来提高吞吐量。
有时,增加吞吐量可能是一个短期解决方案,因此,优秀的系统设计人员将考虑通过最佳方法来扩展给定系统的吞吐量,包括通过拆分请求(或任何其他形式的“负载”)并在各个请求之间进行分配要记住的关键点是什么是吞吐量,什么是约束或瓶颈以及它如何影响系统。
固定延迟和吞吐量不是孤立的,通用的解决方案,也不是相互关联的。它们对整个系统都有影响和考虑因素,因此了解整个系统以及随时间推移对系统提出的需求的性质非常重要。
第三节:系统可用性
软件工程师旨在构建可靠的系统。可靠的系统是一个始终满足用户需求的系统,只要该用户想要满足该需求。这种可靠性的关键组成部分是可用性。
将可用性视为系统的弹性很有帮助。如果系统足够健壮,可以处理网络,数据库,服务器等中的故障,则通常可以将其视为容错系统-这使其成为可用系统。
当然,从多种意义上讲,系统是其各个部分的总和,如果可用性与站点或应用程序的最终用户体验有关,则每个部分都必须具有高可用性。
量化可用性
为了量化系统的可用性,我们计算在给定的时间范围内系统主要功能和操作可用的时间百分比(正常运行时间)。
最关键业务系统将需要具有近乎完美的可用性。在高峰期,支持高变化需求和尖峰和低谷负载的系统可能会以较低的可用性摆脱困境。
这完全取决于系统的用途和性质。但是总的来说,即使是那些需求较低但始终如一或隐含保证系统“按需”的事物,也需要具有高可用性。
想想一个备份图片的网站。您并非总是需要从中访问和检索数据-主要是用于存储内容。您仍然希望每次登录下载任何图片时,它始终可用。
在黑色星期五或网络星期一销售这样的大型电子商务购物日中,可以理解另一种类型的可用性。在这些特定日子里,需求将激增,数以百万计的人将尝试同时访问交易。这将需要极其可靠和高可用性的系统设计来支持这些负载。
高可用性的商业原因只是因为站点上的任何停机都会导致站点亏损。此外,这可能对声誉确实不利,例如,该服务是其他企业用来提供服务的服务。如果AWS S3出现故障,包括Netflix在内的许多公司将遭受损失,这不是很好。
因此,正常运行时间对于成功至关重要。值得记住的是,商业可用性数字是根据年度可用性计算得出的,因此每年0.17%(即99.9%的可用性)的停机时间为每年8.77个小时!
因此,正常运行时间非常高。通常会看到99.99%的正常运行时间(每年52.6分钟的停机时间)。这就是为什么现在通常用“ nines”(正常运行时间保证中的九位数)来指称正常运行时间。
在当今世界上,大规模或关键任务服务是不可接受的。这就是为什么现在“五个九”被认为是理想的可用性标准的原因,因为这相当于每年停机时间超过5分钟。
服务水平协议
为了使在线服务具有竞争力并满足市场期望,在线服务提供商通常会提供服务级别协议/保证。这些是一组保证的服务级别指标。99.999%的正常运行时间就是这样一种指标,通常作为高级订阅的一部分提供。
对于数据库和云服务提供商,如果客户对该产品的核心使用证明了该指标的合理性,则即使在试用或免费套餐上也可以提供此服务。
在许多情况下,如果未能满足SLA,则客户将获得信用证或其他形式的补偿,以弥补提供商未达到该保证的权利。举例来说,这里是Google针对Maps API的SLA。
因此,在设计系统时,SLA是整个商业和技术考虑因素的关键部分。特别重要的是要考虑可用性是否实际上是系统某个部分的关键要求,以及哪些部分需要高可用性。
设计HA
因此,在设计高可用性(HA)系统时,您需要减少或消除“单点故障”。单点故障是系统中的一个元素,是唯一可能导致不希望的可用性损失的元素。
您可以通过在系统中设计“冗余”来消除单点故障。冗余基本上是对对高可用性至关重要的元素进行1个或多个选择(即备份)。
因此,如果您的应用需要用户进行身份验证才能使用它,并且只有一个身份验证服务和后端,并且该服务失败了,那么,由于那是单点故障,因此您的系统不再可用。通过拥有两个或多个可以处理身份验证的服务,您增加了冗余并消除了(或减少了)单点故障。
因此,您需要了解系统并将其分解为所有部分。找出哪些可能导致单点故障,哪些不能容忍这种故障,哪些部分可以容忍它们。由于工程HA需要权衡,并且其中一些权衡在时间,金钱和资源方面可能是昂贵的。
第4节:缓存
正在缓存!这是提高系统性能的非常基础且易于理解的技术。因此,缓存有助于减少系统中的“等待时间”。
在我们的日常生活中,我们将缓存视为常识(大部分时间…)。如果我们住在超市的隔壁,我们仍然想购买一些基本物品并将其存放在冰箱和食物柜中。这是缓存。每当我们想要食物时,我们总是可以走出去,去隔壁并购买这些东西–但是,如果它在食品室或冰箱中,我们会减少制作食物的时间。那是缓存。
常见的缓存方案
同样,就软件而言,如果我们最终经常依赖某些数据,则可能需要缓存该数据,以便我们的应用程序执行得更快。
当由于发出网络请求的延迟而从内存而不是从磁盘检索数据更快时,通常会如此。实际上,许多网站都在CDN中缓存(尤其是内容不经常更改的情况),以便可以更快地将其提供给最终用户,并减少了后端服务器的负载。
缓存可以帮助您解决的另一个问题是后端必须执行一些计算量大且耗时的工作。缓存将您的查找时间从线性O(N)时间转换为恒定O(1)时间的先前结果可能会非常有优势。
同样,如果您的服务器必须进行多个网络请求和API调用才能组成发送回请求者的数据,则缓存数据可以减少网络调用的次数,从而减少延迟。
如果您的系统具有客户端(前端),服务器和数据库(后端),则可以在客户端(例如浏览器存储),客户端与服务器(例如CDN)之间或服务器本身上插入缓存。这将减少对数据库的网络调用。
因此,缓存可以在系统中的多个点或级别上发生,包括在硬件(CPU)级别上。
处理陈旧数据
您可能已经注意到,上面的示例对于“读取”操作是隐式方便的。写入操作在主要原则上并没有什么不同,但有以下补充注意事项:
- 写操作需要保持缓存和数据库同步
- 这可能会增加复杂性,因为要执行更多的操作,并且需要仔细分析有关处理未同步或“过时”数据的新注意事项
- 可能需要实施新的设计原则来处理该同步-应该同步还是异步完成?如果异步,那么以什么间隔?同时,从何处获取数据?缓存需要多久刷新一次,等等。
- 数据“逐出”或更新和刷新数据,以使缓存的数据保持最新和最新状态。这些包括LIFO,FIFO,LRU和LFU之类的技术。
因此,让我们以一些概括性的,非约束性的结论作为结尾。通常,当用于存储静态或不经常更改的数据时,并且当更改的源可能是单个操作而不是用户生成的操作时,缓存效果最佳。
在数据的一致性和新鲜度至关重要的情况下,缓存可能不是最佳的解决方案,除非系统中存在另一个可以有效刷新缓存的元素,这些间隔不会对应用程序的目的和用户体验产生不利影响。
第5节:代理
代理。什么?我们许多人都听说过代理服务器。我们可能已经在某些PC或Mac软件上看到了配置选项,这些选项讨论添加和配置代理服务器或“通过代理”访问。
因此,让我们了解一下相对简单,广泛使用且重要的技术。这是一个完全独立于计算机科学的英语单词,因此让我们从该定义开始。
资料来源:https : //www.merriam-webster.com/dictionary/proxy
现在,您可以将大部分内容从头脑中弹出,然后按住一个关键字:“替换”。
在计算中,代理通常是服务器,并且它是充当客户端和另一台服务器之间的中间人的服务器。从字面上看,这是位于客户端和服务器之间的一些代码。这就是代理的症结所在。
如果您需要复习,或者不确定客户端和服务器的定义,则“客户端”是从另一个进程或计算机(“服务器”)请求数据的进程(代码)或计算机。当浏览器从后端服务器请求数据时,它是客户端。
服务器为客户端提供服务,但也可以是客户端-从数据库检索数据时。然后,数据库是服务器,服务器是(数据库的)客户端,也是前端客户端(浏览器)的服务器。
来源:https://teoriadeisegnali.it/appint/html/altro/bgnet/clientserver.html#figure2
从上面可以看到,客户端-服务器关系是双向的。因此,客户端和服务器都是一回事。如果有一个中间人服务器接收到请求,然后将它们发送到另一个服务,然后将从另一个服务获得的响应转发回原始客户端,即代理服务器。
展望未来,我们将客户端称为客户端,将服务器称为服务器,将代理称为它们之间的事物。
因此,当客户端通过代理向服务器发送请求时,代理有时可能会掩盖客户端的身份-对于服务器,请求中通过的IP地址可能是代理而不是原始客户端。
对于那些访问网站或下载其他限制内容的人(例如从torrent网络或您所在国家/地区禁止的网站),您可能会认识到这种模式-这是建立VPN的原理。
在深入探讨之前,我想先介绍一下-通常使用的术语“代理”是指“转发”代理。转发代理是指代理在客户端与服务器之间的交互中代表客户端(代替客户端)进行操作的代理。
这有别于反向代理-反向代理代表服务器。在图中,它看起来是一样的-代理位于客户端和服务器之间,并且数据流是相同的客户端<->代理<->服务器。
关键区别在于反向代理被设计为替代服务器。通常,客户甚至都不知道网络请求是通过代理路由的,并且代理将其传递给了预期的服务器(并对服务器的响应执行了相同的操作)。
因此,在正向代理中,服务器将不知道客户端的请求及其响应正在通过代理,而在反向代理中,客户端将不知道请求和响应是通过代理进行路由的。
代理感到有点偷偷摸摸:)
但是在系统设计中,特别是对于复杂系统,代理是有用的,反向代理是特别有用的。您的反向代理可以委派许多您不希望主服务器处理的任务-它可以是网守,筛选器,负载平衡器和全方位助手。
因此代理可能有用,但您可能不确定原因。同样,如果你读过我的其他的东西,你知道,我坚信你能正确地只了解事情的时候,你知道为什么他们的存在-知道什么,他们做的是不够的。
我们已经谈到的VPN(用于转发代理)和负载平衡(反向代理服务器),但也有更多的例子在这里-我特别推荐克拉拉Clarkson的高度概括。
第6节:负载平衡
如果您考虑一下负载和平衡这两个词,您将开始对它在计算领域的作用有一个直觉。当服务器同时接收到大量请求时,它可能会减慢速度(吞吐量降低,延迟增加)。在一个点之后,它甚至可能失败(没有可用性)。
您可以赋予服务器更多的力量(垂直缩放),也可以添加更多服务器(水平缩放)。但是现在您必须弄清楚收入请求如何分配到各个服务器-哪些请求被路由到哪些服务器以及如何确保它们也不会过载?换句话说,如何平衡和分配请求负载?
输入负载均衡器。由于本文是对原理和概念的介绍,因此它们在必要时必须经过非常简化的解释。负载平衡器的工作是坐在客户端和服务器之间(但可以在其他位置插入它),并研究如何在多个服务器之间分配传入请求负载,以便最终用户(客户端)的体验始终快速,流畅和可靠。
因此,负载均衡器就像是引导流量的流量管理器。他们这样做是为了保持可用性和吞吐量。
了解负载均衡器在系统架构中的插入位置后,您会发现负载均衡器可以被视为反向代理。但是负载均衡器也可以插入其他位置,例如在其他交换机之间,例如在服务器和数据库之间。
平衡法-服务器选择策略
那么,负载均衡器如何决定如何路由和分配请求流量?首先,每次添加服务器时,都需要让负载均衡器知道它还有一个候选者可以将流量路由到该负载均衡器。
如果卸下服务器,则负载均衡器也需要知道这一点。该配置可确保负载均衡器知道其转到列表中有多少台服务器以及哪些服务器可用。甚至可以使负载平衡器随时了解每台服务器的负载级别,状态,可用性,当前任务等。
将负载均衡器配置为知道可以重定向到哪些服务器后,我们需要制定最佳路由策略,以确保在可用服务器之间进行适当的分配。
一个简单的方法是让负载均衡器随机选择一个服务器,然后以这种方式定向每个传入的请求。但是正如您可以想象的那样,随机性可能会导致问题和“不平衡”的分配,其中某些服务器的负载要比其他服务器更多,这可能会对整个系统的性能产生负面影响。
循环赛和加权循环赛
可以直观理解的另一种方法称为“循环”。这是许多人处理列表循环的方式。您从列表中的第一项开始,依次向下移动,当最后一项完成后,您将循环回到顶部,然后再次开始处理列表。
负载均衡器也可以做到这一点,只需按固定顺序遍历可用服务器即可。这样,负载就以一种易于理解和可预测的模式在服务器上平均分配。
通过将某些服务“加权”于其他服务,可以使轮询变得更“花哨”。在正常的标准轮询中,每个服务器的权重相等(假设所有服务器的权重均为1)。但是,当您对服务器加权时,可以使某些服务器的权重较低(例如,如果它们的功能较弱,则为0.5),而其他服务器的权重可能更高,例如0.7或0.9甚至是1。
然后,将根据这些权重按比例分配总流量,并相应地将其分配给功率与请求量成比例的服务器。
基于负载的服务器选择
更复杂的负载均衡器可以在其go-to列表中计算出服务器的当前容量,性能和负载,并根据当前负载和计算动态分配,从而获得最高吞吐量,最低延迟等。通过监视每个服务器的性能并确定哪些服务器可以和不能处理新请求。
基于IP哈希的选择
您可以将负载均衡器配置为对传入请求的IP地址进行哈希处理,并使用哈希值确定也由哪个服务器定向请求。如果我有5台服务器可用,则哈希函数将设计为返回五个哈希值之一,因此肯定会指定其中一台服务器来处理请求。
当您希望来自某个国家或地区的请求从最适合满足该地区内部需求的服务器获取数据时,或者在服务器缓存请求以便可以快速处理它们的情况下,基于IP哈希的路由可能非常有用。 。
在后一种情况下,您要确保该请求发送到先前已缓存相同请求的服务器,因为这将提高处理和响应该请求的速度和性能。
如果每个服务器都维护独立的缓存,并且负载均衡器没有始终如一地向同一服务器发送相同的请求,则最终服务器将重新执行已经作为对另一个服务器的先前请求而完成的工作,并且您将失去优化能力与缓存数据一起使用。
基于路径或服务的选择
您还可以使负载均衡器根据请求的“路径”或所提供的功能或服务来路由请求。例如,如果您从在线花店购买鲜花,则可能会向一台服务器发送加载“特别花束”的请求,而向另一台服务器发送信用卡付款。
如果只有十分之一的访客实际购买了鲜花,那么您可以拥有一个较小的服务器来处理付款,而较大的服务器可以处理所有浏览流量。
混合袋
与所有事物一样,您可以达到更高和更详细的复杂程度。您可以有多个负载均衡器,每个负载均衡器都有不同的服务器选择策略!而且如果您的系统非常庞大且流量很高,那么您可能需要负载均衡器作为负载均衡器…
最终,您需要向系统中添加组件,直到您的性能适应您的需求为止(您的需求可能看起来很平坦,或者随着时间的推移会缓慢上升,或者容易出现峰值!)。
第7节:一致的哈希
要理解的稍微棘手的概念之一是在负载平衡的上下文中进行哈希处理。因此它有自己的部分。
为了理解这一点,请首先了解哈希在概念上是如何工作的。TL; DR是哈希将输入转换为固定大小的值,通常是整数值(哈希)。
好的哈希算法或函数的关键原则之一是函数必须是确定性的,这是一种很好的说法,可以说相同的输入传递给函数时将生成相同的输出。因此,确定性的意思是-如果我传入字符串“ Code”(区分大小写)并且该函数生成哈希值11002,则每次我传入“ Code”时,它都必须生成“ 11002”作为整数。如果我输入“代码”,它将生成一个不同的数字(一致)。
有时,散列函数可以为多个输入生成相同的散列-这不是世界末日,并且有多种处理方法。实际上,唯一输入范围越多,可能性就越大。但是,当一个以上的输入确定性地生成相同的输出时,则称为“冲突”。
牢记这一点,让我们将其应用于路由和定向到服务器的请求。假设您有5台服务器来分配负载。一种易于理解的方法是对传入的请求进行哈希处理(可能按IP地址或某些客户端详细信息),然后为每个请求生成哈希。然后,将模运算符应用于该哈希,其中正确的操作数是服务器的数量。
例如,这就是您的负载均衡器的伪代码:
1 | request#1 => hashes to 34 |
如您所见,散列函数生成可能值的散布,并且当应用模运算符时,它将带出映射到服务器编号的较小范围的编号。
您肯定会获得映射到同一台服务器的不同请求,这很好,只要对所有服务器的总体分配具有“统一性”即可。
添加服务器和处理故障服务器
那么-如果我们要向其发送流量的服务器之一死了怎么办?哈希函数(请参阅上面的伪代码段)仍然认为有5台服务器,并且mod运算符生成的范围是0-4。但是,由于其中一台发生了故障,我们现在只有4台服务器,并且仍在向其发送流量。哎呀。
相反,我们可以添加第六台服务器,但是由于我们的mod运算符为5,因此永远不会获得任何流量,并且永远不会产生包含新添加的第六台服务器的数字。糟糕!
1 | // Let's add a 6th server |
我们注意到应用mod后的服务器号发生了变化(尽管在此示例中,请求号1和请求号3均不更改-但这仅是因为在这种特定情况下,该号才有效)。
实际上,结果是一半的请求(在其他示例中可能会更多!)现在全部路由到新服务器,并且我们失去了以前在服务器上缓存的数据的好处。
例如,请求#4曾经去到服务器E,但现在去了服务器C。与请求#4坐在服务器E上有关的所有缓存数据都没有用,因为请求现在要去服务器C。您可以计算对于其中一台服务器死亡的地方,也存在类似的问题,但是mod函数不断向其发送请求。
在这个很小的系统中,声音听起来很小。但是在非常大规模的系统中,这是一个很差的结果。#SystemDesignFail。
显然,简单的哈希分配系统无法很好地扩展或处理故障。
流行的解决方案-一致性哈希
不幸的是,这是我觉得单词描述不够的部分。最好从视觉上理解一致性哈希。但是到目前为止,本文的目的是让您对问题有一个直观的认识,它是什么,它为什么会出现,以及基本解决方案中的缺点是什么。请牢记这一点。
正如我们所讨论的,朴素散列的关键问题在于,当(A)服务器发生故障时,流量仍会路由到该服务器,并且(B)添加新服务器时,分配可能会发生实质性变化,从而失去了以前的缓存。
深入研究一致的哈希时,需要牢记两个非常重要的事情:
- 一致的哈希不能消除问题,尤其是B。但是它确实可以大大减少问题。刚开始时,您可能会想知道,一致性哈希有什么大不了,因为潜在的缺点仍然存在-是的,但程度要小得多,这本身对于大型系统来说是一项有价值的改进。
- 一致的哈希将哈希函数应用于传入的请求和服务器。因此,最终的输出落入值的设定范围(连续)。这个细节非常重要。
在观看以下推荐的视频时,请牢记这些内容,这些视频说明了哈希的一致性,否则,其好处可能并不明显。
我强烈推荐该视频,因为它嵌入了这些原则,而又不会给您带来太多细节。
汉娜·巴顿(Hannah Barton)简介如果您在真正理解此策略为何对负载均衡很重要时遇到一些麻烦,建议您稍作休息,然后返回负载均衡部分,然后再次阅读此内容。除非您在工作中直接遇到问题,否则所有这些都会变得非常抽象,这并不罕见!
第8节:数据库
我们简要地考虑了为满足许多不同用例而设计的不同类型的存储解决方案(数据库),并且其中一些更适合于某些任务。但是,从很高的层次上讲,数据库可以分为两种类型:关系型和非关系型。
关系数据库
一个关系数据库是一个已严格执行存储在数据库中的东西之间的关系。通常,通过要求数据库将每个此类事物(称为“实体”)表示为结构化表-具有零或多个行(“记录”,“条目”)和一个或多个列(“属性”),使这些关系成为可能。 ,“字段”)。
通过在实体上强制采用这种结构,我们可以确保每个项目/条目/记录都有正确的数据。它使实体之间具有更好的一致性和紧密关系的能力。
您可以在下表中记录“婴儿”(实体)数据的表格中看到此结构。表中的每个记录(“条目”)都有4个字段,代表与该婴儿有关的数据。这是经典的关系数据库结构(形式化的实体结构称为模式)。
来源:https : //web.stanford.edu/class/cs101/table-1-data.html
因此,了解关系数据库的关键特征是它们是高度结构化的,并且将结构强加给所有实体。通过确保添加到表的数据符合该结构来实施此结构。在其架构不允许的情况下,无法在表格中添加高度字段。
大多数关系数据库都支持一种称为SQL结构化查询语言的数据库查询语言。这是一种专门设计用于与结构化(关系)数据库的内容进行交互的语言。这两个概念紧密地结合在一起,以至于人们经常将关系数据库称为“ SQL数据库”(有时也称为“续集”数据库)。
通常,人们认为SQL(关系)数据库比非关系数据库支持更复杂的查询(组合不同的字段,过滤器和条件)。数据库本身会处理这些查询并发送回匹配的结果。
许多喜欢SQL数据库的人认为,如果没有该功能,则必须获取所有数据,然后让服务器或客户端将数据“存储”在内存中,并应用过滤条件-对于少量数据集,这是可以的但是对于具有数百万条记录和行的大型,复杂的数据集而言,这将严重影响性能。但是,情况并非总是如此,当我们了解NoSQL数据库时就会看到。
关系数据库的一个常见且广受欢迎的示例是PostgreSQL(通常称为“ Postgres”)数据库。
酸
ACID事务是描述良好关系数据库将支持的事务的一组功能。ACID =“原子,一致,隔离,耐用”。事务是与数据库的交互,通常是读或写操作。
原子性要求当一个事务包含多个操作时,数据库必须保证如果一个操作失败,则整个事务(所有操作)也会失败。它是“全有或全无”。这样,如果事务成功,则完成后您将知道所有子操作均已成功完成,并且如果操作失败,则您将知道随之进行的所有操作都将失败。
例如,如果单个事务涉及读取两个表并写入三个表,那么如果这些单个操作中的任何一个失败,则整个事务都会失败。这意味着这些单独的操作都不应该完成。您甚至不希望3个写入事务中的1个起作用—这会“弄脏”数据库中的数据!
一致性要求根据数据库中定义的规则,数据库中的每个事务都是有效的,并且当数据库更改状态(某些信息已更改)时,这种更改是有效的,并且不会破坏数据。每个事务将数据库从一个有效状态移动到另一有效状态。一致性可以认为如下:每个“读取”操作都会收到最新的“写入”操作结果。
隔离意味着您可以“同时”(同时)在数据库上运行多个事务,但是数据库最终将处于一种状态,看起来每个操作都已按顺序进行(按顺序执行,如操作队列) )。我个人认为“隔离”不是这个概念的描述性术语,但是我猜想ACCD比ACID难说…
持久性是保证一旦将数据存储在数据库中,它将保持不变。它将是“持久的”-存储在磁盘上而不是“内存”中。
非关系数据库
相反,非关系数据库对其数据的刚性较差,或者换句话说,结构较灵活。数据通常显示为“键值”对。一种简单的表示方式是将其作为“键-值”对对象的数组(列表),例如:
1 | // baby names |
非关系数据库也称为“ NoSQL”数据库,当您不希望或不需要具有一致的结构化数据时,它们将为您带来好处。
与ACID属性类似,NoSQL数据库属性有时也称为BASE:
基本可用,表示系统保证可用性
软状态均值意味着即使没有输入,系统状态也可能随时间变化
最终一致性指出,除非收到其他输入,否则系统将在(非常短的)时间内保持一致。
由于这些数据库的核心是将数据保存在类似于散列表的结构中,因此它们非常快速,简单且易于使用,非常适合诸如缓存,环境变量,配置文件和会话状态等用例。灵活性使它们非常适合在内存(例如Memcached)和持久性存储(例如DynamoDb)中使用。
还有其他称为文档数据库的“类似于JSON”的数据库,例如广受欢迎的MongoDb,它们的核心也是“键值”存储。
数据库索引
这是一个复杂的主题,因此,我将简要介绍一下表面知识,以便为您提供有关系统设计面试需要的高级概述。
想象一个拥有1亿行的数据库表。该表主要用于在每个记录中查找一个或两个值。要检索特定行的值,您需要遍历表。如果这是最后一条记录,那将需要很长时间!
索引是一种捷径记录的方式,该记录具有匹配值,比遍历每一行更有效。索引通常是添加到数据库中的数据结构,旨在促进快速搜索数据库中的那些特定属性(字段)。
因此,如果人口普查局有1.2亿条具有名称和年龄的记录,并且您最经常需要检索属于某个年龄组的人员列表,则可以在“年龄”属性上对该数据库建立索引。
索引是关系数据库的核心,并且在非关系数据库上也广泛提供索引。因此,从理论上讲,索引的好处可用于两种类型的数据库,这对于优化查找时间非常有利。
复制和分片
虽然这些听起来像是一部生物恐怖电影中的东西,但您更可能每天在数据库扩展的背景下听到它们。
复制是指复制(复制,复制)数据库。您可能还记得我们讨论可用性时的情况。
我们已经考虑了在系统中具有冗余以维持高可用性的好处。如果一个数据库出现故障,复制可确保数据库中的冗余。但这也引发了一个问题,即如何在副本之间同步数据,因为它们具有相同的数据。对数据库的写入和更新操作的复制可以同步(与更改主数据库同时)或异步进行。
同步主数据库和副本数据库之间可接受的时间间隔实际上取决于您的需求-如果您确实需要两个数据库之间的状态保持一致,则复制需要快速进行。您还希望确保,如果对副本的写操作失败,那么对主数据库的写操作也会失败(原子性)。
但是,当您拥有如此多的数据以至于仅仅对其进行复制可能会解决可用性问题,但不能解决吞吐量和延迟问题(速度)时,您该怎么办?
此时,您可能需要考虑将数据“分块”为“碎片”。有人也称此为分区数据(与分区硬盘不同!)。
分片数据会将您的大型数据库分解为较小的数据库。您可以根据数据的结构来确定如何分片数据。就像将每500万行存储在一个不同的分片中一样简单,或者采用其他最适合您的数据,需求和所服务位置的策略。
第9节:领导人选举
让我们再次回到服务器以讨论一个稍微高级的主题。我们已经了解了可用性的原理,以及冗余是提高可用性的一种方式。在处理将请求路由到冗余服务器群集时,我们还介绍了一些实际的注意事项。
但是有时,在这种设置中,多台服务器要做的事情差不多,可能会出现只需要一台服务器带头的情况。
例如,您要确保仅赋予一台服务器负责更新某些第三方API的责任,因为来自不同服务器的多次更新可能会导致问题或增加第三方方面的成本。
在这种情况下,您需要选择该主服务器来将此更新职责委派给该主服务器。这个过程称为领导人选举。
当群集中有多个服务器以提供冗余时,它们之间可以配置为只有一个领导者。他们还将检测该领导服务器何时发生故障,并指定另一台服务器代替它。
原理很简单,但细节在于魔鬼。真正棘手的部分是确保服务器在数据,状态和操作方面“同步”。
例如,始终存在某些中断可能导致一台或两台服务器与其他服务器断开连接的风险。在这种情况下,工程师最终会使用一些在区块链中使用的基础思想来为服务器集群导出共识值。
换句话说,使用共识算法为所有服务器提供一个“商定”值,当确定哪个服务器是领导者时,它们可以全部依靠其逻辑。
领导者选举通常使用诸如etcd之类的软件来实现,该软件是一个键值对存储,通过使用领导者选举本身并使用共识算法,可以提供高可用性和强一致性(这是有价值的和不寻常的组合)。
因此,工程师可以依靠etcd自己的领导者选举架构在系统中进行领导者选举。这是通过在代表etcd的服务中存储代表当前领导者的键值对来完成的。
由于ETCD高度可用和强烈一致的,即键-值对总是可以通过你的系统依赖于包含集群最后的“真理之源”的服务器是当前选出的领导人。
第10节:轮询,流式传输,套接字
在持续更新,推送通知,流内容和实时数据的现代时代,重要的是要掌握支撑这些技术的基本原理。要定期或立即更新应用程序中的数据,需要使用以下两种方法之一。
轮询
这很简单。如果您查看维基百科条目,可能会发现它有点紧张。因此,请查看其词典含义,尤其是在计算机科学的上下文中。牢记这个简单的基础。
轮询只是让您的客户端“检查”向您的服务器发送网络请求并请求更新的数据。这些请求通常按固定的时间间隔发出,例如5秒,15秒,1分钟或您的用例需要的任何其他时间间隔。
每几秒钟轮询一次仍然与实时轮询不太一样,并且还存在以下缺点,特别是如果您同时拥有一百万以上的用户:
- 几乎恒定的网络请求(不适用于客户端)
- 几乎恒定的入站请求(对于服务器负载而言不大-每秒超过100万个请求!)
因此,快速轮询并不是真正有效或高效的方法,因此,在数据更新中的细小间隙对您的应用程序来说不是问题的情况下,最好使用轮询。
例如,如果您构建了一个Uber克隆,则可能让驾驶员端应用程序每5秒发送一次驾驶员位置数据,并且让驾驶员方应用程序每5秒轮询一次驾驶员的位置。
流媒体
流式传输解决了不断轮询的问题。如果有必要不断命中服务器,那么最好使用称为web-sockets的东西。
这是一种旨在通过TCP工作的网络通信协议。它在客户端和服务器之间打开双向专用通道(套接字),有点像两个端点之间的开放热线。
与通常的TCP / IP通信不同,这些套接字是“长期存在的”,因此它向服务器发出的单个请求打开了此热线以进行双向数据传输,而不是多个单独的请求。长期而言,我们的意思是机器之间的套接字连接将持续到双方将其关闭或网络断开为止。
您可能还记得我们在IP,TCP和HTTP上的讨论,它们通过为每个请求-响应周期发送数据“数据包”来进行操作。Web套接字意味着只有一个请求-响应交互(如果您考虑的话,实际上不是一个循环!),这打开了以“流”形式发送两个数据的通道。
轮询和所有基于“常规” IP的通信的最大区别在于,尽管轮询使客户端以固定的时间间隔向服务器请求数据(“拉动”数据),但在流传输中,客户端处于“待命”状态,等待服务器以其方式“推送”一些数据。服务器将在数据更改时发送数据,客户端始终在监听数据。因此,如果数据变化是恒定的,则它将变成“流”,这可能会更好地满足用户的需求。
例如,在使用协作编码IDE时,当两个用户中的任何一个键入内容时,它都可以显示在另一个上,并且这是通过网络套接字完成的,因为您希望进行实时协作。如果在您尝试键入相同的内容后或等待3分钟之后才想知道我在做什么,那么我输入的内容会显示在屏幕上会很糟!
或想到在线多人游戏-这是在玩家之间流式传输游戏数据的完美用例!
总之,用例确定了轮询和流式传输之间的选择。通常,如果您的数据是“实时”的,则希望流式传输,并且如果可以有滞后时间(即使只有15秒仍是滞后时间),那么轮询可能是一个不错的选择。但这全取决于您有多少同时用户以及他们是否希望数据是即时的。流服务的一个常用示例是Apache Kafka。
第11节:端点保护
当您构建大型系统时,保护系统免受过多的操作(使用系统实际上不需要进行此类操作)变得很重要。现在听起来很抽象。但是,请考虑一下-您几次疯狂地单击按钮,认为它将使系统响应速度更快?想象一下,如果这些按钮中的每个按钮都单击ping服务器,而该服务器试图全部处理它们!如果由于某种原因(例如服务器在异常负载下挣扎),系统的吞吐量较低,那么每次单击都会使系统变得更慢,因为它必须处理所有这些!
有时甚至与保护系统无关。有时您想限制操作,因为这是服务的一部分。例如,您可能在第三方API服务上使用了免费套餐,其中每30分钟间隔仅允许您发出20个请求。如果您在30分钟的间隔内发出21或300个请求,则在前20个之后,该服务器将停止处理您的请求。
这就是所谓的限速。使用速率限制,服务器可以在给定的时间范围内限制客户端尝试的操作次数。可以根据用户,请求,时间,有效负载或其他事物来计算速率限制。通常,一旦在时间窗口中超过了限制,则对于该窗口的其余时间,服务器将返回错误。
好的,现在您可能会认为端点“保护”是一种夸张。您只是在限制用户从端点获取某些东西的能力。的确如此,但是当用户(客户端)是恶意的时(例如说是破坏您的端点的机器人),它也是一种保护。为什么会这样?因为向服务器泛滥的请求超出其处理能力,这是恶意人员用来关闭该服务器的策略,从而有效地关闭了该服务。这正是拒绝服务(D0S)攻击的本质。
尽管可以通过这种方式防御DoS攻击,但速率限制本身并不能保护您免受DoS攻击的复杂版本-分布式DoS的攻击。这里的分布只是意味着攻击来自看似无关的多个客户端,并且没有真正的方法将其识别为受单个恶意代理控制。需要使用其他方法来防止这种协调的分布式攻击。
但是无论如何,对于不太可怕的用例,速率限制都是有用且流行的,例如我提到的API限制。考虑到速率限制的工作原理,由于服务器必须首先检查限制条件并在必要时强制执行限制条件,因此您需要考虑要使用哪种数据结构和数据库来使这些检查超快,以便如果请求在允许的范围内,则不会减慢处理请求的速度。另外,如果您将其存储在服务器本身的内存中,则需要保证来自给定客户端的所有请求都将到达该服务器,以便它可以正确实施限制。要处理这种情况,通常使用单独的Redis服务 位于服务器外部,但将用户的详细信息保留在内存中,并且可以快速确定用户是否在允许的范围内。
速率限制可以和您要执行的规则一样复杂,但是上一节应涵盖基本知识和最常见的用例。
第12节:消息和发布订阅
当您设计和构建大型分布式系统时,要使该系统凝聚而流畅地工作,就必须在组成系统的组件和服务之间交换信息。但是,正如我们之前所看到的那样,依赖网络的系统与网络具有同样的弱点-它们非常脆弱。网络发生故障,这种情况很少发生。当网络出现故障时,系统中的组件无法通信可能会降低系统性能(最佳情况)或导致系统完全故障(最坏情况)。因此,即使系统中组件之间存在“任意分区”(即故障),分布式系统也需要可靠的机制来确保通信在中断处继续进行或恢复。
举例来说,假设您正在预订机票。您将获得一个不错的价格,选择您的座位,确认预订,甚至还使用信用卡付款。现在,您正在等待票证PDF到达收件箱。您等待,再等待,它永远不会到来。在某个地方,发生了无法处理或无法正确恢复的系统故障。预订系统通常会与航空公司和定价API连接,以处理实际的航班选择,票价摘要,航班日期和时间等。所有这些操作都是在您单击网站的预订UI时完成的。但是,几分钟后,它不必将票证的PDF发送给您。取而代之的是,UI可以简单地确认您的预订已完成,并且您可以很快在收件箱中找到门票。那’ 这是一种合理且通用的预订用户体验,因为付款的时刻和票证的接收不必同时进行-这两个事件可以是异步的。这样的系统需要进行消息传递以确保异步生成PDF的服务(服务器端点)收到已确认的付费预订以及所有详细信息的通知,然后可以自动生成PDF并通过电子邮件发送给您。但是,如果该消息传递系统发生故障,则电子邮件服务将永远不会知道您的预订,并且不会生成任何票证。和所有详细信息,然后可以自动生成PDF并通过电子邮件发送给您。但是,如果该消息传递系统发生故障,则电子邮件服务将永远不会知道您的预订,并且不会生成任何票证。和所有详细信息,然后可以自动生成PDF并通过电子邮件发送给您。但是,如果该消息传递系统发生故障,则电子邮件服务将永远不会知道您的预订,并且不会生成任何票证。
发布者/订阅者消息
这是一个非常流行的消息传递范例(模型)。关键概念是发布者“发布”消息,而订阅者订阅消息。为了提供更大的粒度,消息可以属于某个“主题”,就像一个类别。这些主题就像专用的“渠道”或管道,其中每个管道互斥处理属于特定主题的消息。订阅者选择他们想要订阅的主题,并获得该主题中消息的通知。该系统的优势在于,发布者和订阅者可以完全分离,即他们不需要彼此了解。发布者宣布,订阅者收听其正在寻找的主题的通告。
服务器通常是消息的发布者,通常有多个主题(渠道)被发布到。特定主题的使用者订阅了这些主题。服务器(发布者)和订户(可以是另一台服务器)之间没有直接通信。唯一的交互作用是发布者和主题之间以及主题和订阅者之间。
主题中的消息只是需要传达的数据,并且可以采用您需要的任何形式。这样一来,您可以在发布/订阅中获得四个角色:发布者,订阅者,主题和消息。
比数据库更好
那么,为什么要为此烦恼呢?为什么不将所有数据持久保存到数据库并直接从那里使用呢?嗯,您需要一个系统来使消息排队,因为每个消息都对应于一个需要根据该消息的数据完成的任务。因此,在我们的票务示例中,如果100个人在35分钟内完成预订,那么将所有内容都放入数据库中并不能解决通过电子邮件发送这100个人的问题。它只存储100个事务。发布/订阅系统处理通信,任务排序和消息将保留在数据库中。因此,系统可以提供有用的功能,例如“至少一次”传递(消息不会丢失),持久存储,消息排序,消息“重试”,“可重播”等。如果没有此系统,仅将消息存储在数据库中将无法帮助您确保消息已传递(使用)并已采取行动成功完成任务。
有时,同一条消息可能会被订户消耗一次以上-通常是由于网络暂时中断,尽管订户消耗了该消息,但并没有让发布者知道。因此,发布者只需将其重新发送给订阅者即可。这就是为什么保证是“至少一次”而不是“一次且只有一次”的原因。在分布式系统中,这是不可避免的,因为网络本质上是不可靠的。这可能会带来复杂性,其中消息会在订户侧触发操作,并且该操作可能会更改数据库中的内容(更改整个应用程序中的状态)。如果单个操作被重复多次,并且应用程序的状态每次更改都会怎样?
控制结果-一个或多个结果?
解决这个新问题的方法称为幂等性(幂等性),这是一个很重要的概念,但在前几次检查时并不直观。这个概念可能看起来很复杂(特别是如果您阅读了Wikipedia条目),因此出于当前目的,这是StackOverflow的一种用户友好的简化形式:
在计算中,幂等运算是指如果使用相同的输入参数多次调用幂等运算,则不会产生任何其他影响。
因此,当订户处理一条消息两次或三遍时,应用程序的总体状态恰好是第一次处理该消息后的状态。例如,如果在预订机票结束时并且输入了信用卡详细信息之后,由于系统运行缓慢,您单击了“立即付款”三次,则您不想支付3倍的机票价格。 ?您需要具有幂等性,以确保第一次点击后的每次点击都不会再次进行购买,并且会多次向您的信用卡收费。相反,您可以在最好的朋友的新闻提要上发表相同评论N次。它们都将显示为单独的注释,除了令人讨厌之外,这实际上不是错误的。另一个示例是在“中”帖子上提供“拍手”-每个拍手都是为了增加拍手的数量,而不是一个,只能一个。后两个示例不需要幂等,但付款示例需要。
消息传递系统有许多种风格,系统的选择取决于要解决的用例。人们经常会提到“基于事件”的体系结构,这意味着系统依赖于有关“事件”的消息(例如为票证付款)来处理操作(例如通过电子邮件发送票证)。真正经常谈论的服务是Apache Kafka,RabbitMQ,Google Cloud Pub / Sub,AWS SNS / SQS。
第13节:较小的必需品
记录中
随着时间的流逝,您的系统将收集大量数据。这些数据大部分非常有用。它可以使您了解系统的运行状况,性能和问题。它还可以为您提供有价值的见解,以了解谁使用您的系统,他们如何使用它,多久使用一次,或多或少使用哪些部分等等。
这些数据对于分析,性能优化和产品改进非常有价值。这对于调试非常有价值,不仅在开发过程中登录到控制台时,而且在寻找测试和生产环境中的错误时,都非常有用。因此,日志也有助于跟踪和审核。
记录日志时要记住的关键技巧是将其视为一系列连续事件,这意味着数据成为时间序列数据,并且应特别设计使用的工具和数据库来帮助处理此类数据。
监控方式
这是登录后的下一步。它回答了“如何处理所有记录数据?”的问题。您监视并分析它。您构建或使用工具和服务来解析这些数据,并为您提供仪表板或图表或以其他人类可读的方式理解这些数据的方法。
通过将数据存储在专门用于处理此类数据(时间序列数据)的数据库中,您可以插入基于该数据结构和意图构建的其他工具。
警示
当您积极监视时,还应该放置一个系统,以提醒您发生重大事件。就像对股票价格超过某个上限或低于某个阈值发出警报一样,如果您正在观察的某些指标过高或过低,则可能会发送警报。如果响应时间(延迟)或错误和失败超过“可接受的”水平,则可以设置警报。
良好的日志记录和监视的关键是确保您的数据在一段时间内相当一致,因为使用不一致的数据可能会导致缺少字段,从而破坏分析工具或降低日志记录的好处。
资源资源
如所承诺的,一些有用的资源如下:
- 精彩的Github回购,其中包含概念,图表和学习准备
- Tushar Roy对系统设计的介绍
- Gaurav Sen的YouTube播放列表
- SQL与NoSQL
希望您喜欢这份长篇指南!