电子说
我们生活在一个时代,大规模的,面向互联网的系统(例如Google,Amazon,Facebook等)是工程界的偶像。 他们每秒处理大量请求,并管理规模空前的数据存储库。 出于商业秘密的原因,在这些系统上获取准确的流量数字并不容易。 但是,这些站点随着使用量的增长而迅速扩展的能力是其持续成功的关键因素。
幸运的是,我们可以通过一家技术公司的年度使用情况报告深入了解以Internet规模处理的请求和数据量。 从2019年起,您可以在此浏览其令人难以置信的详细使用情况统计信息。这是对大规模系统功能的令人着迷的一瞥。 不过请注意,这是Pornhub.com。 该报告不适合该报告。 以下是PG-13的一个示例性数据点-他们在2019年有420亿的访问量!
对于绝大多数企业和政府系统而言,可扩展性并不是开发和部署初期的主要质量要求。 增强可用性和实用性的新功能是我们开发周期的驱动力。 只要正常负载下的性能足够,我们就会继续添加面向用户的功能,以提高系统的业务价值。
尽管如此,系统发展成为一种状态,在这种状态下,增强的性能和可伸缩性变得迫在眉睫,甚至是生存,这并不少见。 引人注目的功能和高实用性催生了成功,这带来了更多的处理请求和更多的数据管理。 这通常预示着一个临界点,在轻载下有意义的设计决策现在突然变成了技术负担。 外部触发事件通常会导致这些转折点-在2020年3月的媒体上,许多报道称政府失业和超市在线订购网站由于冠状病毒大流行而在需求下崩溃。
当达到这个临界点时,架构师有责任带领系统演进为高响应,可扩展的系统。 核心系统的架构机制和模式将需要重新设计,以使其能够应对不断增长的需求。 对于许多架构师而言,这是未知的或陌生的领域,因为可伸缩性使我们走下了有时对于广泛理解的软件体系结构原理而言异端的道路。
以下六个经验法则代表着每位软件架构师必须帮助他们构建可伸缩系统的知识。 这些通用规则可以为架构师在可靠地处理不断增长的请求负载和数据量的过程中提供指导。
成本和可扩展性有着不可磨灭的联系
扩展系统的核心原则是能够轻松添加新的处理资源来处理增加的负载。 对于许多系统,一种简单有效的方法是部署多个无状态服务器资源实例,并使用负载平衡器在这些实例之间分配请求(请参见图1)。 假设将这些资源部署在Amazon Web Services之类的云平台上,则基本费用为:
· 每个服务器实例的虚拟机部署成本
· 负载均衡器的成本,取决于新请求和活动请求的数量以及处理的数据量
在这种情况下,随着请求负载的增长,就需要运行服务器代码的已部署虚拟机具有更大的处理能力。 这导致更高的成本。 您的负载均衡器成本还将与您的请求负载和数据大小成比例地增长。
> Figure 1 A Simple Load Balancing Example
因此,成本和规模是并存的。 您关于可伸缩性的设计决策不可避免地会影响您的部署成本。 忘记了这一点,您可能会发现自己以及许多知名公司在一个月底收到了意外大笔的部署费用!
在这种情况下,您的设计如何降低成本? 主要有两种方法:
· 使用弹性负载平衡器可根据瞬时请求负载来调整服务器实例的数量。 然后,在交通繁忙期间,您需要为最少数量的服务器实例付费。 随着请求量的增长,负载均衡器会产生新的实例,并且容量也会相应地增长。
· 增加每个服务器实例的容量。 这通常是通过调整服务器部署参数(例如线程数,连接数,堆大小等)来完成的。 默认平台设置很少适合您的工作负载。 仔细选择参数设置可以显着提高性能,从而提高容量。 您基本上是在使用相同的资源来完成更多工作-这是实现扩展的关键原则。
您的系统存在瓶颈。 某处!
扩展系统实质上意味着增加其容量。 在上面的示例中,我们通过在负载平衡的计算资源上部署更多服务器实例来提高请求处理能力。 但是,软件系统包括多个从属处理元素或微服务。 不可避免地,随着某些微服务中容量的增加,您将淹没其他微服务中的容量。
在我们的负载均衡示例中,假设我们的服务器实例均具有到同一共享数据库的连接。 随着部署服务器数量的增加,我们增加了数据库上的请求负载(请参见图2)。 在某个阶段,此数据库将达到饱和,并且数据库访问将开始引起更多的延迟。 现在,您的数据库已成为瓶颈,无论您增加更多的服务器处理能力都无所谓。 为了进一步扩展,解决方案是以某种方式增加数据库容量。 您可以尝试优化查询,或添加更多的CPU和/或内存。 也许复制和/或分片数据库。 有很多可能性。
> Figure 2 Increasing Server Capacity creates a Bottleneck at the Database
系统中的任何共享资源都可能成为限制容量的瓶颈。 当您增加部分架构的容量时,您需要仔细查看下游的容量,以确保不会因请求突然而意外地淹没您的系统。 这会迅速导致级联故障(请参阅下一条规则),并使整个系统崩溃。
数据库,消息队列,长时间等待的网络连接,线程和连接池以及共享的微服务都是瓶颈的主要候选者。 您可以放心,高流量负载将迅速暴露这些元素。 关键是防止瓶颈暴露时突然崩溃,并能够快速部署更多容量。
慢服务比失败服务更邪恶
在正常操作下,系统应设计为在微服务和组成系统的数据库之间提供稳定,低延迟的通信。 尽管系统负载保持在您设计的运行配置文件之内,但性能仍然是可预测的,一致且快速的,如图3所示。
> Figure 3 Low latencies under normal load
随着客户端负载增加到超出操作配置文件的范围,微服务之间的请求等待时间将开始增加。 首先,这通常是一个缓慢而稳定的增长,可能不会严重影响整个系统的运行,尤其是在负载激增短暂的情况下。 但是,如果传入请求负载继续超过容量(服务B),则未完成的请求将开始备份在发出请求的微服务(服务A)中,由于下游等待时间较慢,该服务现在看到的传入请求多于已完成的请求。 如图4所示。
> Figure 4 Increased load causes longer latencies and requests to back up
在这种情况下,事情可能真的会迅速消退。 当一项服务不堪重负,并且由于颠簸或资源耗尽而基本停止运行时,请求的服务将无法响应其客户,而客户也将停止运行。 导致的结果称为级联故障-缓慢的微服务导致请求沿着请求处理路径建立,直到整个系统突然出现故障。
这就是为什么缓慢的服务是邪恶的,而不是不可用的服务更是如此。 如果您呼叫失败的服务或由于瞬态网络问题而进行了分区的服务,则您会立即收到异常消息,并可以明智地决定做什么(例如退避并重试,报告错误)。 逐渐不堪重负的服务正常运行,只是延迟时间更长。 这暴露了所有依赖服务中的潜在瓶颈,最终,出了一些大问题。
断路器和隔板等架构模式可防止级联故障。 如果对服务的延迟超过指定值,则断路器可以限制请求负载或释放请求负载。 如果只有一个下游服务依赖项失败,则隔板可以保护整个微服务免于失败。 使用它们来构建弹性以及高度可扩展的体系结构。
数据层最难扩展
核心业务数据示例是客户资料,交易和帐户余额。 这些必须正确,一致且可用。
操作数据示例包括用户会话时长,每小时访问者和页面浏览量。 该数据通常具有保存期限,并且可以随时间进行汇总和汇总。 如果还不是100%完成,那就不是世界末日。 因此,我们可以更轻松地捕获和存储带外操作数据,例如将其写入日志文件或消息队列。 消费者然后定期检索数据并将其写入数据存储。
随着系统请求处理层的扩展,共享事务数据库将承受更多负载。 随着查询负载的增加,这些可能迅速成为瓶颈。 查询优化是一个有用的第一步,添加更多内存以使数据库引擎能够缓存索引和表数据也是如此。 最终,您的数据库引擎将耗尽所有精力,并且需要进行更根本的更改。
首先要注意的是,任何数据组织的更改都可能在数据层带来痛苦。 如果更改关系数据库中的架构,则可能必须运行脚本以重新加载数据以匹配新架构。 在脚本运行期间(对于大型数据库而言可能是很长的时间),系统无法进行写入。 这可能不会使您的客户满意。
NoSQL,无模式数据库减轻了重新加载数据库的需要,但是您仍然必须更改查询级代码才能识别已修改的数据组织。 如果您拥有业务数据集合,其中某些项目的格式已修改,而某些项目的原始格式已修改,则可能还需要管理数据对象的版本控制。
进一步扩展可能需要分发数据库。 带有只读副本的领导者追随者模型可能就足够了。 对于大多数数据库来说,这很容易设置,但是需要密切的持续监视。 领导者死亡时,故障转移很少是瞬时的,有时需要手动干预。 这些问题都是非常依赖数据库引擎的。
如果采用无领导者方法,则必须决定如何最好地在多个节点之间分配和分区数据。 在大多数数据库中,一旦选择了分区键,就无法在不重建数据库的情况下进行更改。 不用说,分区键需要明智地选择! 分区键还控制如何在节点之间分配数据。 随着添加节点以提供更大的容量,需要进行重新平衡以在新节点上分布数据和请求。 同样,数据库文档的内容中描述了它的工作方式。 有时它并不像它可能的那样平滑。
由于分布式数据库的潜在管理难题,基于托管的基于云的替代方案(例如AWS Dynamodb,Google Firestore)通常是首选。 当然,需要权衡取舍-这是另一个故事的主题!
这里的信息很简单。 更改逻辑和物理数据模型以扩展查询处理能力很少是一个平稳而简单的过程。 想要不经常面对的人。
快取! 快取! 并缓存更多!
减轻数据库负载的一种方法是避免尽可能访问它。 这就是缓存的用处。您友好的数据库引擎应该能够充分利用节点缓存上的资源。 这是一个简单且有用的解决方案,如果可能会很昂贵。
更好的是,如果不需要,为什么还要查询数据库? 对于经常读取且很少更改的数据,可以修改您的处理逻辑以首先检查分布式缓存,例如memcached服务器。 这需要远程调用,但是如果您需要的数据在高速缓存中,则在快速网络上,这比查询数据库实例要便宜得多。
引入缓存层需要修改您的处理逻辑,以检查缓存数据。 如果所需的内容不在高速缓存中,则您的代码仍必须查询数据库,然后将结果加载到高速缓存中并将其返回给调用方。 您还需要决定何时删除或使缓存的结果无效-这取决于您的应用程序向客户提供陈旧结果的容忍度。
设计良好的缓存方案对于扩展系统绝对是无价的。 如果您可以处理来自缓存的大部分读取请求,则可以在数据库上购买额外的容量,因为它们不参与处理大多数请求。 这意味着您可以避免复杂且痛苦的数据层修改(请参阅上一条规则),同时为越来越多的请求创建容量。 这是使大家开心的秘诀! 甚至会计师。
监控是可伸缩系统的基础
这是很多工作。 而且很难使所有事物都代表现实,因此您可以获得有意义的结果。 毫不奇怪,它很少完成。
替代方法是监视。 对系统的简单监视涉及确保基础结构正常运行。 如果资源不足,例如内存或磁盘空间不足,或者远程调用失败,则应向您发出警报,以便在真正的不良情况发生之前可以采取补救措施。
以上监视见解是必要的,但不足。 随着系统的扩展,您需要了解应用程序行为之间的关系。 一个简单的示例是,随着并发写入请求数量的增加,数据库的写入性能如何。 您还想知道何时由于下游延迟增加而使断路器在微服务中跳闸,负载平衡器何时开始产生新实例或消息在队列中保留的时间超过指定的阈值。
有许多可用于监视的解决方案。 Splunk是一个全面而强大的日志聚合框架。 任何云都有其自己的监视框架,例如AWS Cloudwatch。 这些功能使您可以捕获有关系统行为的指标,并将其显示在统一的仪表板中,以支持对性能的监视和分析。 术语"可观察性"通常用于涵盖监视和性能分析的整个过程。
有两件事(至少!)要考虑。
首先,要获得对性能的深入了解,您将需要生成与应用程序行为细节相关的自定义指标。 仔细设计这些指标,并在您的微服务中添加代码以将其注入到您的监视框架中,以便可以在系统仪表板中对其进行观察和分析。
其次,监视是系统中的必要功能(和成本)。 它已打开。 总是。 当您需要调整性能和扩展系统时,捕获的数据将指导您的实验和工作。 在系统演进中以数据为驱动力有助于确保您花费时间修改和改进系统部分,这些部分对于支持性能和扩展要求至关重要。
结论
高性能和可伸缩性通常不是我们构建的许多系统的优先质量要求。 通常,了解,实施和发展功能需求是很成问题的,以致浪费所有可用的时间和预算。 但是有时,在外部事件或意外成功的驱动下,可伸缩性是必要的,否则您的系统将因负载不足而崩溃。 不可用的系统(或由于性能降低而实际上不可用)对任何人都没有用。
全部0条评论
快来发表一下你的评论吧 !