Java教程

提高应用程序可用性:冗余和持久性

本文主要是介绍提高应用程序可用性:冗余和持久性,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

继续我们关于应用可用性的旅程,让我们扩展我们在本系列第一部分中开始的内容。在我们之前的 article 中,我们探讨了可用性、著名的五个 9s 以及为什么根据其组成部分的重要性来划分应用是很重要的。

我们还简要讨论了冗余作为提高应用程序可用性的第一步。让我们从这里继续,首先聚焦基本原理,然后介绍可以使用的具体技术示例。

系列结构是:

  • 可用性的基础
  • 冗余和持久性 (本文)
  • 优雅降级和异步处理
  • 灾难恢复
  • 主动-主动模式
冗余

最简单的情况下,可以通过拥有一个或多个你试图使用的资源的实例来实现冗余,无论是计算资源、持久化资源还是两者之间的任何资源。如果某个资源不再可用,你可以快速将流量转移到其任何冗余的实例上。

下面的例子说明了有两个网络服务器,一个主服务器及其副本。最初,流量仅定向到主服务器。一旦检测到故障,流量将被转移到副本服务器。

图1. 通过将流量路由到工作服务器实现的基本冗余。

一种常见的解决方案被称为反向代理或负载均衡器。

图2. 使用负载均衡器在服务器之间分配流量。

客户端最终将请求发送到代理/负载均衡器的IP地址,该代理/负载均衡器维护了该特定流量模式下可用服务器的列表,并将流量导向其中一台服务器。

需要注意的是,尽管可用性和可扩展性是两个不同的主题,但可用性解决方案通常作为可扩展性方案的一部分被使用。例如,在使用负载均衡器时,流量会被发送到所有可用的服务器,而没有主服务器的概念。

常见的实现可以配置为定期访问服务器上的健康端点,以帮助确定当某个服务器不再可用时,并相应地动态更新列表。

图3. 负载均衡器中可用/健康的服务器的动态更新。

如果你正在使用 Kubernetes,当 pods 创建或销毁时,此过程也会发生,并且请求会被路由到健康的 pods 中。

图4。类似的健康路由在K8S中也会发生。

值得一提的是,拥有冗余资源并非没有问题或成本。拥有应用程序副本的主要挑战之一是确保无论使用哪个实例,行为都能保持一致。

例如,想象一下你向一个 web 服务器发送了一个请求,它处理并返回了一个响应,然后在回复之后不久就宕机了。你接下来的请求会被路由到一个新的实例上。新的实例会像原来的那样工作吗?

图5. 如果后续请求依赖于谁为其服务,我们将陷入困境。

这在你的应用程序没有持久化并且整个上下文都可以通过后续请求提供的情况下要简单得多。在这种情况下,你的主要关注点是确保在部署应用程序时,所有副本都能接收相同版本的请求,以保证版本之间的向后兼容性。

图6. 当存在副本时,确保副本使用相同版本的应用程序是一个挑战/需求。

重要的是要强调,即使您的应用程序没有外部持久化,它也可能使用短暂的本地存储(如内存)来存储与请求相关的上下文。

图7. 粘滞会话对冗余性构成问题。

依赖这些请求“会话”的应用程序容易出现问题,因为在失败的情况下,存储在这些会话中的任何信息在其他实例中都是不可用的。

因此,无状态服务通常更容易提供冗余。无论是新的服务器、虚拟机、容器、Pod 还是函数即服务,启动一个新的实例,将其添加到路由负载均衡器中,即可完成。

但是当需要持久化状态时又该怎么办呢?

持久化

大多数应用程序由两个主要组件组成:

  • 定义要执行逻辑的代码
  • 代码运行时使用的、收集的或操作的数据

在大多数应用程序中,代码更改的频率远低于数据更改的频率。这就是为什么持久性的冗余有自己的独特需求和解决方案。

图8. 只需一次写操作,副本就会产生分歧!

在第一次修改之后,我们的冗余服务器就过时了。在示例中,次要请求将无法找到已修改的信息,因为这些信息仅存在于原始服务器中。

为了应对这一点,我们需要确保信息能够提供给所有持久化服务器。

图9。解决方案是将更新从一个服务器复制到其他服务器。

大多数持久化解决方案会以两种形式之一实现此同步:同步异步

在同步版本中,只有当数据被保存到 N 个服务器中时(其中 1 < N <= 总服务器数),数据才被认为已经保存。

图10. 同步仅在信息已被其他地方持久化后才认为成功。

这通常是复杂度最高的方法,因为它需要解决诸如第一个写操作成功但第二个失败这样的问题。我是否应该撤销第一个操作,还是尝试再次执行(或者用一个新的服务器替换失败的服务器)?

此外,由于两台服务器之间的非零延迟,随着需要确认的成功副本数量的增加,整体延迟也会增加。

在异步版本中,当数据被保存到接收请求的服务器时,就认为数据已被保存。然后异步地复制到其他服务器。

图11. 异步返回立即将信息保存到本地后立即返回。

虽然这种方法可能比同步版本简单得多,但它有一些需要考虑的因素:

  • 可能会丢失数据

由于复制是异步进行的,如果第一个服务器在新更改传播之前出现问题,任何被提升以接收新请求的剩余服务器将不会包含这些更改。

虽然不理想,但对于某些应用程序来说,这可能更容易接受,而对于其他应用程序来说,这可能是一个重大问题。

图12. 如果原始服务器在复制之前就失效了,我们可能会丢失数据。

  • 如果您在副本之间分配读取操作,可能会提供过时的数据

一种常见的可扩展性策略是将读取请求在服务器之间分配,以避免写入服务器过载。然而,这样做可能会导致写入与传播之间的延迟,从而引发读取后写入的问题:即应用程序修改了一个实体的状态,然后读取该实体以执行额外的操作。

图13. 由于信息需要时间才能反映到所有服务器上,因此最终是一致的。

让我们来看看AWS提供的常见持久性解决方案的一些特性。

AWS 解决方案

AWS 提供了一系列持久性服务。我将介绍其中一部分可用的服务,并重点讨论它们的冗余方面。

随着我们在可用性之旅中不断前进,其中一些内容将在未来的文章中从不同角度重新讨论。

可用区

“可用性区域”这个术语会经常出现,所以我们先给出一个简单的定义。AWS 基础设施被划分为区域,每个区域覆盖特定的地理范围。每个区域又被细分为可用性区域(AZ),每个可用性区域由一组地理位置分散的数据中心组成,这些数据中心相互连接以保证最小的延迟。

你可以在这里了解更多关于区域和可用区的内容here,我们将重点关注的关键点是:

  • 它们是独立的,这意味着一个可用区(AZ)中的任何故障都不会影响其他可用区。
  • 它们在物理上是分离的,减少了同时影响所有区域的重大问题的可能性。
  • 它们彼此距离足够近,可以实现它们之间的低延迟通信,这对大多数应用程序来说可以忽略不计。
关系型数据库服务 (RDS)

使用 RDS,您可以选择所需的数据库引擎,包括 MySQL、PostgreSQL、Microsoft SQL Server 和 Oracle 等选项,并且可以选择所需的计算能力(CPU、内存、I/O)和存储。这里无法涵盖每个引擎的所有选项,因此以下示例将使用 PostgreSQL 作为引擎。

使用 RDS,您可以选择最多两个位于不同可用区的副本,提供一个主实例(写入器)和其他两个备用实例(读取器)。

主实例和备用实例之间的通信是同步进行的,以确保不丢失任何数据。

你有一个特定的读取端点,可以帮助减少读取延迟,同时将写入操作定向到主实例。如果主实例不再适合接收写入操作,将会发生故障转移,其中一个备用实例将在35至60秒内被提升为新的主实例。在此期间,写入尝试预计会失败。

通过选择这种方法,您可以实现所需的冗余,预期的正常运行时间为99.95%。

Aurora

除了 RDS 之外,Aurora 还是另一个提供一些有趣功能的服务:

  • 它将 Multi-AZ 副本从 2 个扩展到最多 15 个。
  • 即使您没有任何读取副本,它也会自动将数据跨同一区域内的六个存储节点(位于不同的 AZ 中)复制。

拥有更多的副本可以提高读取容量,但也会在将读取副本提升为新主实例时提供更多选择。主实例和副本之间的延迟通常小于100毫秒,这意味着在写入后仍然有可能读取到过时的数据。

即使你决定不使用副本,将数据自动存储在不同的可用区(AZ)中这一事实也有助于防止数据丢失,尽管这会以较长的恢复时间作为代价,因为在现有主实例发生故障时,需要重新创建一个新的主实例。

DynamoDB

与传统的基于 SQL 的选项不同,DynamoDB 提供了一种持久化模型,其中信息分布在分区中,并采用双一致性方法。

一个写操作首先将更新的数据保存到一个持久节点。然后同步复制到另一个持久节点。只有在这个时候,操作才会被确认给调用者。

有一个异步过程将它从第二个持久化节点复制到第三个节点。

图14. DynamoDB中同步+异步复制的混合模式。

这意味着你的数据会被持久化到3个节点,每个节点位于不同的可用区。同时,你不需要等待所有3个节点都保存完毕后再返回操作,这有助于将延迟保持在较低水平。

在检索数据时,你有两个选择:最终一致性和强一致性。

如果你选择最终一致性的模式,你的操作将会被定向到三个节点中的任意一个。如果碰巧是异步复制的那个节点,那么你获取的信息可能会比主节点上的信息过时。

相比之下,强一致性模式只会指向主节点。

情节变复杂了

高可用性的一个关键在于冗余。拥有多个能够处理我们请求的资源,使我们能够在主要资源发生故障时有备选方案。

这意味着为应用程序的计算和持久化方面增加冗余。处理计算方面的一个常见方法是使用某种负载均衡器或反向代理,这些工具可以隐藏真实的服务器,从客户端的角度来看,并处理将流量重定向到正确服务器的任务。

如果您的应用程序是无状态的,这会更容易,因为后续请求可以由不同的实例处理,而这些请求之间是独立的。

另一方面,持久化本质上更为复杂,因为它存储的数据会随时间发生变化,并且请求 N 会影响请求 N+1。

在 AWS 方面,许多托管解决方案提供了处理冗余的设施,这些设施在后台处理冗余问题。在这里,我们首先遇到了在拥有多个地理上分散的副本的安全性和最终一致性或由于同步需求导致的更高延迟之间的权衡。

但我们的探索还没有结束。在下一篇文章中,我们将讨论更多您可以采用的实践,以提高应用程序的可用性,然后再深入探讨诸如主动-主动多区域架构等更昂贵的解决方案。

编辑评论由 Catherine Heim 和 Sam-Nicolai Johnston 撰写。

想加入我们吗?点击 这里 查看 SSENSE 所有开放的职位!

这篇关于提高应用程序可用性:冗余和持久性的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!