【微服务】详解弹性设计中各个模式的应用场景
@TOC
推荐超级课程:
迁移不同架构不会完全解决所有容错问题。
分布式计算架构只有在具有弹性及其模式作为构建本质时才可以被称为容错性。我们需要注意到分布式组件可能会发生故障,但在这种情况下,我们将预测故障并对其进行管理。
从概念上讲,容忍性假设故障随时可能发生,因此我们必须做好应对任何场景的准备。例如:
- 服务A与服务B通信仅通过服务C的响应工作。
- 如果我们让服务B停止会发生什么?
- 在这种情况下,我们将有什么备用处理计划?
- 使用默认值?
- 从服务C请求信息?
- 返回错误消息?
通过应用弹性及其模式,您可能会了解如何在构思和构建产品时以最佳方式行事,这比在生产环境中产生危机更为有利。
故障和微服务架构
在从单体架构迁移到微服务之后,您会注意到需要管理的组件数量。所有这些相互连接的组件构成了解决方案。将此架构与交响乐团进行比较是一个很好的方法。为了让交响乐团走向观众的狂喜,指挥家指挥着整个交响乐团,弹性将成为我们交响乐团的指挥,促使我们探讨容限和对待故障作为场景而不是危机。为了使我们的产品表现出色,组件不能出现故障并影响所有我们的工程和架构。
如何构建具有弹性的服务?
场景识别
在交付到生产之前,我们进行集成、性能、单元和架构测试。所有这些测试对于函数的完美运行及代码质量至关重要,但它们并不能减少故障场景。在设计解决方案时,映射故障场景是一项重要任务。架构师和工程师需要密切合作,为业务找到最佳解决方案。在决定如何处理故障时,业务需要全力以赴。诸如:
- 我们能够映射我们的风险和危机场景吗?
- 我们有能力支持整个服务层中的故障吗?
- 如何快速从故障中恢复并造成最小影响?
在敏捷方法论团队中将容限作为核心的最佳情景是:解决方案架构师、技术领导和产品所有者需要在细化时有一个错误映射过程,以便每个功能都有其风险地图和包含所有功能细节和解决方案决策的文档。这需要记录并提供给工程团队在构建时查阅。
一旦我们拥有故障缓解计划和自动恢复计划,我们将引入一种广为人知的概念,即混沌工程。在这种情况下,我们将测试并创造工程情况,我们将在这些情景中跟踪一组服务的行为以及容错性在以下情景中的表现:
- 服务A无法与其他服务通信
- 数据库不可用
- 服务器无法通信
- 响应时间过长
- 一般集成故障
减少级联故障
服务之间的依赖性使解决方案容易发生级联错误,一个小问题可能成为一个大问题。为了不传播错误,需要确保这种类型的故障不会发生,以更好地管理网络资源。使用线程是网络的一种好方法,但一个重要的点是失败的服务会自动恢复。
减少单点故障
在设计某物的瞬间,我们需要讨论容错性,确保我们设计的东西不依赖于单个组件。解决方案需要设计得不依赖于单点故障,处理请求是技术挑战,确保整个生态系统的可用性至关重要。除了映射外,恢复策略对于成功至关重要。
我们的口号是容忍故障,错误和异常发生时,我们需要以最佳方式处理它们,为业务创建消息或默认值,这是我们可以采用的解决方案,使故障和优雅并存。
隔舱模式
隔舱模式是一种容错的应用程序设计类型。在隔舱架构中,应用程序的元素被隔离到池中,以便如果一个失败,其他元素仍然可以继续运行。这种模式以船舱分隔(隔舱)命名,类似于船体分隔的隔舱。如果船体的舱室被破坏,只有损坏的区域会充满水,从而防止船只沉没。
根据使用者负载和可用性要求,将服务实例分区成不同的组。这种设计有助于隔离故障,并允许您在发生故障时为一些使用者继续提供服务功能,即使故障发生。一个使用者也可以分区资源,以确保用于调用一个服务的资源不会影响用于调用另一个服务的资源。例如,调用多个服务的使用者可以为每个服务分配一个连接池。如果一个服务开始失败,它只会影响为该服务分配的连接池,允许使用者继续使用其他服务。这种模式的优点包括:
- 将使用者和服务隔离出级联故障。影响使用者或服务的问题可以在其自己的隔舱中隔离,防止整个解决方案失败。
- 允许您在服务失败时保留一些功能。应用程序的其他服务和功能将继续工作。
- 允许您部署为消耗应用程序提供不同服务质量的服务。高优先级消费者池可以配置为使用高优先级服务。
何时使用此模式
使用此模式来实现以下目的:
- 隔离用于消费一组后端服务的资源,特别是如果应用程序在一个服务未响应时仍然能够提供一定水平的功能。
- 将关键消费者与标准消费者隔离。
- 保护应用程序免受级联故障的影响。
此模式可能不适用于以下情况:
- 项目中可能不接受资源使用效率低。
- 不需要额外的复杂性。
断路器模式
非常适用于防止可能导致整个应用程序出现级联故障的服务故障。微服务必须通过类似于电气断路器的代理对另一个微服务发起请求。代理应计算发生的最近故障数量,用于决定是否允许操作继续进行,或者立即返回异常。
断路器有三种状态:关闭,打开和半开
关闭
断路器将请求转发给微服务,并在一定时间内计算故障次数。如果在一定时间内的故障次数超过阈值,断路器会跳闸并进入打开状态。
打开
微服务请求立即失败,并返回异常。超时后,断路器进入半开状态。
半开
仅允许有限数量的微服务请求通过并调用操作。如果这些请求成功,断路器将进入关闭状态。如果任何请求失败,断路器将进入打开状态。
优势
- 提高微服务架构的容错性和弹性
- 阻止故障级联到其他微服务。
劣势
- 需要复杂的异常处理。
何时使用此模式
- 将大型后端单体应用程序逐步迁移到微服务。
- 在大型公司中,API网关是强制性的,以集中跨项目和安全问题。
不适用断路器的情况
- 错综复杂,事件驱动的微服务架构。
速率限制器模式
限速可以通过减少一段时间内发送到服务的记录数量来减少您的流量,并可能通过优化在指定时间段内发送到服务的操作的数量和/或大小的方式来提高吞吐量。
服务可能会基于时间的不同度量标准对流量进行节流,例如:
- 操作数量(例如,每秒20次请求)。
- 数据量(例如,每分钟2 GiB)。
- 操作的相对成本(例如,每秒20,000流RUs)。
无论选择哪种度量标准对流量进行节流,您的限流实现都将涉及控制在特定时间段内发送到服务的操作的数量和/或大小,优化对服务的使用同时不超过其限流容量。
如果您的API可以处理请求的速度比任何限速的接受服务允许时,您将需要管理您可以快速使用服务的速度。但只将限速视为数据传输速率不匹配的问题,并简单地缓冲您的摄取请求直到被限速的服务可以跟上,是有风险的。如果在这种情况下您的应用程序崩溃,您可能会冒着丢失任何已缓冲数据的风险。
为了避免这种风险,请考虑将记录发送到可以处理您的全部摄取速率的持久性消息系统。 (例如,Azure事件中心可以处理每秒数百万次操作)。然后,您可以使用一个或多个作业处理器按照能够在给定时间间隔内处理的速率控制读取消息系统的记录。将记录发送到消息系统可以通过只允许在给定时间间隔内处理的记录来减少内部内存使用。
何时使用此模式
- 减少由受限制摄取服务引起的限流错误。
- 与对错误的简单重试方法相比,减少流量。
- 通过在容量处理任何受限制的摄取服务不被允许的摄取服务之间缓冲请求来减少内存消耗。
重试模式
通过透明重试失败操作,使应用程序能够处理在尝试连接到服务或网络资源时发生的瞬时故障,可以提高应用程序的稳定性。
在云中,瞬时故障并不罕见,应用程序应设计为优雅和透明地处理这些故障。这最小化了故障对应用程序正在执行的业务任务的影响。
如果应用程序在尝试将请求发送到远程服务时检测到故障,它可以使用以下策略来处理故障:
- 取消。 如果故障表明故障不是瞬时的或重复可能性不太成功,则应用程序应取消操作并报告异常。例如,由于提供无效凭证导致的身份验证失败不太可能成功,无论尝试多少次。
- 重试。 如果报告的特定故障不寻常或罕见,它可能是由于不寻常的情况导致的,例如在传输期间网络数据包被破坏。在这种情况下,应用程序可能会立即重试失败的请求,因为同样的故障不太可能重复,并且请求很可能会成功。
- 延迟后重试。 如果故障是由更常见的连接或繁忙故障之一引起的,网络或服务可能需要一段时间来纠正连接问题或清理工作积压。应用程序应在重试请求之前等待适当的时间。
何时使用此模式
当应用程序在与远程服务交互或访问远程资源时可能经历瞬时故障时,使用此模式。这些故障预计会是短暂的,重复之前失败的请求可能会在后续尝试中成功。
本模式可能不适用于:
- 故障可能持续很长时间的情况,因为这可能会影响应用程序的响应性。应用程序可能会浪费时间和资源尝试重复预计会失败的请求。
- 处理不是由瞬时故障引起的故障,比如由应用程序中业务逻辑错误引起的内部异常。
- 作为系统扩展性问题的替代方案。如果应用程序经常遇到忙碌故障,经常是表明所访问的服务或资源应该进行扩展。
超时模式
在微服务架构中,一个服务(A)依赖于另一个服务(B),有时由于某些网络问题,服务B可能无法按预期回应。这种延迟可能会影响到服务A,因为A正在等待B的响应以进一步进行。由于这种问题并不罕见,因此在设计您的微服务时最好考虑到这种服务不可用的问题。
超时模式非常直观且许多HTTP客户端都配置了默认超时时间。其目标是避免对响应无限等待时间,并因此在超时内未收到响应的情况下视每个请求为失败。
几乎每个应用程序都使用超时来避免请求永远被卡住。然而,处理超时并不容易。想象一下在网上商店中下订单时出现超时的情况。如果订单创建仍在进行中,或者请求从未被处理,您无法确定订单是否已成功下达但响应超时了。如果将超时与重试结合使用,您可能会出现重复订单。如果将订单标记为失败,客户可能会认为订单未成功,但实际上可能成功了并且会被收费。
使用该模式的优势
- 当依赖的服务不可用时,使核心服务始终工作
- 我们不希望无限等待
- 我们不希望阻塞任何线程
- 处理与网络相关的问题,并使用一些缓存响应使系统保持功能。
Implementing Resilience Patterns
Now that we have defined the concepts of fault tolerance in agile projects, recovery and resilience, we will implement the main resilience patterns. We will go in a conceptual approach with drawings that illustrate the situations, we will make available a project on GitHub that implements all the mentioned patterns so that in addition to the concept we have construction examples. The patterns that will be implemented are:
Repository with all patterns mentioned
All projects will be built with the following technologies:
- Java 17
- Spring Boot
- Lombok
- Spring Boot Actuator