mirror of https://github.com/abpframework/abp.git
78 changed files with 1271 additions and 151 deletions
@ -0,0 +1,99 @@ |
|||
# Deploying to a Clustered Environment |
|||
|
|||
This document introduces the topics that you should consider when you are deploying your application to a clustered environment where **multiple instances of your application run concurrently**, and explains how you can deal with these topics in your ABP based application. |
|||
|
|||
> This document is valid regardless you have a monolith application or a microservice solution. The Application term is used for a process. An application can be a monolith web application, a service in a microservice solution, a console application, or another kind of an executable process. |
|||
> |
|||
> For example, if you are deploying your application to Kubernetes and configure your application or service to run in multiple pods, then your application or service runs in a clustered environment. |
|||
|
|||
## Understanding the Clustered Environment |
|||
|
|||
> You can skip this section if you are already familiar with clustered deployment and load balancers. |
|||
|
|||
### Single Instance Deployment |
|||
|
|||
Consider an application deployed as a **single instance**, as illustrated in the following figure: |
|||
|
|||
 |
|||
|
|||
Browsers and other client applications can directly make HTTP requests to your application. You can put a web server (e.g. IIS or NGINX) between the clients and your application, but you still have a single application instance running in a single server or container. Single-instance configuration is **limited to scale** since it runs in a single server and you are limited with the server's capacity. |
|||
|
|||
### Clustered Deployment |
|||
|
|||
**Clustered deployment** is the way of running **multiple instances** of your application **concurrently** in a single or multiple servers. In this way, different instances can serve different requests and you can scale by adding new servers to the system. The following figure shows a typical implementation of clustering using a **load balancer**: |
|||
|
|||
 |
|||
|
|||
### Load Balancers |
|||
|
|||
[Load balancers](https://en.wikipedia.org/wiki/Load_balancing_(computing)) have a lot of features, but they fundamentally **forward an incoming HTTP request** to an instance of your application and return your response back to the client application. |
|||
|
|||
Load balancers can use different algorithms for selecting the application instance while determining the application instance that is used to deliver the incoming request. **Round Robin** is one of the simplest and most used algorithms. Requests are delivered to the application instances in rotation. First instance gets the first request, second instance gets the second, and so on. It returns to the first instance after all the instances are used, and the algorithm goes like that for the next requests. |
|||
|
|||
### Potential Problems |
|||
|
|||
Once multiple instances of your application run in parallel, you should carefully consider the following topics: |
|||
|
|||
* Any **state (data) stored in memory** of your application will become a problem when you have multiple instances. A state stored in memory of an application instance may not be available in the next request since the next request will be handled by a different application instance. While there are some solutions (like sticky sessions) to overcome this problem user-basis, it is a **best practice to design your application as stateless** if you want to run it in a cluster, container or/and cloud. |
|||
* **In-memory caching** is a kind of in-memory state and should not be used in a clustered application. You should use **distributed caching** instead. |
|||
* You shouldn't store data in the **local file system**. It should be available to all instances of your application. Different application instance may run in different containers or servers and they may not be able to have access to the same file system. You can use a **cloud or external storage provider** as a solution. |
|||
* If you have **background workers** or **job queue managers**, you should be careful since multiple instances may try to execute the same job or perform the same work concurrently. As a result, you may have the same work done multiple times or you may get a lot of errors while trying to access and change the same resources. |
|||
|
|||
You may have more problems with clustered deployment, but these are the most common ones. ABP has been designed to be compatible with the clustered deployment scenario. The following sections explain what you should do when you are deploying your ABP based application to a clustered environment. |
|||
|
|||
## Switching to a Distributed Cache |
|||
|
|||
ASP.NET Core provides different kind of caching features. [In-memory cache](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/memory) stores your objects in the memory of the local server and is only available to the application that stored the object. Non-sticky sessions in a clustered environment should use the [distributed caching](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) except some specific scenarios (for example, you can cache a local CSS file into memory. It is read-only data and it is the same in all application instances. You can cache it in memory for performance reasons without any problem). |
|||
|
|||
[ABP's Distributed Cache](../Caching.md) extends [ASP.NET Core's distributed cache](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) infrastructure. It works in-memory by default. You should configure an actual distributed cache provider when you want to deploy your application to a clustered environment. |
|||
|
|||
> You should configure the cache provider for clustered deployment, even if your application doesn't directly use `IDistributedCache`. Because the ABP Framework and the pre-built [application modules](../Modules/Index.md) are using distributed cache. |
|||
|
|||
ASP.NET Core provides multiple integrations to use as your distributed cache provider, like [Redis](https://redis.io/) and [NCache](https://www.alachisoft.com/ncache/). You can follow [Microsoft's documentation](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) to learn how to use them in your applications. |
|||
|
|||
If you decided to use Redis as your distributed cache provider, **follow [ABP's Redis Cache Integration document](../Redis-Cache.md)** for the steps you need to follow to install it into your application and setup your Redis configuration. |
|||
|
|||
> Based on your preferences while creating a new ABP solution, Redis cache might be pre-installed in your solution. For example, if you have selected the *Tiered* option with the MVC UI, Redis cache comes as pre-installed. Because, in this case, you have two applications in your solution and they should use the same cache source to be consistent. |
|||
|
|||
## Using a Proper BLOB Storage Provider |
|||
|
|||
If you have used ABP's [BLOB Storing](../Blob-Storing.md) feature with the [File System provider](../Blob-Storing-File-System.md), you should use another provider in your clustered environment since the File System provider uses the application's local file system. |
|||
|
|||
The [Database BLOB provider](../Blob-Storing-Database) is the easiest way since it uses your application's main database (or another database if you configure) to store BLOBs. However, you should remember that BLOBs are large objects and may quickly increase your database's size. |
|||
|
|||
> [ABP Commercial](https://commercial.abp.io/) startup solution templates come with the database BLOB provider as pre-installed, and stores BLOBs in the application's database. |
|||
|
|||
Check the [BLOB Storing](../Blob-Storing.md) document to see all the available BLOG storage providers. |
|||
|
|||
## Configuring Background Jobs |
|||
|
|||
ABP's [background job system](../Background-Jobs.md) is used to queue tasks to be executed in the background. Background job queue is persistent and a queued task is guaranteed to be executed (it is re-tried if it fails). |
|||
|
|||
ABP's default background job manager is compatible with clustered environments. It uses a [distributed lock](../Distributed-Locking.md) to ensure that the jobs are executed only in a single application instance at a time. See the *Configuring a Distributed Lock Provider* section below to learn how to configure a distributed lock provider for your application, so the default background job manager properly works in a clustered environment. |
|||
|
|||
If you don't want to use a distributed lock provider, you may go with the following options: |
|||
|
|||
* Stop the background job manager (set `AbpBackgroundJobOptions.IsJobExecutionEnabled` to `false`) in all application instances except one of them, so only the single instance executes the jobs (while other application instances can still queue jobs). |
|||
* Stop the background job manager (set `AbpBackgroundJobOptions.IsJobExecutionEnabled` to `false`) in all application instances and create a dedicated application (maybe a console application running in its own container or a Windows Service running in the background) to execute all the background jobs. This can be a good option if your background jobs consume high system resources (CPU, RAM or Disk), so you can deploy that background application to a dedicated server and your background jobs don't affect your application's performance. |
|||
|
|||
> If you are using an external background job integration (e.g. [Hangfire](../Background-Workers-Hangfire.md) or [Quartz](../Background-Workers-Quartz.md)) instead of the default background job manager, then please refer to your provider's documentation to learn how it should be configured for a clustered environment. |
|||
|
|||
## Configuring a Distributed Lock Provider |
|||
|
|||
ABP provides a distributed locking abstraction with an implementation made with the [DistributedLock](https://github.com/madelson/DistributedLock) library. A distributed lock is used to control concurrent access to a shared resource by multiple applications to prevent corruption of the resource because of concurrent writes. The ABP Framework and some pre-built [application modules](../Modules/Index.md) are using distributed locking for several reasons. |
|||
|
|||
However, the distributed lock system works in-process by default. That means it is not distributed actually, unless you configure a distributed lock provider. So, please follow the [distributed lock](../Distributed-Locking.md) document to configure a provider for your application, if it is not already configured. |
|||
|
|||
## Implementing Background Workers |
|||
|
|||
ASP.NET Core provides [hosted services](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services) and ABP provides [background workers](../Background-Workers.md) to perform tasks in background threads in your application. |
|||
|
|||
If your application has tasks running in the background, you should consider how they will behave in a clustered environment, especially if your background tasks are using the same resources. You should design your background tasks so that they continue to work properly in the clustered environment. |
|||
|
|||
Assume that your background worker in your SaaS application checks user subscriptions and sends emails if their subscription renewal date approaches. If the background task runs in multiple application instances, it is probable to send the same email many times to some users, which will disturb them. |
|||
|
|||
We suggest you to use one of the following approaches to overcome the problem: |
|||
|
|||
* Implement your background workers so that they work in a clustered environment without any problem. Using the [distributed lock](../Distributed-Locking.md) to ensure concurrency control is a way of doing that. A background worker in an application instance may handle a distributed lock, so the workers in other application instances will wait for the lock. In this way, only one worker does the actual work, while others wait in idle. If you implement this, your workers run safely without caring about how the application is deployed. |
|||
* Stop the background workers (set `AbpBackgroundWorkerOptions.IsEnabled` to `false`) in all application instances except one of them, so only the single instance runs the workers. |
|||
* Stop the background workers (set `AbpBackgroundWorkerOptions.IsEnabled` to `false`) in all application instances and create a dedicated application (maybe a console application running in its own container or a Windows Service running in the background) to execute all the background tasks. This can be a good option if your background workers consume high system resources (CPU, RAM or Disk), so you can deploy that background application to a dedicated server and your background tasks don't affect your application's performance. |
|||
@ -0,0 +1,9 @@ |
|||
# Deployment |
|||
|
|||
Deploying an ABP application is not different than deploying any .NET or ASP.NET Core application. You can deploy it to a cloud provider (e.g. Azure, AWS, Google Could) or on-premise server, IIS or any other web server. ABP's documentation doesn't contain much information on deployment. You can refer to your provider's documentation. |
|||
|
|||
However, there are some topics that you should care about when you are deploying your applications. Most of them are general software deployment considerations, but you should understand how to handle them within your ABP based applications. We've prepared guides for this purpose and we suggest you to read these guides carefully before designing your deployment configuration. |
|||
|
|||
## Guides |
|||
|
|||
* [Deploying to a clustered environment](Clustered-Environment.md): Explains how to configure your application when you want to run multiple instances of your application concurrently. |
|||
@ -0,0 +1,82 @@ |
|||
# LeptonX Lite Angular UI |
|||
LeptonX Lite has implementation for the ABP Framework Angular Client. It's a simplified variation of the [LeptonX Theme](https://x.leptontheme.com/). |
|||
|
|||
> If you are looking for a professional, enterprise ready theme, you can check the [LeptonX Theme](https://x.leptontheme.com/), which is a part of [ABP Commercial](https://commercial.abp.io/). |
|||
|
|||
> See the [Theming document](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Theming) to learn about themes. |
|||
|
|||
## Installation |
|||
|
|||
To add `LeptonX-lite` into your project, |
|||
|
|||
* Install `@abp/ng.theme.lepton-x` |
|||
|
|||
`yarn add @abp/ng.theme.lepton-x@preview` |
|||
|
|||
* Install `bootstrap-icons` |
|||
|
|||
`yarn add bootstrap-icons` |
|||
|
|||
|
|||
* Then, we need to edit the styles array in `angular.json` to replace the existing style with the new one. |
|||
|
|||
Add the following style |
|||
|
|||
```json |
|||
"node_modules/bootstrap-icons/font/bootstrap-icons.css", |
|||
``` |
|||
|
|||
* Finally, remove `ThemeBasicModule` from `app.module.ts`, and import the related modules in `app.module.ts` |
|||
|
|||
```js |
|||
import { ThemeLeptonXModule } from '@abp/ng.theme.lepton-x'; |
|||
import { SideMenuLayoutModule } from '@abp/ng.theme.lepton-x/layouts'; |
|||
|
|||
@NgModule({ |
|||
imports: [ |
|||
// ... |
|||
|
|||
// do not forget to remove ThemeBasicModule |
|||
// ThemeBasicModule.forRoot(), |
|||
ThemeLeptonXModule.forRoot(), |
|||
SideMenuLayoutModule.forRoot(), |
|||
], |
|||
// ... |
|||
}) |
|||
export class AppModule {} |
|||
``` |
|||
|
|||
Note: If you employ [Resource Owner Password Flow](https://docs.abp.io/en/abp/latest/UI/Angular/Authorization#resource-owner-password-flow) for authorization, you should import the following module as well: |
|||
|
|||
```js |
|||
import { AccountLayoutModule } from '@abp/ng.theme.lepton-x/account'; |
|||
|
|||
@NgModule({ |
|||
// ... |
|||
imports: [ |
|||
// ... |
|||
AccountLayoutModule.forRoot(), |
|||
// ... |
|||
], |
|||
// ... |
|||
}) |
|||
export class AppModule {} |
|||
``` |
|||
|
|||
To change the logos and brand color of `LeptonX`, simply add the following CSS to the `styles.scss` |
|||
|
|||
```css |
|||
:root { |
|||
--lpx-logo: url('/assets/images/logo.png'); |
|||
--lpx-logo-icon: url('/assets/images/logo-icon.png'); |
|||
--lpx-brand: #edae53; |
|||
} |
|||
``` |
|||
|
|||
- `--lpx-logo` is used to place the logo in the menu. |
|||
- `--lpx-logo-icon` is a square icon used when the menu is collapsed. |
|||
- `--lpx-brand` is a color used throughout the application, especially on active elements. |
|||
|
|||
### Server Side |
|||
|
|||
In order to migrate to LeptonX on your server side projects (Host and/or IdentityServer projects), please follow the [Server Side Migration](mvc.md) document. |
|||
@ -0,0 +1,130 @@ |
|||
# LeptonX Lite Blazor UI |
|||
|
|||
````json |
|||
//[doc-params] |
|||
{ |
|||
"UI": ["Blazor", "BlazorServer"] |
|||
} |
|||
```` |
|||
|
|||
LeptonX Lite has implementation for the ABP Framework Blazor WebAssembly & Blazor Server. It's a simplified variation of the [LeptonX Theme](https://x.leptontheme.com/). |
|||
|
|||
> If you are looking for a professional, enterprise ready theme, you can check the [LeptonX Theme](https://x.leptontheme.com/), which is a part of [ABP Commercial](https://commercial.abp.io/). |
|||
|
|||
> See the [Theming document](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Theming) to learn about themes. |
|||
|
|||
## Installation |
|||
|
|||
{{if UI == "Blazor"}} |
|||
- Complete the [MVC Razor Pages Installation](mvc.md#installation) for the **HttpApi.Host** application first. _If the solution is tiered/micro-service, complete the MVC steps for all MVC applications such as **HttpApi.Host** and if identity server is separated, install to the **IdentityServer**_. |
|||
|
|||
- Add **Volo.Abp.AspNetCore.Components.WebAssembly.LeptonXLiteTheme** package to your **Blazor WebAssembly** application. |
|||
```bash |
|||
dotnet add package Volo.Abp.AspNetCore.Components.WebAssembly.LeptonXLiteTheme |
|||
``` |
|||
|
|||
- Remove the old theme from the **DependsOn** attribute in your module class and add the **AbpAspNetCoreComponentsWebAssemblyLeptonXLiteThemeModule** type to the **DependsOn** attribute. |
|||
|
|||
```diff |
|||
[DependsOn( |
|||
- typeof(AbpAspNetCoreComponentsWebAssemblyBasicThemeModule), |
|||
+ typeof(AbpAspNetCoreComponentsWebAssemblyLeptonXLiteThemeModule) |
|||
)] |
|||
``` |
|||
|
|||
- Change startup App component with the LeptonX one. |
|||
|
|||
```csharp |
|||
// Make sure the 'App' comes from 'Volo.Abp.AspNetCore.Components.Web.LeptonXLiteTheme.Themes.LeptonXLite' namespace. |
|||
builder.RootComponents.Add<App>("#ApplicationContainer"); |
|||
``` |
|||
|
|||
- Run the `abp bundle` command in your **Blazor** application folder. |
|||
|
|||
{{end}} |
|||
|
|||
|
|||
{{if UI == "BlazorServer"}} |
|||
|
|||
- Complete the [MVC Razor Pages Installation](mvc.md#installation) first. _If the solution is tiered/micro-service, complete the MVC steps for all MVC applications such as **HttpApi.Host** and **IdentityServer**_. |
|||
|
|||
- Add **Volo.Abp.AspNetCore.Components.Server.LeptonXLiteTheme** package to your **Blazor server** application. |
|||
```bash |
|||
dotnet add package Volo.Abp.AspNetCore.Components.Server.LeptonXLiteTheme |
|||
``` |
|||
|
|||
- Remove old theme from the **DependsOn** attribute in your module class and add the **AbpAspNetCoreComponentsWebAssemblyLeptonXLiteThemeModule** type to the **DependsOn** attribute. |
|||
|
|||
```diff |
|||
[DependsOn( |
|||
- typeof(AbpAspNetCoreComponentsServerBasicThemeModule), |
|||
+ typeof(AbpAspNetCoreComponentsServerLeptonXLiteThemeModule) |
|||
)] |
|||
``` |
|||
|
|||
- Update AbpBundlingOptions |
|||
```diff |
|||
options.StyleBundles.Configure( |
|||
- BlazorBasicThemeBundles.Styles.Global, |
|||
+ BlazorLeptonXLiteThemeBundles.Styles.Global, |
|||
bundle => |
|||
{ |
|||
bundle.AddFiles("/blazor-global-styles.css"); |
|||
//You can remove the following line if you don't use Blazor CSS isolation for components |
|||
bundle.AddFiles("/MyProjectName.Blazor.styles.css"); |
|||
}); |
|||
``` |
|||
|
|||
- Update `_Host.cshtml` file. _(located under **Pages** folder by default.)_ |
|||
|
|||
- Add following usings to Locate **App** and **BlazorLeptonXLiteThemeBundles** classes. |
|||
```csharp |
|||
@using Volo.Abp.AspNetCore.Components.Web.LeptonXLiteTheme.Themes.LeptonXLite |
|||
@using Volo.Abp.AspNetCore.Components.Server.LeptonXLiteTheme.Bundling |
|||
``` |
|||
- Then replace script & style bundles as following: |
|||
```diff |
|||
- <abp-style-bundle name="@BlazorBasicThemeBundles.Styles.Global" /> |
|||
+ <abp-style-bundle name="@BlazorLeptonXLiteThemeBundles.Styles.Global" /> |
|||
``` |
|||
|
|||
```diff |
|||
- <abp-script-bundle name="@BlazorBasicThemeBundles.Scripts.Global" /> |
|||
+ <abp-script-bundle name="@BlazorLeptonXLiteThemeBundles.Scripts.Global" /> |
|||
``` |
|||
|
|||
{{end}} |
|||
|
|||
|
|||
--- |
|||
|
|||
## Customization |
|||
|
|||
### Toolbars |
|||
LeptonX Lite includes separeted toolbars for desktop & mobile. You can manage toolbars independently. Toolbar names can be accessible in the **LeptonXLiteToolbars** class. |
|||
|
|||
- `LeptonXLiteToolbars.Main` |
|||
- `LeptonXLiteToolbars.MainMobile` |
|||
|
|||
```csharp |
|||
public async Task ConfigureToolbarAsync(IToolbarConfigurationContext context) |
|||
{ |
|||
if (context.Toolbar.Name == LeptonXLiteToolbars.Main) |
|||
{ |
|||
context.Toolbar.Items.Add(new ToolbarItem(typeof(MyDesktopComponent))); |
|||
} |
|||
|
|||
if (context.Toolbar.Name == LeptonXLiteToolbars.MainMobile) |
|||
{ |
|||
context.Toolbar.Items.Add(new ToolbarItem(typeof(MyMobileComponent))); |
|||
} |
|||
|
|||
return Task.CompletedTask; |
|||
} |
|||
``` |
|||
|
|||
{{if UI == "BlazorServer"}} |
|||
|
|||
> _You can visit the [Toolbars Documentation](https://docs.abp.io/en/abp/latest/UI/Blazor/Toolbars) for better understanding._ |
|||
|
|||
{{end}} |
|||
@ -0,0 +1,67 @@ |
|||
# LeptonX Lite MVC UI |
|||
LeptonX Lite has implementation for the ABP Framework Razor Pages. It's a simplified variation of the [LeptonX Theme](https://x.leptontheme.com/). |
|||
|
|||
> If you are looking for a professional, enterprise ready theme, you can check the [LeptonX Theme](https://x.leptontheme.com/), which is a part of [ABP Commercial](https://commercial.abp.io/). |
|||
|
|||
> See the [Theming document](https://docs.abp.io/en/abp/latest/UI/AspNetCore/Theming) to learn about themes. |
|||
|
|||
## Installation |
|||
|
|||
- Add **Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite** package to your **Web** application. |
|||
|
|||
```bash |
|||
abp add-package Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite |
|||
``` |
|||
|
|||
- Make sure the old theme is removed and LeptonX is added in your Module class. |
|||
|
|||
```diff |
|||
[DependsOn( |
|||
- typeof(AbpAspNetCoreMvcUiBasicThemeModule), |
|||
+ typeof(AbpAspNetCoreMvcUiLeptonXLiteThemeModule) |
|||
)] |
|||
``` |
|||
|
|||
- Update AbpBundlingOptions |
|||
|
|||
```diff |
|||
Configure<AbpBundlingOptions>(options => |
|||
{ |
|||
options.StyleBundles.Configure( |
|||
- BasicThemeBundles.Styles.Global, |
|||
+ LeptonXLiteThemeBundles.Styles.Global |
|||
bundle => |
|||
{ |
|||
bundle.AddFiles("/global-styles.css"); |
|||
} |
|||
); |
|||
}); |
|||
``` |
|||
|
|||
--- |
|||
|
|||
## Customization |
|||
|
|||
### Toolbars |
|||
LeptonX Lite includes separeted toolbars for desktop & mobile. You can manage toolbars independently. Toolbar names can be accessible in the **LeptonXLiteToolbars** class. |
|||
|
|||
- `LeptonXLiteToolbars.Main` |
|||
- `LeptonXLiteToolbars.MainMobile` |
|||
|
|||
```csharp |
|||
public class MyProjectNameMainToolbarContributor : IToolbarContributor |
|||
{ |
|||
public async Task ConfigureToolbarAsync(IToolbarConfigurationContext context) |
|||
{ |
|||
if (context.Toolbar.Name == LeptonXLiteToolbars.Main) |
|||
{ |
|||
context.Toolbar.Items.Add(new ToolbarItem(typeof(MyDesktopComponent))); |
|||
} |
|||
|
|||
if (context.Toolbar.Name == LeptonXLiteToolbars.MainMobile) |
|||
{ |
|||
context.Toolbar.Items.Add(new ToolbarItem(typeof(MyMobileComponent))); |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 30 KiB |
@ -0,0 +1,99 @@ |
|||
# 部署到群集环境 |
|||
|
|||
本文档介绍了在将应用程序部署到**多个应用程序实例同时运行**的集群环境中时应注意的内容, 并解释了如何在基于ABP的应用程序中处理这些内容. |
|||
|
|||
> 无论你使用的是单体式应用程序还是微服务解决方案, 本文档均有效. 适用于一个流程. 应用程序可以是单体式web应用程序、微服务解决方案中的服务、控制台应用程序或其他类型的可执行进程. |
|||
> |
|||
> 例如, 如果你将应用程序部署到Kubernetes并把应用程序或服务在多个POD中运行, 那么应用程序或服务将在集群环境中运行. |
|||
|
|||
## 了解集群环境 |
|||
|
|||
> 如果你已经熟悉集群部署和负载均衡器, 可以跳过本节. |
|||
|
|||
### 单实例部署 |
|||
|
|||
考虑作为**单个实例**部署的应用程序, 如下图所示: |
|||
|
|||
 |
|||
|
|||
浏览器和其他客户端应用程序可以直接向应用程序发出HTTP请求. 你可以在客户端和应用程序之间放置一个web服务器(例如IIS或NGINX), 但仍有一个应用程序实例在单个服务器或容器中运行. 单实例的配置**限于规模**, 因为它在一台服务器上运行, 并且你受到服务器容量的限制. |
|||
|
|||
### 集群部署 |
|||
|
|||
**集群部署**是在一台或多台服务器上**同时运行**应用程序**多个实例**的方式. 通过这种方式, 不同的实例可以满足不同的请求, 并且可以通过在系统中添加新服务器来扩展. 下图显示了集群使用**负载均衡器**的典型实现: |
|||
|
|||
 |
|||
|
|||
### 负载均衡器 |
|||
|
|||
[负载均衡器](https://en.wikipedia.org/wiki/Load_balancing_(computing)) 有很多特性, 但它们基本上会将**传入的HTTP请求转发**给应用程序的实例, 并将响应返回给客户端应用程序. |
|||
|
|||
负载平衡器可以使用不同的算法来选择应用程序实例, 同时确定用于传递传入请求的应用程序实例. **循环**是最简单、最常用的算法之一. 请求被轮流传递到应用程序实例. 第一个实例得到第一个请求, 第二个实例得到第二个请求, 依此类推. 在所有实例都被使用之后, 它返回到第一个实例, 并且下一个请求的算法也是类似的. |
|||
|
|||
### 潜在问题 |
|||
|
|||
一旦应用程序的多个实例并行运行, 你应该仔细考虑以下内容: |
|||
|
|||
* 当你有多个实例时, 存储在应用程序 **内存中的任何状态(数据)** 都将成为问题. 存储在应用程序实例内存中的状态可能在下一个请求中不可用, 因为下一个请求将由不同的应用程序实例处理. 虽然有一些解决方案(比如粘性会话)可以解决这个问题, 但如果你想在集群、容器或云中运行应用程序, **最好将其设计为无状态**. |
|||
* **内存缓存** 是一种内存状态, 不应在集群应用程序中使用. 你应该使用**分布式缓存**. |
|||
* 你不应该在**本地文件系统**中存储应用程序所有实例都可以使用的数据. 不同的应用程序实例可能在不同的容器或服务器中运行, 并且它们可能无法访问同一个文件系统. 你可以使用**云或外部存储提供商**作为解决方案. |
|||
* 如果你有**后台工作者**或**作业队列管理器**, 则应小心, 因为多个实例可能会尝试执行同一作业或同时执行同一工作. 因此, 你可能会多次完成相同的工作, 或者在尝试访问和更改相同的资源时可能会出现很多错误. |
|||
|
|||
集群部署可能会有更多问题, 但这些是最常见的问题. ABP被设计为与集群部署场景兼容. 以下各节介绍了将基于ABP的应用程序部署到集群环境时应执行的操作. |
|||
|
|||
## 切换分布式缓存 |
|||
|
|||
ASP.NET Core提供了不同类型的缓存功能. [内存缓存](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/memory)将对象存储在本地服务器的内存中, 并且仅对存储该对象的应用程序可用. 集群环境中的非粘性会话应使用[分布式缓存](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed), 除了一些特定场景(例如, 你可以将本地CSS文件缓存到内存中. 它是只读数据, 在所有应用程序实例中都是相同的. 出于性能原因, 你可以将其缓存到内存中, 而不会出现任何问题). |
|||
|
|||
[ABP的分布式缓存](../Caching.md)扩展了[ASP.NET Core的分布式缓存](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed)的基础设施. 默认情况下, 它在内存中工作. 当你要将应用程序部署到集群环境时, 应该配置实际的分布式缓存提供程序. |
|||
|
|||
> 即使应用程序不直接使用`IDistributedCache`, 也应该为集群部署配置缓存提供程序. 因为ABP框架和预构建的[应用程序模块](../Modules/Index.md)正在使用分布式缓存. |
|||
|
|||
ASP.NET Core提供了可以用作分布式缓存提供程序的多种集成, 如[Redis](https://redis.io/)和[NCache](https://www.alachisoft.com/ncache/). 你可以按照[微软文档](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed)了解如何在应用程序中使用它们. |
|||
|
|||
如果你决定使用Redis作为分布式缓存提供程序, **请遵循[ABP的Redis缓存集成文档](../Redis-Cache.md)** 了解将其安装到应用程序并配置Redis所需遵循的步骤. |
|||
|
|||
> 根据你在创建新ABP解决方案时的偏好, Redis缓存可能会预先安装在你的解决方案中. 例如, 如果你在MVC UI中选择了*Tiered*选项, Redis缓存将进行预装. 因为, 在这种情况下, 解决方案中有两个应用程序, 它们应该使用相同的缓存源来保持一致. |
|||
|
|||
## 使用合适的BLOB存储提供程序 |
|||
|
|||
如果你在[文件系统提供程序](../Blob-Storing-File-System.md)中使用了ABP的[BLOB存储](../Blob-Storing.md)功能, 则应该在集群环境中使用另一个提供程序, 因为文件系统提供程序使用应用程序的本地文件系统. |
|||
|
|||
[数据库BLOB提供程序](../Blob-Storing-Database)是最简单的方法, 因为它使用应用程序的主数据库(或另一个数据库, 如果你配置的话)来存储BLOB. 但是, 你应该记住, BLOB是大型对象, 可能会迅速增加数据库的大小. |
|||
|
|||
> [ABP商业版](https://commercial.abp.io/)启动解决方案模板预装了数据库BLOB提供程序, 并将BLOB存储在应用程序的数据库中. |
|||
|
|||
查看[BLOB Storing](../Blob-Storing.md)文档以查看所有可用的BLOB存储提供程序. |
|||
|
|||
## 配置后台作业 |
|||
|
|||
ABP的[后台作业系统](../Background-Jobs.md)将要在后台执行的任务进行排队. 后台作业队列是持久性的, 排队的任务能够保证执行(如果失败, 将重新尝试). |
|||
|
|||
ABP的默认后台作业管理器与集群环境兼容. 它使用[分布式锁](../Distributed-Locking.md)来确保一次只能在单个应用程序实例中执行作业. 请参阅下面的*配置分布式锁提供程序*部分, 了解如何为应用程序配置分布式锁提供程序, 以便默认后台作业管理器在集群环境中正常工作. |
|||
|
|||
如果不想使用分布式锁提供程序, 可以使用以下选项: |
|||
|
|||
* 停止所有应用程序实例中的后台作业管理器(将`AbpBackgroundJobOptions.IsJobExecutionEnabled`设置为`false`)只保留其中一个应用程序实例, 以便只有单个实例执行作业(而其他应用程序实例仍可以对作业进行排队). |
|||
* 在所有应用程序实例中停止后台作业管理器(将`AbpBackgroundJobOptions.IsJobExecutionEnabled`设置为`false`), 并创建一个专用的应用程序(可能是在自己的容器中运行的控制台应用程序或在后台运行的Windows服务)来执行所有后台作业. 如果你的后台作业占用大量系统资源(CPU、RAM或磁盘), 那么这是一个不错的选择, 这样你就可以将该后台应用程序部署到专用服务器上, 并且后台作业不会影响应用程序的性能. |
|||
|
|||
> 如果你使用的是外部后台作业集成(例如[Hangfire](../Background-Workers-Hangfire.md)或[Quartz](../Background-Workers-Quartz.md))而不是默认的后台作业管理器, 请参阅提供程序的文档, 了解如何为集群环境配置它. |
|||
|
|||
## 配置分布式锁提供程序 |
|||
|
|||
ABP通过[分布式锁](https://github.com/madelson/DistributedLock)库实现了一个抽象的分布式锁. 分布式锁用于控制多个应用程序对共享资源的并发访问, 以防止由于并发写入而导致资源损坏. ABP框架和一些预构建的[应用程序模块](../Modules/Index.md)出于一些原因正在使用分布式锁. |
|||
|
|||
但是, 分布式锁系统默认在进程中工作. 这意味着它实际上不是分布式的, 除非配置分布式锁提供程序. 因此, 如果尚未配置应用程序的提供程序, 请按照[分布式锁](../Distributed-Locking.md)文档为其配置提供程序. |
|||
|
|||
## 实现后台工作者 |
|||
|
|||
ASP.NET Core[托管服务](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services), ABP提供[后台工作者](../Background-Workers.md)在应用程序的后台线程中执行任务. |
|||
|
|||
如果你的应用程序有在后台运行的任务, 你应该注意它们在集群环境中的行为, 尤其是在后台任务使用相同资源的情况下. 你应该设计后台任务, 以便它们在集群环境中继续正常工作. |
|||
|
|||
假设SaaS应用程序中的后台工作者检查用户订阅, 并在订阅续订日期临近时发送电子邮件. 如果后台任务在多个应用程序实例中运行, 可能会多次向某些用户发送同一封电子邮件, 这会影响他们. |
|||
|
|||
我们建议你使用以下方法之一来解决此问题: |
|||
|
|||
* 实现你的后台工作者, 以便他们在集群环境中工作时不会出现任何问题. 使用[分布式锁](../Distributed-Locking.md)来确保并发控制是一种方法. 应用程序实例中的后台工作者可能会处理分布式锁, 因此其他应用程序实例中的工作者将等待该锁. 这样, 只有一个工作者在实际工作, 而其他的则在等待. 如果你实现了这一点, 你的后台工作者就可以安全地运行, 不必关心应用程序是如何部署的. |
|||
* 停止所有应用程序实例中的后台工作者(将`AbpBackgroundWorkerOptions.IsEnabled`设置为`false`), 只保留其中一个应用程序实例, 因此只有单个实例运行这些后台工作者. |
|||
* 停止所有应用程序实例中的后台工作者(将`AbpBackgroundWorkerOptions.IsEnabled`设置为`false`), 并创建一个专用的应用程序(可能是在自己的容器中运行的控制台应用程序或在后台运行的Windows服务)来执行所有后台任务. 如果你的后台工作者消耗大量系统资源(CPU、RAM或磁盘), 那么这是一个不错的选择, 这样你就可以将该后台应用程序部署到专用服务器上, 并且你的后台任务不会影响应用程序的性能. |
|||
@ -0,0 +1,9 @@ |
|||
# 部署 |
|||
|
|||
部署ABP应用程序与部署其他.NET或ASP.NET Core应用程序并没有什么不同. 你可以将其部署到云服务提供商(例如Azure、AWS、Google)或内部部署服务器、IIS或任何其他web服务器. ABP的文档中没有太多关于部署的信息. 你可以参考提供商的文档. |
|||
|
|||
但是, 在部署应用程序时, 有些主题是你应该注意的. 其中大多数是一般的软件部署注意事项, 但你应该了解如何在基于ABP的应用程序中处理它们. 我们为此准备了指南, 建议你在设计部署配置之前仔细阅读这些指南. |
|||
|
|||
## 指南 |
|||
|
|||
* [部署到群集环境](Clustered-Environment.md): 讲解了当你希望同时运行应用程序的多个实例时, 如何来配置应用程序. |
|||
@ -0,0 +1,110 @@ |
|||
# 分布式锁 |
|||
分布式锁是一种管理多个应用程序访问同一资源的技术. 主要目的是同一时间只允许多个应用程序中的一个访问资源. 否则, 从不同的应用程序访问同一对象可能会破坏资源. |
|||
|
|||
> ABP当前的分布式锁实现基于[DistributedLock](https://github.com/madelson/DistributedLock)库. |
|||
|
|||
## 安装 |
|||
|
|||
你可以打开一个命令行终端并输入以下命令来安装[Volo.Abp.DistributedLocking](https://www.nuget.org/packages/Volo.Abp.DistributedLocking)到你的项目中: |
|||
|
|||
````bash |
|||
abp add-package Volo.Abp.DistributedLocking |
|||
```` |
|||
|
|||
这个库提供了使用分布式锁系统所需的API, 但是, 在使用它之前, 你应该配置一个提供程序. |
|||
|
|||
### 配置一个提供程序 |
|||
|
|||
[DistributedLock](https://github.com/madelson/DistributedLock)库对[Redis](https://github.com/madelson/DistributedLock/blob/master/docs/DistributedLock.Redis.md)和[ZooKeeper](https://github.com/madelson/DistributedLock/blob/master/docs/DistributedLock.ZooKeeper.md)提供[多种实现](https://github.com/madelson/DistributedLock#implementations). |
|||
|
|||
例如, 如果你想使用[Redis provider](https://github.com/madelson/DistributedLock/blob/master/docs/DistributedLock.Redis.md), 你应该将[DistributedLock.Redis](https://www.nuget.org/packages/DistributedLock.Redis) NuGet包添加到项目中, 然后将以下代码添加到ABP[模块](Module-Development-Basics.md)类的`ConfigureServices`方法中: |
|||
|
|||
````csharp |
|||
using Medallion.Threading; |
|||
using Medallion.Threading.Redis; |
|||
|
|||
namespace AbpDemo |
|||
{ |
|||
[DependsOn( |
|||
typeof(AbpDistributedLockingModule) |
|||
//If you have the other dependencies, you should do here |
|||
)] |
|||
public class MyModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
var configuration = context.Services.GetConfiguration(); |
|||
|
|||
context.Services.AddSingleton<IDistributedLockProvider>(sp => |
|||
{ |
|||
var connection = ConnectionMultiplexer |
|||
.Connect(configuration["Redis:Configuration"]); |
|||
return new |
|||
RedisDistributedSynchronizationProvider(connection.GetDatabase()); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
此代码从[配置](Configuration.md)获取Redis连接字符串, 因此你可以将以下行添加到`appsettings.json`文件: |
|||
|
|||
````json |
|||
"Redis": { |
|||
"Configuration": "127.0.0.1" |
|||
} |
|||
```` |
|||
|
|||
## 使用 |
|||
|
|||
有两种方法可以使用分布式锁API: ABP的`IAbpDistributedLock`抽象和[DistributedLock](https://github.com/madelson/DistributedLock)库的API. |
|||
|
|||
### 使用IAbpDistributedLock服务 |
|||
|
|||
`IAbpDistributedLock`是ABP框架提供的一个用于简单使用分布式锁的服务. |
|||
|
|||
**实例: 使用`IAbpDistributedLock.TryAcquireAsync`方法** |
|||
|
|||
````csharp |
|||
using Volo.Abp.DistributedLocking; |
|||
|
|||
namespace AbpDemo |
|||
{ |
|||
public class MyService : ITransientDependency |
|||
{ |
|||
private readonly IAbpDistributedLock _distributedLock; |
|||
public MyService(IAbpDistributedLock distributedLock) |
|||
{ |
|||
_distributedLock = distributedLock; |
|||
} |
|||
|
|||
public async Task MyMethodAsync() |
|||
{ |
|||
await using (var handle = |
|||
await _distributedLock.TryAcquireAsync("MyLockName")) |
|||
{ |
|||
if (handle != null) |
|||
{ |
|||
// your code that access the shared resource |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
`TryAcquireAsync`可能无法获取锁. 如果无法获取锁, 则返回`null`. 在这种情况下, 你不应该访问资源. 如果句柄不为`null`, 则表示你已获得锁, 并且可以安全地访问资源. |
|||
|
|||
`TryAcquireAsync`方法拥有以下参数: |
|||
|
|||
* `name` (`string`, 必须): 锁的唯一名称. 不同的锁命名用于访问不同的资源. |
|||
* `timeout` (`TimeSpan`): 等待获取锁的超时值. 默认值为`TimeSpan.Zero`, 这意味着如果锁已经被另一个应用程序拥有, 它不会等待. |
|||
* `cancellationToken`: 取消令牌可在触发后取消操作. |
|||
|
|||
### 使用DistributedLock库的API |
|||
|
|||
ABP的`IAbpDistributedLock`服务非常有限, 主要用于ABP框架的内部使用. 对于你自己的应用程序, 可以使用DistributedLock库自己的API. 参见[文档](https://github.com/madelson/DistributedLock)详细信息. |
|||
|
|||
## Volo.Abp.DistributedLocking.Abstractions库 |
|||
|
|||
如果你正在构建一个可重用的库或应用程序模块, 那么对于作为单个实例运行的简单应用程序, 你可能不希望为模块带来额外的依赖关系. 在这种情况下, 你的库可以依赖于[Volo.Abp.DistributedLocking.Abstractions](https://nuget.org/packages/Volo.Abp.DistributedLocking.Abstractions)库, 它定义了`IAbpDistributedLock`服务, 并将其在进程内实现(实际上不是分布式的). 通过这种方式, 你的库可以在作为单个实例运行的应用程序中正常运行(没有分布式锁提供程序依赖项). 如果应用程序部署到[集群环境](Deployment/Clustered-Environment.md), 那么应用程序开发人员应该安装一个真正的分布式提供程序, 如*安装*部分所述. |
|||
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 30 KiB |
@ -0,0 +1,18 @@ |
|||
using Confluent.Kafka; |
|||
|
|||
namespace Volo.Abp.EventBus.Kafka; |
|||
|
|||
public static class MessageExtensions |
|||
{ |
|||
public static string GetMessageId<TKey, TValue>(this Message<TKey, TValue> message) |
|||
{ |
|||
string messageId = null; |
|||
|
|||
if (message.Headers.TryGetLastBytes("messageId", out var messageIdBytes)) |
|||
{ |
|||
messageId = System.Text.Encoding.UTF8.GetString(messageIdBytes); |
|||
} |
|||
|
|||
return messageId; |
|||
} |
|||
} |
|||
@ -0,0 +1,4 @@ |
|||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|||
const shouldQuote = require('should-quote'); |
|||
export const getParamName = (paramName: string) => |
|||
shouldQuote(paramName) ? `["${paramName}"]` : paramName; |
|||
Loading…
Reference in new issue