部署到集群环境
本文档说明了将应用程序部署到集群环境(即应用程序的多个实例同时运行)时应考虑的主题。它解释了如何在您的 ABP 应用程序中处理这些主题。
本文档既适用于单体架构也适用于微服务解决方案。 术语“应用程序”指一个进程。一个应用程序可以是单体 Web 应用程序、微服务解决方案中的服务、控制台应用程序或其他可执行进程。 例如,如果您将应用程序部署到 Kubernetes 并配置您的应用程序或服务在多个 Pod 中运行,那么您的应用程序或服务就在集群环境中运行。
理解集群环境
如果您熟悉集群部署和负载均衡器,可以跳过本节。
单实例部署
考虑一个部署为单实例的应用程序,如下图所示:
浏览器和其他客户端应用程序可以直接向您的应用程序发出 HTTP 请求。 您可以在客户端和您的应用程序之间放置一个 Web 服务器(例如 IIS 或 NGINX),但您仍然只有一个在单个服务器或容器中运行的应用程序实例。 单实例配置受规模限制,因为它运行在单台服务器上,服务器的容量可能会限制应用程序的性能。
集群部署
集群部署是在单台或多台服务器上同时运行您的应用程序的多个实例的方式。 在此架构中,不同的实例可以服务不同的请求,您可以通过向系统添加新服务器来扩展它们。 下图展示了使用负载均衡器进行集群的典型实现:
负载均衡器
负载均衡器具有很多功能,但其根本作用是将传入的 HTTP 请求转发到您的应用程序的一个实例,并将您的响应返回给客户端应用程序。
负载均衡器可以使用不同的算法将请求路由到应用程序实例。轮询是最简单和最常用的算法之一。请求按顺序分发给应用程序实例。第一个实例获得第一个请求;第二个实例获得第二个,依此类推。在所有实例都被使用后,算法会回到第一个实例,并如此迭代直到下一个请求。
潜在问题
一旦您的应用程序的多个实例并行运行,您应仔细考虑以下主题:
- 当您拥有多个实例时,任何存储在应用程序内存中的状态(数据) 都会成为问题。存储在一个应用程序实例内存中的状态在下一次请求时可能不可用,因为下一个请求可能由不同的应用程序实例处理。虽然有一些解决方案(如粘性会话)可以解决基于用户的此问题,但如果您想在集群、容器或/和云中运行应用程序,最佳实践是将应用程序设计为无状态。
- 内存缓存是一种内存状态,不应在集群应用程序中使用。您应改用分布式缓存。
- 您不应将数据存储在本地文件系统中。它应该对您的应用程序的所有实例都可用。不同的应用程序实例可能运行在不同的容器或服务器中,它们可能无法访问相同的文件系统。您可以使用云或外部存储提供商作为解决方案。
- 如果您有后台工作者或作业队列管理器,则需要小心,因为多个实例可能尝试同时执行相同的作业或执行相同的工作。因此,您可能会多次完成相同的工作,或者在尝试访问和更改相同资源时可能遇到大量错误。
您可能会遇到更多集群部署的问题,但这些都是最常见的。ABP 的设计考虑了与集群部署场景的兼容性。以下部分解释了在将基于 ABP 的应用程序部署到集群环境时应采取的措施。
切换到分布式缓存
ASP.NET Core 提供了不同类型的缓存功能。内存缓存将您的对象存储在本地服务器的内存中,并且仅对存储该对象的应用程序可用。集群环境中的非粘性会话应使用 分布式缓存 ,除了一些特定场景(例如,您可以将本地 CSS 文件缓存到内存中。它是只读数据,并且在所有应用程序实例中都是相同的。出于性能原因,您可以将其缓存在内存中,而不会出现任何问题)。
ABP 的分布式缓存 扩展了 ASP.NET Core 的分布式缓存 基础设施。它默认在内存中工作。当您希望将应用程序部署到集群环境时,您应配置一个实际的分布式缓存提供程序。
即使您的应用程序不直接使用
IDistributedCache,您也应该为集群部署配置缓存提供程序。 ABP 和预构建的 应用程序模块 使用分布式缓存。
ASP.NET Core 提供了多种集成可作为您的分布式缓存提供程序,例如 Redis 和 NCache。您可以参考 微软的文档 来了解如何在您的应用程序中使用它们。
如果您决定使用 Redis 作为分布式缓存提供程序,请遵循 ABP 的 Redis 缓存集成文档,了解将其安装到应用程序并设置 Redis 配置所需的步骤。
根据您创建新的 ABP 解决方案时的偏好,Redis 缓存可能已经预安装。 例如,如果您选择带有 MVC UI 的分层选项,Redis 缓存默认会被预安装。 因为在这种情况下,您的解决方案中有两个应用程序,它们应使用相同的缓存源以保持一致。
使用适当的 BLOB 存储提供程序
如果您在集群环境中使用 ABP 的 BLOB 存储 功能以及文件系统提供程序,您应该使用另一个提供程序,因为文件系统提供程序使用应用程序的本地文件系统。
数据库 BLOB 提供程序是最简单的方式,因为它使用您应用程序的主数据库(或者如果您配置的话,另一个数据库)来存储 BLOB。但是,您应该记住 BLOB 是大对象,可能会迅速增加数据库的大小。
ABP 启动解决方案模板预装了数据库 BLOB 提供程序,并将 BLOB 存储在应用程序的数据库中。
查看 BLOB 存储 文档以了解所有可用的 BLOB 存储提供程序。
配置后台作业
ABP 的 后台作业系统 将任务排队以便在后台执行。后台作业队列是持久的,并且保证排队的任务会被执行(如果失败会重试)。
ABP 的默认后台作业管理器与集群环境兼容。它使用 分布式锁 来确保作业在同一时间只在一个应用程序实例中执行。请参阅下面的配置分布式锁提供程序部分,了解如何为您的应用程序配置分布式锁提供程序。因此,默认的后台作业管理器在集群环境中正常工作。
如果您不想使用分布式锁提供程序,可以选择以下选项:
- 在除一个实例之外的所有应用程序实例中停止后台作业管理器(将
AbpBackgroundJobOptions.IsJobExecutionEnabled设置为false),这样只有单个实例执行作业(而其他应用程序实例仍然可以排队作业)。 - 在所有应用程序实例中停止后台作业管理器(将
AbpBackgroundJobOptions.IsJobExecutionEnabled设置为false),并创建一个专用的应用程序(可能是一个在自己的容器中运行的控制台应用程序,或者在后台运行的 Windows 服务)来执行所有后台作业。如果您的后台作业消耗大量系统资源(CPU、RAM、磁盘),这可能是一个不错的选择,您可以将该后台应用程序部署到专用服务器上,您的后台作业就不会影响应用程序的性能。
如果您使用外部后台作业集成(例如 Hangfire 或 Quartz)而不是默认的后台作业管理器,请参考您的提供程序的文档,了解如何为集群环境配置它。
配置分布式锁提供程序
ABP 提供了一个分布式锁抽象,并使用 DistributedLock 库实现了一个实现。分布式锁用于控制多个应用程序对共享资源的并发访问,以防止由于并发写入而导致资源损坏。ABP 和一些预构建的 应用程序模块 出于多种原因使用分布式锁。
然而,分布式锁系统默认在进程内工作。这意味着除非您配置分布式锁提供程序,否则它实际上不是分布式的。因此,如果尚未配置,请遵循 分布式锁 文档为您的应用程序配置一个提供程序。
配置 SignalR
ABP 提供 SignalR 集成包以简化集成和使用。每当您需要在应用程序中添加实时 Web 功能(实时消息传递、实时通知等)时,都可以使用 SignalR。
SignalR 要求特定连接的所有 HTTP 请求都使用同一个服务器进程处理(需要跟踪其所有连接)。因此,当 SignalR 在集群环境(具有多个服务器)上运行时,必须使用**“粘性会话”**。
如果您考虑横向扩展您的服务器,并且不希望活动套接字连接出现不一致,您可以使用 Azure SignalR 服务 或 Redis 背板。
要了解更多关于如何在集群环境中托管和扩展 SignalR 的信息,请查看 ASP.NET Core SignalR 托管和扩展。
实现后台工作者
ASP.NET Core 提供 托管服务 ,ABP 提供 后台工作者 来在应用程序的后台线程中执行任务。
如果您的应用程序有在后台运行的任务,您应考虑它们在集群环境中的行为方式,尤其是在后台任务使用相同资源的情况下。您应该设计您的后台任务,使其在集群环境中继续正常工作。
假设您的 SaaS 应用程序在后台检查用户订阅,并向订阅续订日期临近的用户发送电子邮件。如果后台任务在多个应用程序实例上运行,用户可能会收到重复的电子邮件。
采用以下方法之一来克服多个工作者相同池问题:
- 实现您的后台工作者,使其在集群环境中正常工作而没有任何问题。使用 分布式锁 来确保并发控制是一种方法。应用程序实例中的一个后台工作者可能持有分布式锁,因此其他应用程序实例中的工作者将等待该锁。这样,只有一个工作者执行实际工作,而其他工作者处于空闲等待状态。如果您实现了这一点,您的后台工作者将安全运行,而无需关心应用程序是如何部署的。
- 在除一个实例之外的所有应用程序实例中停止后台工作者(将
AbpBackgroundWorkerOptions.IsEnabled设置为false),这样只有单个实例运行工作者。 - 在所有应用程序实例中停止后台工作者(将
AbpBackgroundWorkerOptions.IsEnabled设置为false),并创建一个专用的应用程序(可能是一个在自己的容器中运行的控制台应用程序,或者在后台运行的 Windows 服务)来执行所有后台任务。如果您的后台工作者消耗大量系统资源(CPU、RAM、磁盘),这可能是一个不错的选择,这样您可以将该后台应用程序部署到专用服务器上,您的后台任务就不会影响应用程序的性能。
抠丁客




