diff --git a/docs/en/Background-Jobs.md b/docs/en/Background-Jobs.md index 2e0b5d3f65..e9a1311ef2 100644 --- a/docs/en/Background-Jobs.md +++ b/docs/en/Background-Jobs.md @@ -155,8 +155,6 @@ public class MyModule : AbpModule } ```` -> Default background manager (see below) does not support multiple processes execute the same job queue. So, if you have multiple running instance of your application and you are using the default background job manager, you should only enable one application instance process the job queue. - ## Default Background Job Manager ABP framework includes a simple `IBackgroundJobManager` implementation that; @@ -189,12 +187,23 @@ public class MyModule : AbpModule ### Data Store -The default background job manager needs a data store to save and read jobs. It defines `IBackgroundJobStore` as an abstraction. So, you can replace the implementation if you want. +The default background job manager needs a data store to save and read jobs. It defines `IBackgroundJobStore` as an abstraction to store the jobs. -Background Jobs module implements `IBackgroundJobStore` using various data access providers. See its own [documentation](Modules/Background-Jobs.md). +Background Jobs module implements `IBackgroundJobStore` using various data access providers. See its own [documentation](Modules/Background-Jobs.md). If you don't want to use this module, you should implement the `IBackgroundJobStore` interface yourself. > Background Jobs module is already installed to the startup templates by default and it works based on your ORM/data access choice. +### Clustered Deployment + +The default background job manager is compatible with [clustered environments](Deployment/Clustered-Environment.md) (where multiple instances of your application run concurrently). It uses a [distributed lock](Distributed-Locking.md) to ensure that the jobs are executed only in a single application instance in a time. + +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. + +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` as explained in the *Disable Job Execution* section) 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` as explained in the *Disable Job Execution* section) in all application instances and create a dedicated application (maybe a console application running in its own container or a Windows Service running in background) to execute all the background jobs. This can be a good option if your background jobs consume high system resource (CPU, RAM or Disk), so you can deploy that background application to a dedicated server and your background jobs doesn't affect your application's performance. + ## Integrations Background job system is extensible and you can change the default background job manager with your own implementation or on of the pre-built integrations. diff --git a/docs/en/Background-Workers.md b/docs/en/Background-Workers.md index 726a8817ab..922740f92a 100644 --- a/docs/en/Background-Workers.md +++ b/docs/en/Background-Workers.md @@ -126,10 +126,11 @@ Background workers only work if your application is running. If you host the bac Be careful if you run multiple instances of your application simultaneously in a clustered environment. In that case, every application runs the same worker which may create conflicts if your workers are running on the same resources (processing the same data, for example). -If that's a problem for your workers, you have two options; +If that's a problem for your workers, you have the following options: -* Disable the background worker system using the `AbpBackgroundWorkerOptions` described above, for all the application instances, except one of them. -* Disable the background worker system for all the application instances and create another special application that runs on a single server and execute the workers. +* Implement your background workers so that they works 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 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 how the application is deployed. +* Stop background workers (set `AbpBackgroundWorkerOptions.IsEnabled` to `false`) in all application instances except one of them, so only the single instance runs the workers. +* Stop 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 background) to execute all the background tasks. This can be a good option if your background workers consume high system resource (CPU, RAM or Disk), so you can deploy that background application to a dedicated server and your background tasks doesn't affect your application's performance. ## Integrations diff --git a/docs/en/Caching.md b/docs/en/Caching.md index 074d6d951f..1ea95cec87 100644 --- a/docs/en/Caching.md +++ b/docs/en/Caching.md @@ -1,7 +1,9 @@ -# Caching +# Distributed Caching ABP Framework extends the [ASP.NET Core distributed cache](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed). +> **Default implementation of the `IDistributedCache` interface is the `MemoryDistributedCache` which works in-memory.** See [ASP.NET Core's documentation](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) to see how to switch to Redis or another cache provider. Also, see the [Redis Cache](Redis-Cache.md) document if you want to use the Redis as the distributed cache server. + ## Installation > This package is already installed by default with the [application startup template](Startup-Templates/Application.md). So, most of the time, you don't need to install it manually. @@ -27,8 +29,6 @@ ASP.NET Core defines the `IDistributedCache` interface to get/set the cache valu > `IDistributedCache` is defined in the `Microsoft.Extensions.Caching.Abstractions` package. That means it is not only usable for ASP.NET Core applications, but also available to **any type of applications**. -> Default implementation of the `IDistributedCache` interface is the `MemoryDistributedCache` which works **in-memory**. See [ASP.NET Core's documentation](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) to see how to switch to Redis or another cache provider. Also, see the [Redis Cache](Redis-Cache.md) document if you want to use the Redis as the distributed cache server. - See [ASP.NET Core's distributed caching document](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) for more information. ### `IDistributedCache` Interface diff --git a/docs/en/Deployment/Clustered-Environment.md b/docs/en/Deployment/Clustered-Environment.md new file mode 100644 index 0000000000..ac7b425377 --- /dev/null +++ b/docs/en/Deployment/Clustered-Environment.md @@ -0,0 +1,99 @@ +# Deploying to a Clustered Environment + +This document introduces the topics that you should care when you are deploying your application to a clustered environment where **multiple instances of your application runs 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 executable process. +> +> For example, if you are deploying your application to Kubernetes and configure so that your application or service runs 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: + +![deployment-single-instance](../images/deployment-single-instance.png) + +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 to 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**: + +![deployment-clustered](../images/deployment-clustered.png) + +### Load Balancers + +[Load balancers](https://en.wikipedia.org/wiki/Load_balancing_(computing)) have a lot of features, but they fundamentally **forwards an incoming HTTP request** to an instance of your application and returns 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 runs 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** that should be available to all instances of your application. Difference application instance may run in different containers or servers and they may not be able to 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 clustered deployment scenario. The following sections explains 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 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 a 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 a clustered deployment, even if your application doesn't directly use `IDistributedCache`. Because ABP Framework and pre-built [application modules](../Modules/Index.md) are using the distributed cache. + +ASP.NET Core's 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 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 available BLOG storage providers. + +## Configuring Background Jobs + +ABP's [background job system](../Background-Jobs.md) is used to queue tasks to be executed in 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 in 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 background) to execute all the background jobs. This can be a good option if your background jobs consume high system resource (CPU, RAM or Disk), so you can deploy that background application to a dedicated server and your background jobs doesn'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 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. ABP Framework and some pre-built [application modules](../Modules/Index.md) are using the 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 background, you should care on 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 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 run in multiple application instances, it is probable to send the same email many times to some users, which will disturb them. + +We suggest to you to use one of the following approaches to overcome the problem: + +* Implement your background workers so that they works 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 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 how the application is deployed. +* Stop background workers (set `AbpBackgroundWorkerOptions.IsEnabled` to `false`) in all application instances except one of them, so only the single instance runs the workers. +* Stop 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 background) to execute all the background tasks. This can be a good option if your background workers consume high system resource (CPU, RAM or Disk), so you can deploy that background application to a dedicated server and your background tasks doesn't affect your application's performance. \ No newline at end of file diff --git a/docs/en/Deployment/Index.md b/docs/en/Deployment/Index.md new file mode 100644 index 0000000000..b11125275b --- /dev/null +++ b/docs/en/Deployment/Index.md @@ -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 you should care 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. \ No newline at end of file diff --git a/docs/en/Distributed-Locking.md b/docs/en/Distributed-Locking.md index d57fd45c83..44cdca74ac 100644 --- a/docs/en/Distributed-Locking.md +++ b/docs/en/Distributed-Locking.md @@ -1,7 +1,5 @@ # Distributed Locking -Distributed locking is a technique to manage many applications that try to access the same resource. -The main purpose is to allow only one of many applications to access the same resource at the same time. -Otherwise, accessing the same object from various applications may corrupt the value of resources. +Distributed locking is a technique to manage many applications that try to access the same resource. The main purpose is to allow only one of many applications to access the same resource at the same time. Otherwise, accessing the same object from various applications may corrupt the value of resources. > ABP's current distributed locking implementation is based on the [DistributedLock](https://github.com/madelson/DistributedLock) library. @@ -15,7 +13,7 @@ abp add-package Volo.Abp.DistributedLocking This package provides the necessary API to use the distributed locking system, however, you should configure a provider before using it. -### Configuring a provider +### Configuring a Provider The [DistributedLock](https://github.com/madelson/DistributedLock) library provides [various of implementations](https://github.com/madelson/DistributedLock#implementations) for the locking, like [Redis](https://github.com/madelson/DistributedLock/blob/master/docs/DistributedLock.Redis.md) and [ZooKeeper](https://github.com/madelson/DistributedLock/blob/master/docs/DistributedLock.ZooKeeper.md). @@ -61,7 +59,7 @@ This code gets the Redis connection string from the [configuration](Configuratio There are two ways to use the distributed locking API: ABP's `IAbpDistributedLock` abstraction and [DistributedLock](https://github.com/madelson/DistributedLock) library's API. -### Using the IAbpDistributedLock service +### Using the IAbpDistributedLock Service `IAbpDistributedLock` is a simple service provided by the ABP framework for simple usage of distributed locking. @@ -103,6 +101,10 @@ namespace AbpDemo * `timeout` (`TimeSpan`): A timeout value to wait to obtain the lock. Default value is `TimeSpan.Zero`, which means it doesn't wait if the lock is already owned by another application. * `cancellationToken`: A cancellation token that can be triggered later to cancel the operation. -### Using DistributedLock library's API +### Using DistributedLock Library's API ABP's `IAbpDistributedLock` service is very limited and mainly designed to be internally used by the ABP Framework. For your own applications, you can use the DistributedLock library's own API. See its [own documentation](https://github.com/madelson/DistributedLock) for details. + +## The Volo.Abp.DistributedLocking.Abstractions Package + +If you are building a reusable library or an application module, then you may not want to bring an additional dependency to your module for simple applications those run as a single instance. In this case, your library can depend on the [Volo.Abp.DistributedLocking.Abstractions](https://nuget.org/packages/Volo.Abp.DistributedLocking.Abstractions) package which defines the `IAbpDistributedLock` service and implements it as in-process (not distributed actually). In this way, your library can run properly (without a distributed lock provider dependency) in an application that runs as a single instance. If the application is deployed to a [clustered environment](Deployment/Clustered-Environment.md), then the application developer should install a real distributed provider as explained in the *Installation* section. diff --git a/docs/en/Redis-Cache.md b/docs/en/Redis-Cache.md index 3e11584bce..9fe8efdb27 100644 --- a/docs/en/Redis-Cache.md +++ b/docs/en/Redis-Cache.md @@ -21,7 +21,7 @@ abp add-package Volo.Abp.Caching.StackExchangeRedis ## Configuration -Volo.Abp.Caching.StackExchangeRedis package automatically gets the redis [configuration](Configuration.md) from the `IConfiguration`. So, for example, you can set your configuration inside the `appsettings.json`: +Volo.Abp.Caching.StackExchangeRedis package automatically gets the Redis [configuration](Configuration.md) from the `IConfiguration`. So, for example, you can set your configuration inside the `appsettings.json`: ````js "Redis": { diff --git a/docs/en/_resources/Diagrams.pptx b/docs/en/_resources/Diagrams.pptx new file mode 100644 index 0000000000..05edf2ced7 Binary files /dev/null and b/docs/en/_resources/Diagrams.pptx differ diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index e32570481a..d021824d7f 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -1237,6 +1237,16 @@ "text": "Testing", "path": "Testing.md" }, + { + "text": "Deployment", + "path": "Deployment/Index.md", + "items": [ + { + "text": "Deploying to a Clustered Environment", + "path": "Deployment/Clustered-Environment.md" + } + ] + }, { "text": "Application Modules", "items": [ diff --git a/docs/en/images/deployment-clustered.png b/docs/en/images/deployment-clustered.png new file mode 100644 index 0000000000..74177482c5 Binary files /dev/null and b/docs/en/images/deployment-clustered.png differ diff --git a/docs/en/images/deployment-single-instance.png b/docs/en/images/deployment-single-instance.png new file mode 100644 index 0000000000..8c3f8719b2 Binary files /dev/null and b/docs/en/images/deployment-single-instance.png differ