【系统设计】系统设计面试相关课题的速成“葵花宝典”!
@TOC
推荐超级课程:
这是一门关于系统设计面试概念的完全速成讲解,这些概念是您在面试系统设计师工作前需要了解的。 系统设计面试与编码无关,人们不想看到您编写实际代码,而是想看到您如何将整个系统组合在一起, 这正是我们将在本教程中涵盖的内容,我们将讨论所有您设计大规模分布式系统之前需要了解的概念。
计算机架构(磁盘存储、内存、缓存、CPU)
要了解个人计算机的高级架构,在计算机上不同部件如何共同执行我们的代码,计算机通过分层系统运行,每一层针对不同任务进行优化。
当程序运行时,其变量,中间结果计算,运行时堆栈等存储在RAM中,因为它允许快速读写访问,这是一种易失性内存,这意味着它需要电源来保留其内容,重启计算机后可能数据不会被保存,就容量而言RAM在消费设备中为几GB,而高端服务器可能为几百GB,在读写速度上通常超过每秒5000兆字节,比甚至最快的SSD 硬盘速度都要快,但有时候即使是这种速度也不够,这就引出了缓存,缓存比RAM更小,通常以兆字节为单位,但与RAM相比,缓存内存的访问时间更快,L1缓存,CPU首先检查L1缓存中的数据,如果没有找到,它会检查L2和L3缓存,最后检查RAM缓存的目的是减少访问数据的平均时间,这就是我们在这里存储频繁使用的数据以优化CPU性能CPU是计算机的大脑,当您运行代码时,CPU处理在程序中定义的操作,在可以运行我们的代码之前,需要首先编译成像Java,C++,Python或其他语言一样的机器代码编译器执行此转换,一旦代码编译成机器代码,CPU可以执行它,可以从我们的RAM磁盘和缓存数据读取和写入数据。
最后主板,它是将所有组件连接在一起的组件,它提供路径允许数据在这些组件之间流动,现在让我们看一下生产应用程序体系结构(CI/CD、负载均衡器、日志记录和监控)
应用程序体系结构(CI/CD、负载均衡器、日志记录和监控)
当事情不按计划进行时,意味着我们的日志系统检测到请求失败或异常,首先它会激活我们的警报服务,然后将推送通知发送给用户在日志系统检测到错误的请求或异常之后,首先会出现我们的警报服务,然后会向用户发送推送通知,以便让用户了解,从一般的某事崩溃到具体的支付失败
有效的沟通使用户不被留在黑暗中,培养信任和可靠性,现代实践是将这些警报直接集成到常用的平台中,比如Slack,想象一个专门的Slack频道,当问题出现时,警报会立即弹出,这使开发人员几乎可以即刻采取行动,解决根本原因,之后开发人员需要首先调试问题首先必须确定问题,我们之前提到的日志是我们的第一通电话,开发人员会浏览这些日志,寻找可能指向问题来源的模式或异常,之后它需要在安全环境中复制此问题安全环境的黄金规则是永远不要在生产环境中调试,而是开发人员在分期或测试环境中重新创建此问题,这可以确保用户不会受到调试过程的影响,然后开发人员使用工具观看运行数据应用程序并开始调试,一旦bug修复,会发布热修补,这是一种快速临时修复,旨在使事物再次运行,这就像一个补丁,在实现更持久的解决方案之前,这就像具体用例的最佳解决方案,这意味着制定关于我们可以承受何种妥协的明智决定。
系统SLA
系统的一个重要指标是可用性,这是系统的运行性能和可靠性的度量。
当我们谈及可用性时,实质上我们在问的是我们的系统在用户需要时是否正常运行,这通常以百分比的方式衡量,目标是达到"黄金59可用性",比如我们运行一个关键服务,具有99.9%的可用性,这意味着每年可以有大约8.76小时的宕机时间,但是如果我们加入两个NES,我们仅仅是在谈论每年大约5分钟的宕机时间,这是一个巨大的差异,尤其适用于每秒都重要的服务。我们经常以正常运行时间和宕机时间来衡量它,这里就涉及到服务水平目标和服务水平协议。SLO就像为我们的系统性能和可用性设定目标,例如,我们可能会设置一个SLO,规定我们的网络服务应该在99.9%的时间内在300毫秒内响应请求。另一方面,SLA就类似于我们与用户或客户之间的正式合同,它们定义了我们承诺提供的最低服务水平,所以如果我们的SLA保证了99.99%的可用性,而我们低于这个水平,我们可能需要向客户提供退款或其他补偿。将弹性融入我们的系统意味着对意外情况有所准备,这可能意味着实施冗余系统,确保总是有备用系统可以在出现故障时接管,或者可能意味着设计我们的系统以优雅地退化,即使某些功能不可用,核心功能仍然完好。为了衡量这一方面,我们使用可靠性、容错性和冗余度。可靠性意味着确保我们的系统正确而一贯地工作,容错性是指为发生意外情况做好准备,我们的系统如何处理意外故障或攻击,冗余度是指备份,确保如果系统的一部分失效,另一部分可以顶替上去。我们还需要衡量我们系统的速度,为此我们有吞吐量和延迟。吞吐量衡量我们的系统在一定时间内可以处理多少数据,我们有服务器吞吐量,单位是每秒请求量,这个指标提供了服务器可以处理多少客户端请求的指示,更高的RPS值通常表示更好的性能和处理更多并发用户的能力,我们有数据库吞吐量,单位是每秒查询次数,这量化了数据库在一秒内可以处理的查询数量,和服务器吞吐量类似,更高的QPS值通常代表更好的性能,我们还有数据吞吐量,单位是每秒字节数,反映了一定时间内通过网络传输或系统处理的数据量,另一方面,延迟衡量处理单个请求需要多长时间,即请求获得响应的时间,针对一个优化,通常可能会牺牲另一个,例如批处理操作可以增加吞吐量,但也可能增加延迟,设计一个多功能系统可能会导致许多问题,从性能瓶颈到安全漏洞,与可以轻松重构代码不同,重新设计系统可能是一项艰巨的任务,因此投资时间和资源来确保设计从一开始就是正确的,并建立一个坚实基础来支撑未来功能和用户增长是至关重要的,接下来是网络(TCP、UDP、DNS、IP地址和IP头)
网络设计
让我们谈谈网络基础知识,当我们谈网络基础知识时,实质上我们在探讨计算机如何相互通信,这种通信的核心是IP地址,每个网络中的设备都有一个唯一的标识符,IP V4地址是32位的,允许大约40亿个唯一地址,但随着设备数量的增加,我们正在转向IP V6,它使用128位地址,显着增加了可用唯一地址的数量,当两台计算机在网络上通信时,它们发送和接收数据包,每个数据包都包含一个IP头部,其中包含发送者和接收者的IP地址等基本信息,确保数据到达正确的目的地,这个过程受Internet协议的管理,它是一组定义数据如何发送和接收的规则,除了IP层,我们还有应用层,其中存储了特定于应用程序协议的数据,数据包中的数据格式按照特定的应用程序协议数据进行格式化,例如HTTP用于Web浏览,以便接收设备可以正确解释数据,一旦我们了解了IP地址和数据包的基础知识,我们就可以进入传输层,这时TCP和UDP出现,TCP在传输层运行,确保可靠通信,它类似于一个快递员,不仅确保您的包裹到达,还检查是否有任何遗漏,因此每个数据包还包括一个TCP头部,其中包含端口号和控制流等必要信息,用于管理连接和数据流,TCP以其可靠性而闻名,确保数据包的完整正确交付,它通过顺序号等功能实现,以跟踪数据包的顺序和通过所谓的握手过程建立稳定的连接,而UDP比TCP更快,但不如TCP可靠,它在发送数据之前不建立连接,也不保证数据包的交付或顺序,但这使UDP在对实时性通信更快,例如视频通话或直播流时更受欢迎,速度是至关重要的,而一些数据损失是可以接受的,为了将所有这些概念联系在一起,让我们来谈谈DNS域名系统,DNS就像互联网的电话簿,将人类友好的域名解析成IP地址,当您在浏览器中键入URL时,浏览器发送DNS查询以找到相应的IP地址,从而建立与服务器的连接并检索网页,DNS的运行由IEN监督,协调全球IP地址空间和域名系统,域名注册商如Namechip或GoldEd经ICANN认可可以向公众出售域名,DNS使用不同类型的记录,如A记录,将域名映射到其对应的IP地址,确保您的请求到达正确的服务器,或AAAA记录,将域名映射到IP V6地址,最后让我们谈谈支持所有这些通信的网络基础设施,网络上的设备具有公共或私有IP地址,公共IP地址在互联网上是唯一的,而私有IP地址在本地网络中是唯一的,IP地址可以永久分配给设备,也可以是动态的随时间变化,动态IP地址通常用于家庭互联网连接,设备在本地区域网络中直接通信,为了保护这些网络,我们使用防火墙来监控和控制进出网络的流量,在设备内部,特定进程或服务通过端口进行识别,当与IP地址结合时产生一个对网络服务的唯一标识符,一些端口用于特定的协议,如80用于HTTP或22用于SSH,现在让我们覆盖所有基本的应用层协议(HTTP、WebSockets、WebRTC、MQTT等)
应用层协议中最常见的协议是HTTP,它代表着超文本传输协议,构建在TCP/IP之上,它是一个请求响应协议,但可以将其想象为一次没有记忆的对话,每次互动都是独立的,没有过去的记忆,这意味着服务器不必在请求之间存储任何上下文,相反,每个请求都包含了所有必要的信息,注意到头部包含了诸如URL和方法的细节,而正文携带了请求或响应的内容,每个响应还包括状态代码,用于提供关于客户端请求在服务器上的结果的反馈,例如200系列是成功代码,表示请求已成功接收和处理,300系列是重定向代码,表示需要用户代理进一步采取行动以完成请求,400系列是客户端错误代码,用于请求包含恶劣语法或无法实现,500系列是服务器错误代码,表示服务器发生故障,我们还有每个请求的方法,最常见的方法是get、post、put、patch和delete,get用于获取数据,post通常用于在服务器上创建数据,put和patch用于更新记录,delete用于从数据库中删除记录,HTTP是一种单向连接,但对于实时更新,我们使用WebSocket,提供了一个双向通信通道,通过单个长连接允许服务器向客户端实时推送更新,这对需要不断更新数据的应用程序非常重要,无需重复的HTTP请求响应周期,通常用于聊天应用、现场体育更新或股票市场信息流,关于电子邮件相关的协议,SMTP是互联网上的电子邮件传输标准,它是发送邮件消息之间的协议,大多数电子邮件客户端使用SMTP发送邮件,IMAP或POP3用于检索邮件,IMAP用于从服务器检索电子邮件,允许客户端访问和操作邮件,这对需要从多个设备访问电子邮件的用户来说是理想的,POP3用于从服务器下载电子邮件到本地客户端,通常用于在单个设备上管理电子邮件,继续讨论文件传输和管理协议,传统的文件传输协议是FTP,通常用于网站维护和大数据传输,用于客户端和服务器之间的文件传输,有助于将文件上传到服务器或备份文件,我们还有SSH或安全外壳,用于在未加密网络上安全地操作网络服务,常用于远程登录到远程计算机并执行命令或传输文件,还有实时通信协议如WebRTC,它可以实现浏览器到浏览器的语音通话、视频聊天和文件共享,无需内部或外部插件,这对像视频会议和直播流这样需要的不间断更新的应用程序非常关键,还有MQTT,它是一种轻量级的消息传输协议,适用于处理处理能力有限的设备和需要低带宽的场景,例如物联网设备,AMQP是用于消息中间件的协议,为企业级消息通信提供了稳健性和安全性,例如,在RabbitMQ等工具中使用,最后让我们谈谈RPC,这是一种允许一台计算机上的程序在服务器或另一台计算机上执行代码的协议,它是一种调用函数的方法,就像是一个本地调用一样在远程机器上执行函数,隐藏了网络通信的细节,使开发人员可以轻松地与远程函数交互,就像它们是与应用程序本地通信一样,许多应用层协议使用RPC机制执行其操作,例如,在Web服务中,HTTP请求可能会导致后台进行RPC调用,处理数据或代表客户端执行操作,或SMTP服务器可能会内部使用RPC调用处理电子邮件消息或与数据库交互,当然还有许多其他应用层协议,但在这里介绍的协议是最常用和对Web开发至关重要的。在这一部分,让我们讨论API设计。
API设计
通过从基础开始并逐步发展到定义出色的API的最佳实践,作为IT系统设计师,让我们考虑一个像Shopify这样的电子商务平台的API,如果您对此不熟悉,Shopify是一个著名的电子商务平台,允许企业在其上建立在线商店。在API设计中,我们关注定义输入,比如卖家提供的新产品的产品详细信息,以及查询API产品时返回的信息如何暴露给用户界面。CR代表创建、读取、更新和删除,这是任何数据驱动应用程序的基本操作。例如,要添加一个新产品,我们需要向/API SL产品发送post请求,产品详细信息在请求体中发送。要检索这些产品,我们需要向/i/产品发送get请求。对于更新,我们使用put或patch请求到/product/该产品的ID,删除与更新类似,再次/product/产品的ID。类似地,我们可能还有另一个向/product/产品的ID发送get请求来获取单个产品。另一部分是决定要使用的通信协议,如HTTP、Websockets或其他协议,以及数据传输机制,可以是Json、XML或协议缓冲区。这通常用于restful APIs,但我们也有GraphQL和gRPC范式,因此APIs有不同的范式,每个都有自己的协议和标准。最常见的是rest,代表表述状态转移,它是无状态的,这意味着从客户端到服务器的每个请求都必须包含理解和完成请求所需的所有信息,它使用标准的HTTP方法get post put和delete,并且可以被不同的客户端、浏览器或移动应用轻松消费。然而,restful API的缺点是它可能导致数据获取或数据获取不足,因为可能需要更多的端点来访问特定数据,通常restful APIs使用Json进行数据交换。另一方面,GraphQL APIs允许客户端请求他们需要的确切数据,避免过度获取和不足获取数据,它具有强类型的查询,但复杂的查询可能会影响服务器性能,所有请求都被发送作为post请求,graph API通常以HTTP状态代码响应,即使在错误情况下,响应体中也有错误详细信息。gRPC代表Google远程过程调用,它建立在提供高级功能的HTTP 2上,如复用和服务器推送,它使用协议缓冲区的方式序列化结构化数据,因此在带宽和资源方面足够,尤其适用于微服务,然而,与Json相比,它 less可读,并且需要http2支持才能运行,在电子商务环境中,您可能有用户到订单或订单到产品之间的关系,您需要设计端点以反映这些关系,例如,为了获取特定用户的订单,您需要查询get/用户SL用户id/orders。常见的查询还包括限制和偏移用于分页或开始和结束日期用于筛选某个日期范围内的产品,这使用户或客户端能够检索特定的数据集而不会使系统不堪重负。一个良好设计的get请求应该是IDMponasent,意味着多次调用它不会改变结果,并且始终返回相同的结果,get请求永远不应该修改数据,它们仅用于检索,如果需要更新或创建数据,您需要做put或post请求,当修改端点时,保持向后兼容性非常重要,这意味着我们需要确保更改不会破坏现有客户端,一种常见做法是引入新版本,例如第二版本产品,以便第一版本API仍然可以为旧客户端提供服务,第二版本API应为当前客户端提供服务,这是restful APIs的情况。在graphql APIs的情况下,添加新字段如V2字段而不移除旧字段有助于发展API而不破坏现有客户端,另一个最佳做法是设置速率限制,这可以防止API遭受DDoS攻击,用于控制用户在某个时间段内可以发出的请求数量,防止单个用户向您的单个API发送过多请求。另一个常见做法是设置CORS设置,这代表跨源资源共享,通过CORS设置,您可以控制哪些域可以访问您的API,防止不必要的跨站点交互。
缓存和CDN设计
现在想象一家公司正在芬兰的谷歌云数据中心中托管一个网站,对于在欧洲的用户加载可能需要大约100毫秒,但对于墨西哥的用户可能需要3到5秒。
幸运的是,有策略可以最小化远程用户的请求延迟,这些策略被称为缓存和内容交付网络,这两个概念是现代网站开发和系统设计中的重要概念。缓存是一种用于提高系统性能和效率的技术,它涉及在临时存储中存储某些数据的副本,以便将来对该数据的请求可以更快地提供服务。缓存可以存储在四个常见的地方,第一个是浏览器缓存,在这里我们在用户的本地计算机上存储网站资源,所以当用户重新访问站点时,浏览器可以从本地缓存中加载站点,而不是再次从服务器获取所有内容。用户可以通过调整浏览器设置来禁用缓存,在大多数浏览器中,开发者可以通过开发者工具禁用缓存,例如在Chrome中,我们在开发者工具的网络选项卡中有一个禁用缓存选项,缓存存储在由浏览器管理的客户端硬盘上的目录中,浏览器缓存将HTML、CSS和JS捆绑文件存储在用户的本地计算机上,通常在一个专用的缓存目录中由浏览器管理。我们使用缓存控制头来告诉浏览器该内容应该被缓存多长时间,例如,在这里,缓存控制被设置为7200秒,相当于2小时,当请求的数据在缓存中找到时,我们称之为缓存命中,另一方面,我们有缓存未命中,这发生在请求的数据不在缓存中时,需要从原始来源获取,并且缓存比率是从缓存中服务的请求与所有请求进行对比的百分比,较高的比率表示更有效的缓存,你可以检查缓存是否命中或未命中的header,例如在这种情况下,它显示未命中,所以缓存未命中,在缓存被发现的情况下,我们将会有命中,此外还有服务器缓存,该缓存涉及将经常访问的数据存储在服务器端,减少执行昂贵操作的需要,比如数据库查询,服务器端缓存存储在服务器上或在一个单独的缓存服务器上,或者在内存中,例如Redis,或者在硬盘上,通常服务器在查询数据库之前从数据缓存中查找数据,如果数据在缓存中,直接返回,否则服务器查询数据库,如果数据不在缓存中,服务器从数据库中检索它返回给用户,然后存储在缓存中以备将来请求,这是直接写入缓存的WR缓存情况,在这种情况下数据直接写入永久存储,绕过缓存,在写入性能不那么关键时使用,也有write through cache,数据同时写入缓存和永久存储,确保数据一致性,但可能比写入缓存慢一些,还有write back cache,数据首先写入缓存,然后在稍后的时间写入永久存储,这可以提高写入性能,但有丢失数据的风险,如果服务器崩溃,当缓存满了我们需要释放一些空间再次使用缓存,为此,我们有淘汰政策,这些政策是确定缓存满时要从中移除哪些项目的规则,常见的政策包括删除最近最少使用的条目或先进先出,我们首先移除最先添加的条目或删除最不经常使用的条目,数据库缓存是另一个至关重要的方面,它指的是缓存数据库查询结果以提高基于数据库的应用程序的性能,通常在数据库系统本身内进行,或者在外部缓存层上,如Radis或M cache,当发出查询时,我们首先检查缓存,以查看是否存储了该查询的结果,如果是,则返回缓存的数据,避免对数据库执行查询,但如果在缓存中找不到数据,那么查询将针对数据库执行,结果将存储在缓存中以备将来请求,这对于读取密集型应用程序是有益处的,一些查询经常被执行,我们使用与服务器端缓存相同的淘汰政策,另一种缓存类型是CDN,它们是一个分布在地理上的服务器网络,通常用于提供静态内容,如JavaScript、HTML、CSS或图像和视频文件,它们从原始服务器缓存内容并将其交付给最近的CDN服务器的用户,当用户请求像图片或网站这样的文件时,请求被重定向到最近的CDN服务器,如果CDN服务器有缓存内容,它将将其提供给用户,否则它将从原始服务器获取内容缓存,然后转发给用户,这是基于池的CDN类型,当用户首次请求时,CDN自动从原始服务器中拉取内容,这适用于具有大量经常更新的静态内容的网站,因为CDN自动保持内容最新,所以需要较少的主动管理,另一种类型是推送型CDN,这是您将内容上传到原始服务器,然后它将这些文件分发到CDN,当有大文件不经常更新但需要快速分发时,这非常有用,但需要更多的主动管理以确定CDN上存储了哪些内容,我们再次使用缓存控制头来告诉浏览器要缓存CDN上的内容多长时间,CDN通常用于传送静态资产,如图像、CSS文件、JavaScript捆绑包或视频内容,如果您需要确保用户的高可用性和性能,这将非常有用,它还可以减少对原始服务器的负荷,但在某些情况下仍然需要访问原始服务器,例如在提供经常变化的动态内容或处理需要实时处理的任务时,以及应用程序需要在CDN中无法执行复杂的服务器端逻辑的情况下,我们从CDN获得的一些好处是通过从距用户更近的位置提供内容来降低延迟,CDN显著降低延迟,还增加了高可用性和可伸缩性,CDN可以处理高流量负载,并且对硬件故障具有弹性,另外,它还提高了安全性,因为许多CDN提供安全功能,如防护和流量加密,另外缓存的好处也包括降低延迟,因为我们有快速的数据检索,因为数据是从附近的缓存中获取的,而不是从远程服务器获取,通过减少对主要数据源的请求次数降低服务器负载,总体更快的加载时间会带来更好的用户体验,现在让我们谈谈代理服务器。
代理服务器 (正向和反向)
负载均衡器设计
数据库设计
第一种类型是关系型数据库, 将关系型数据库视为一个组织有序的文件柜,所有文件都整齐地分类到不同的抽屉和文件夹中。一些流行的SQL数据库示例是PostgreSQL、MySQL和SQLite,所有的SQL数据库都使用表来存储数据,并使用SQL作为查询语言。它们非常适用于事务、复杂查询和完整性,关系型数据库也符合ACD属性,A代表原子性,意味着交易是全有或全无的,C代表一致性,意味着在交易之后,数据库应该处于一致的状态,I代表隔离性,意味着交易应该是独立的,D代表持久性,意味着一旦交易提交,数据将会存留。
我们还有NoSQL数据库, 它取消了一致性属性,想象一个NoSQL数据库就像是一个粘贴便签的头脑风暴板,你可以以任何形状或形式添加或删除便签,它很灵活。 流行的例子包括mongodb、Cassandra和Redis,还有不同类型的NoSQL数据库,例如键值对数据库、文档型数据库或基于图的数据库,NoSQL数据库是无模式的,意味着它们在表之间没有外键,用来将数据链接在一起,它们非常适用于非结构化数据,理想的可扩展性、快速迭代和简单查询。
还有内存数据库, 这就像拥有一个白板用于快速计算和临时素描,因为所有的东西都在内存中,所以速度很快。一些例子包括Redis和Mcache,它们具有快速的数据检索,主要用于缓存和会话存储。现在让我们看看如何扩展数据库,第一个选项是垂直扩展或横向扩展,在垂直扩展中,通过增强运行数据的单个服务器的能力来提高数据库的性能,这可能涉及增加CPU 力量,增加更多的RAM,添加更快或更多的磁盘存储,或升级网络,但对单台机器能够添加的资源有一个最大限制,所以非常有限。
下一项是水平扩展或横向扩展, 这涉及向现有资源池中添加更多的机器,而不是升级单个单元数据库,支持水平扩展的数据库将数据分布到一组机器的集群中,这可能涉及数据库分片或数据复制,第一个选项是数据库分片,这是将数据集的不同部分分布到多台服务器上,这意味着你将数据分割成更小的块,并分配到多台服务器上。
一些分片策略包括基于范围的分片,根据给定键的范围分布数据,基于目录的分片,利用查找服务将流量指向正确的数据库,我们还有地理分片,根据地理位置拆分数据库,下一个横向扩展选项是数据复制,这是在多台服务器上保留数据副本以获得高可用性,你有主从复制,其中你有一个主数据库和若干只读从数据库,或者你可以有主主应用程序,多个可以同时读写的数据库。
- 扩展你的数据库是一回事,但你也希望更快地访问它,所以让我们谈谈不同的性能技巧 可以帮助你更快地访问数据,最显而易见的就是缓存,缓存不仅仅适用于Web服务器,数据库缓存可以通过内存数据库如Redis来实现,你可以用它来缓存频繁的查询并提高性能 下一个技巧是索引,索引是提高数据库性能的另一种方式,为频繁访问的列创建索引将显著加快检索时间, 下一个技巧是查询优化,你也可以考虑优化查询以实现快速数据访问,包括最小化连接和使用SQL查询分析器或解释器来了解你的查询的性能, 在所有情况下,你都应记住CAP定理,它规定在设计系统时只能选择这三个中的两个:一致性、可用性和分区容忍性, 你应根据面试给出的要求,优先选择其中两个。