在云中构建可靠的应用程序不同于传统的本地应用程序开发。 虽然过去购买了更高端的硬件来进行纵向扩展,但在云环境中需要横向扩展。目标是最大程度地降低其影响并使系统保持稳定,而不是尝试防止故障。
也就是说,可靠的云应用程序显示了不同的特征:
- 它们可复原,可从问题中轻松恢复,并继续正常工作。
- 它们高度可用 (HA) 并按设计在正常状态下运行,没有明显的停机时间。
了解这些特性如何协同工作,以及它们如何影响成本,对于构建可靠的云原生应用程序至关重要。 接下来,我们将看看如何利用 Azure 云中的功能,在云原生应用程序中构建复原能力和可用性。
具有复原能力的设计
我们已经说过复原能力使应用程序能够应对故障并仍能正常工作。 白皮书(Azure 中的复原能力白皮书)提供了在 Azure 平台中实现复原能力的指导。 下面是一些关键建议:
硬件故障。 通过在不同的容错域中部署组件,在应用程序中构建冗余。 例如,使用可用性集确保将 Azure VM 放在不同的机架中。
数据中心故障。 通过跨数据中心的容错隔离区域,在应用程序中构建冗余。 例如,通过使用 Azure 可用性区域确保将 Azure VM 放置在不同的故障隔离数据中心。
区域性故障。 将数据和组件复制到另一个区域,以便能够快速恢复应用程序。 例如,使用 Azure Site Recovery 将 Azure VM 复制到另一个 Azure 区域。
重负载。 在实例之间进行负载均衡,以处理高峰使用量。 例如,将两个或更多 Azure VM 放在负载均衡器后面,以将流量分配到所有 VM。
意外的数据删除或损坏。 备份数据,以便在发生任何删除或损坏时可以将其还原。 例如,使用 Azure 备份定期备份 Azure VM。
具有冗余的设计
故障的影响范围有所不同。 硬件故障(例如磁盘故障)可能会影响群集中的单个节点。 网络交换机故障可能会影响整个服务器机架。 不太常见的故障(例如断电)可能会中断整个数据中心。 很少会出现整个区域不可用的情况。
冗余是提供应用程序复原能力的一种方法。 所需的确切冗余级别取决于你的业务需求,并将影响系统的成本和复杂性。 例如,多区域部署比单区域部署更昂贵且管理更复杂。 你将需要操作过程来管理故障转移和故障回复。 对于某些业务方案,额外的成本和复杂性可能是合理的,但对于其他方案则不合理。
若要构建冗余,需要确定应用程序中的关键路径,然后确定路径中每个点是否有冗余? 如果子系统发生故障,应用程序是否会故障转移到其他位置? 最后,你需要清楚地了解内置于 Azure 云平台的功能,你可以利用这些功能满足冗余要求。 下面是用于构建冗余的建议:
部署服务的多个实例。 如果应用程序依赖于服务的单个实例,则会造成单一故障点。 预配多个实例能够提高复原能力和可伸缩性。 在 Azure Kubernetes 服务中托管时,可以在 Kubernetes 清单文件中以声明方式配置冗余实例(副本集)。 可以通过编程方式、在门户中或通过自动缩放功能来管理副本计数值。
利用负载均衡器。 负载均衡将应用程序的请求分散到正常服务实例,并自动从轮换中删除运行不正常的实例。 部署到 Kubernetes 时,可以在“服务”部分的 Kubernetes 清单文件中指定负载均衡。
规划多区域部署。 如果将应用程序部署到单个区域,并且该区域变得不可用,应用程序也会变得不可用。 在应用程序的服务级别协议条款下,这可能是不可接受的。 应转为考虑跨多个区域部署应用程序及其服务。 例如,将 Azure Kubernetes 服务 (AKS) 群集部署到单个区域。 为了保护系统免受区域性故障的危害,你可以将应用程序部署到跨不同区域的多个 AKS 群集,并使用配对区域功能来协调平台更新并设置恢复工作的优先级。
启用异地复制。 Azure SQL 数据库和 Cosmos DB 等服务的异地复制将跨多个区域创建数据的次要副本。 尽管这两种服务都将自动复制同一区域内的数据,但异地复制可让你在故障转移到次要区域时防止出现区域性中断。 异地复制的另一种最佳做法以存储容器映像为中心。 若要在 AKS 中部署服务,需要从存储库中存储和拉取映像。 Azure 容器注册表与 AKS 集成,可以安全地存储容器映像。 若要提高性能和可用性,请考虑将映像异地复制到你拥有 AKS 群集的每个区域中的注册表。 然后,每个 AKS 群集从其区域中的本地容器注册表中提取容器映像,如图 6-4 所示:
图 6-4 。 跨区域复制的资源
- 实现 DNS 流量负载均衡器。Azure 流量管理器通过在 DNS 级别进行负载均衡,为关键应用程序提供高可用性。 它可以根据地理位置、群集响应时间甚至应用程序终结点运行状况,将流量路由到不同区域。 例如,Azure 流量管理器可以将客户定向到最近的 AKS 群集和应用程序实例。 如果在不同的区域中创建了多个 AKS 群集,请使用流量管理器控制如何将流量传送到每个群集中运行的应用程序。 图 6-5 显示了这种方案。
图 6-5 。 AKS 和 Azure 流量管理器
可伸缩性设计
云因缩放经久不衰。 增加/减少系统资源以满足增加/减少系统负载的能力是 Azure 云的关键原则。 但为了有效地缩放应用程序,你需要了解应用程序中包含的每个 Azure 服务的缩放功能。 下面是在系统中有效实施缩放的建议。
针对缩放进行设计。 应用程序必须针对缩放进行设计。 若要开始,服务应是无状态的,以便可以将请求路由到任何实例。 具有无状态服务还意味着添加或删除实例并不会对当前用户造成不利影响。
对工作负荷进行分区。 将域分解为独立的、自包含的微服务,使每个服务能够独立于其他服务进行缩放。 通常,服务具有不同的可伸缩性需求和要求。 利用分区,你只需缩放需要缩放的内容,而无需对整个应用程序进行缩放。
优选横向扩展。基于云的应用程序倾向于横向扩展资源,而不是纵向扩展。 横向扩展(也称为水平缩放)涉及将更多服务资源添加到现有系统,以满足并共享所需的性能级别。 纵向扩展(也称为垂直缩放)涉及使用更强大的硬件(更多磁盘、内存和处理内核)替换现有资源。 可以使用某些 Azure 云资源中提供的自动缩放功能来自动调用横向扩展。 跨多个资源横向扩展还会增加整个系统的冗余性。 最后,纵向扩展单个资源通常比在多个较小的资源上横向扩展更昂贵。 图 6-6 显示了两种方法:
图 6-6 。 纵向扩展和横向扩展
按比例缩放。 缩放服务时,请考虑资源集。 如果要大幅横向扩展特定服务,对后端数据存储、缓存和依赖服务有什么影响? 某些资源(如 Cosmos DB)可以按比例横向扩展,而其他更多的资源则不能。 需要确保不会将资源横向扩展到将耗尽其他关联资源的点。
避免关联。 最佳做法是确保节点不需要本地关联,这通常称为“粘滞会话”。 请求应能够路由到任何实例。 如果需要保存状态,则应将其保存到分布式缓存(如 Azure Redis 缓存)。
充分利用平台自动缩放功能。 尽可能使用内置自动缩放功能,而不是自定义或第三方机制。 尽可能使用计划的缩放规则,确保资源在开始使用时没有延迟,但应根据需要向规则中添加反应性的自动缩放,以应对需求方面的意外变化。 有关详细信息,请参阅自动缩放指南。
主动横向扩展。 最后一种做法是主动横向扩展,以便可以在不丢失业务的情况下快速应对流量的即时峰值。 然后,保守地横向缩减(即删除不需要的实例)以保持系统稳定。 实现此目的的一种简单方法是将冷却时间(即缩放操作之间等待的时间)设置为:5 分钟用于添加资源,最长 15 分钟用于删除实例。
服务中的内置重试
我们建议在前面的部分中实现编程重试操作的最佳做法。 请记住,许多 Azure 服务及其对应的客户端 SDK 还包括重试机制。 以下列表汇总了本书所述的许多 Azure 服务中的重试功能:
Azure Cosmos DB。 客户端 API 中的 DocumentClient 类会自动重试失败的尝试。 重试次数和最长等待时间是可配置的。 客户端 API 引发的异常可能是超过重试策略或非暂时性错误的请求。
Azure Redis 缓存。 Redis StackExchange 客户端使用连接管理器类,其中包含尝试失败时的重试。 重试次数、特定的重试策略和等待时间全都是可配置的。
Azure 服务总线。 服务总线客户端公开了一个 RetryPolicy 类,该类可以使用退避间隔、重试计数和 TerminationTimeBuffer(即指定操作可能需要的最大时间)进行配置。 默认策略是最多 9 次重试尝试,尝试之间有 30 秒的退避期。
Azure SQL 数据库。 使用 Entity Framework Core 库时,将提供重试支持。
Azure 存储。 存储客户端库支持重试操作。 Azure 存储表、blob 和队列的策略各不相同。 同样,启用异地冗余功能时,备用重试会在主存储服务位置和辅助存储服务位置之间切换。
Azure 事件中心。 事件中心客户端库具有 RetryPolicy 属性,该属性包含可配置的指数退避功能。