ABP Introduction Series (13) - Redis Cache Used

Keywords: ASP.NET Redis Database github Windows

ABP Introductory Series Catalogue: Practical Exercise on Learning Abp Framework
Source path: Github-Learning MpaAbp

1. introduction

When creating tasks, we need to specify who to assign. In Demo, we use a drop-down list to display all users of the current system for user selection. Every time we create a task, we go to the database to fetch the user list once, and then bind it to the user drop-down list. If only for a demo, this implementation is justifiable, but in a formal project, it is obviously unreasonable, wastes program performance, and needs to be optimized.
When it comes to optimization, you must immediately think of using caching. Yes, caching is one of the most efficient ways to improve program performance.
In this section, we will take a look at how caching can be used in Abp to improve program performance.

2. Caching mechanism of Abp

Before we use caching directly, let's simply tease out Abp's caching mechanism.
The reason why Abp has become an excellent DDD framework has a lot to do with the author's detailed documentation.
The author has already introduced how to use it in the official ABP document. Caching If you have a good command of English, look directly at the official ones.

Abp abstractly defines the ICache interface for caching, which is located in the Abp.Runtime.Caching namespace.
It also provides the default implementation of AbpMemoryCache for ICache. AbpMemoryCache is based on MemoryCache An implementation method. MemoryCache is a set of cache mechanism defined by Microsoft in the System.Runtime.Caching namespace, as the name implies, caching in memory. We look at Abp's implementation of Cache through the type dependency graph:

It can be seen from the figure that there are mainly four parts:

  • ICache - > CacheBase - > AbpMemoryCache: The abstraction and implementation of caching;
  • ITypedCache: Generic implementation of caching;
  • ICache Manager - > Cache Manager Base - > AbpMemory Cache Manager: Cache management class abstraction and implementation, code can be injected into ICache Manager to obtain the cache;
  • ICaching Configuration - > Caching Configuration: Used to configure which cache to use.

3. Abp Cache Practice Walkthrough

3.1. Location optimization point

Locate to our Tasks Controller, where there are two Action s that create Tasks, the code is as follows:

public PartialViewResult RemoteCreate() {
    var userList = _userAppService.GetUsers();
    ViewBag.AssignedPersonId = new SelectList(userList.Items, "Id", "Name");
    return PartialView("_CreateTaskPartial");
}

[ChildActionOnly] 
public PartialViewResult Create() {
    var userList = _userAppService.GetUsers();
    ViewBag.AssignedPersonId = new SelectList(userList.Items, "Id", "Name");
    return PartialView("_CreateTask");
}

You can see that both methods need to call _userAppService.GetUsers(); to get a list of users.
Now let's use caching technology to optimize it. First of all, we should think of the Output Cache, a caching mechanism that comes with Asp.net mvc.

3.2. Caching with [OutputCache]

If you don't know about OutputCache, you can refer to my article. How much Asp.net mvc knows (9).

We can simply add [Output Cache] features to Action.

[OutputCache(Duration = 1200, VaryByParam = "none")]
[ChildActionOnly] 
public PartialViewResult Create() {
    var userList = _userAppService.GetUsers();
    ViewBag.AssignedPersonId = new SelectList(userList.Items, "Id", "Name");
    return PartialView("_CreateTask");
}

[Output Cache (Duration = 1200, VaryByParam = none)] This code means that the action only caches 1200s. After 1200s, ASP.NET MVC will re-execute action and cache again. Because [Output Cache] is used in [Child Action Only], the cache belongs to Donut Hole caching.
A breakpoint in this method is that only the first call of the test will enter the method, and then it will not enter the method again within 1200s, and it will enter again after 1200s, which indicates that the cache is successful! __________

3.3. Caching with ICache Manager

According to the above analysis of Abp cache mechanism, we can inject ICache Manager into the cache where we need to use the cache to manage the cache.
Now let's inject ICache Manager into Tasks Controller.
Affirm the private variable and inject it into the constructor with the following code:

private readonly ITaskAppService _taskAppService;
private readonly IUserAppService _userAppService;
private readonly ICacheManager _cacheManager;

public TasksController(ITaskAppService taskAppService, IUserAppService userAppService, ICacheManager _cacheManager) {
    _taskAppService = taskAppService;
    _userAppService = userAppService;
    _cacheManager = cacheManager;
}

Modify RemoteCreation as follows:

public PartialViewResult RemoteCreate()
{   
    var userList = _cacheManager.GetCache("ControllerCache").Get("AllUsers", 
                          () => _userAppService.GetUsers()) as ListResultDto<UserListDto>;
    ViewBag.AssignedPersonId = new SelectList(userList.Items, "Id", "Name");
    return PartialView("_CreateTaskPartial");
}

The analysis code finds that the caches we get from the above code require type conversion. The original _cacheManager.GetCache returns the ICache type, while ICache defines the key-value corresponding to the string-object type, so naturally type conversion is needed after data is retrieved from the cache. Is there a generic version? Smart as you are, the author wrapped ICache with an ITypedCache to achieve type safety. There are five implementations of code types, which can be explored as follows:

public PartialViewResult RemoteCreate()
{
    //1.1 Annotate the code, using the following caching method
    //var userList = _userAppService.GetUsers();

    //1.2 Synchronized invocation of asynchronous solutions (the latest Abp-created template project has removed this synchronization method, so you can get a list of users in the following way)
    //var userList = AsyncHelper.RunSync(() => _userAppService.GetUsersAsync());

    //1.3 Cached Version
    var userList = _cacheManager.GetCache("ControllerCache").Get("AllUsers", () => _userAppService.GetUsers());

    //Converting 1.4 to generic version
    //var userList = _cacheManager.GetCache("ControllerCache").AsTyped<string, ListResultDto<UserListDto>>().Get("AllUsers", () => _userAppService.GetUsers());

    //1.5 Generic Cached Version
    //var userList = _cacheManager.GetCache<string, ListResultDto<UserListDto>>("ControllerCache").Get("AllUsers", () => _userAppService.GetUsers());

    ViewBag.AssignedPersonId = new SelectList(userList.Items, "Id", "Name");
    return PartialView("_CreateTaskPartial");
}

After testing, the user list is cached correctly.

Compared with [Output Cache], we naturally ask why the cache provided by Abp does not configure the cache expiration time. The framework you envisioned must also think that the default cache expiration time of Abp is 60 mins. We can customize the cache time by using the Module (module) of the cache project.
Because we are Cache used in Web projects, we locate XxxWebModule.cs and configure the cache in the PreInitialize method.

//The default expiration time for configuring all Cache s is 2 hours
Configuration.Caching.ConfigureAll(cache =>
{
    cache.DefaultSlidingExpireTime = TimeSpan.FromHours(2);
});

//Configure the specified Cache expiration time to be 10 minutes
Configuration.Caching.Configure("ControllerCache", cache =>
{
    cache.DefaultSlidingExpireTime = TimeSpan.FromMinutes(10);
});

3.4. Caching entities using IEntity Cache

3.4.1. Reflections on Caching

The above two caching methods are generally used to store custom caches, but there is a limitation, which is limited by the specific cache expiration time.
Think about it, the list of users we cache is a collection of real-time changes, and this real-time is not timely. There may be new users registered within 1 minute, or there may be no users registered in a few days (such as our Demo), at this time it is difficult to set the cache expiration (refresh) time.
But because we're Demo in nature just to demonstrate usage, it's no mistake that we set the cache expiration time to 10 minutes.

Is there a caching mechanism that automatically re-caches data when it changes without setting the cache expiration time?
The answer is yes, Abp provides us with IEntity Cache, the entity caching mechanism.
We can use IEntity Cache when we need to get entity data through ID and don't want to go to the database frequently.
In other words, IEntity Cache supports dynamic caching by entity Id.

3.4.2. IEntity Cache Caching Principle

Before demonstrating the specific operation, let's explain the caching principle of IEntityCache:

  • First it retrieves entities from the database for the first time, and then subsequent calls will be retrieved from the cache.
  • When an entity is updated or deleted, it automatically invalidates the cached entity, so it will be retrieved from the database in the next request.
  • It uses the full class name of the cached class as the cache name and can modify the cache name by passing parameters to the constructor.
  • It is thread-safe.
  • It maps entities to cached items using IObjectMapper. IObjectMapper is implemented by AutoMapper module. So, if you use it, you need the AutoMapper module. You can override the MapToCacheItem method to manually map entities to cached items.

3.4.3. IEntity Cache

Since it's a cached entity, based on our demo, let's play with the Task entity.
Here we first review what DTO is and reiterate why DTO is introduced into DDD.
Data Transfer Objects (DTO s) are used to transfer data between the application layer and the presentation layer.

Necessity of DTO:

  1. Domain level abstraction
  2. Data hiding
  3. Serialization and Delayed Loading

What does this DTO have to do with the entity cache?
Not to go around the curve, that is to say, entity caching should not directly cache Entity, in order to avoid serializing objects and entities that should not be serialized when caching.
How does that work? Let's go straight to Demo.
We define a TaskCacheItem to cache Title, Description, State. And define the mapping rule [AutoMapFrom (typeof (Task)].

namespace LearningMpaAbp.Tasks.Dtos
{
    [AutoMapFrom(typeof(Task))]
    public class TaskCacheItem
    {
        public string Title { get; set; }

        public string Description { get; set; }

        public TaskState State { get; set; }
    }
}

Next, we define a cache interface for TaskCacheItem.

namespace LearningMpaAbp.Tasks
{
    public interface ITaskCache:IEntityCache<TaskCacheItem>
    {
    }
}

Implement ITaskCache cache interface:

namespace LearningMpaAbp.Tasks
{
    public class TaskCache : EntityCache<Task, TaskCacheItem>, ITaskCache, ISingletonDependency
    {
        public TaskCache(ICacheManager cacheManager, IRepository<Task, int> repository, string cacheName = null) 
            : base(cacheManager, repository, cacheName)
        {
        }
    }
}

Now, when we need to get Title, Description, State from TaskId, we can get it from the cache by injecting ITaskCache into the required classes.
Next, we add an interface TaskCacheItem GetTaskFromCacheById(int taskId) to ITaskAppService.
Then implement it in TaskAppService, declare variables and inject ITaskCache into the constructor to implement the defined interface:

private readonly ITaskCache _taskCache;

/// <summary>
///     In constructor, we can get needed classes/interfaces.
///     They are sent here by dependency injection system automatically.
/// </summary>
public TaskAppService(IRepository<Task> taskRepository, IRepository<User, long> userRepository,
    ISmtpEmailSenderConfiguration smtpEmialSenderConfigtion, INotificationPublisher notificationPublisher, ITaskCache taskCache)
{
    _taskRepository = taskRepository;
    _userRepository = userRepository;
    _smtpEmialSenderConfig = smtpEmialSenderConfigtion;
    _notificationPublisher = notificationPublisher;
    _taskCache = taskCache;
}

public TaskCacheItem GetTaskFromCacheById(int taskId)
{
    return _taskCache[taskId];
}

The test is as follows. By calling the method directly in the instant window, we find that only one Sql query is generated, which indicates that the entity cache is successful.

Perhaps reading this, you may ask, "Redis Cache is used." You talked about it for half a day, and it has nothing to do with Redis.

Redis is such a powerful skill, of course, to be on the stage, Redis will talk about next.

4. What is Redis?

Redis is an open source (BSD licensed) in-memory data structure storage system that can be used as a database, cache and message middleware. It supports a variety of data structures, such as strings, hashes, lists, sets, sorted sets, range queries, bitmaps, hyperloglogs and geospatial index radius queries.

The official explanation is that for the first time we know Redis, we can simply interpret it as a Key-Value database with very fast memory-based speed and excellent performance.

One thing to note is that Redis officially only supports Linux systems and does not support Windows systems.
But Microsoft is doing well. The Microsoft Open Tech group has developed and maintained a version of Win64. We can do this in https://github.com/MSOpenTech/redis Download Win64 version to play.

For more information, please refer to Official Chinese Documents or Official English Documents.

5. Start playing Redis

5.1. Install Redis

Open Edith Github maintained by Microsoft Open Source Technology Team link Find the Releases directory and download the latest version of the msi installation.

After downloading, you can install it next step.

5.2. Simple Trial

Find the installation directory, open cmd and enter the installation directory, and enter redis-server redis.windows.conf to start the Redis service. Redis service starts by default on port 6379.

Start a cmd window and execute redis-cli.exe to open a Redis client.
Execute the set command to set the cache.
Implement get command for cache reading;
Perform subscribe command for channel monitoring;
Publish the message to the specified channel by executing the publish command.
Detailed steps are shown in the following figure:

6. Try Redis Cache on ABP

Following my steps, I have a basic understanding of Redis. Let's move on to today's pivotal theme and introduce how to use redis for caching under Abp.
First, we need to know why we need to use Redis for caching.
The default cache management is in-memory caching. When you have more than one concurrent web server running the same application, the default cache management does not meet your needs. You may need a distributed / central cache server for cache management, and Redis will be ready to roll out.

6.1. Abp Integrated Redis

First, open the Web layer and download the Abp.RedisCache Nuget package installation.
Modify XxxWebModule.cs, add dependencies on AbpRedisCacheModule on DependsOn features, and call the UseRedis extension method in the PreInitialize method of the module. The code is as follows:

[DependsOn(
        typeof(LearningMpaAbpDataModule),
        typeof(LearningMpaAbpApplicationModule),
        typeof(LearningMpaAbpWebApiModule),
        typeof(AbpWebSignalRModule),
        //typeof(AbpHangfireModule), - ENABLE TO USE HANGFIRE INSTEAD OF DEFAULT JOB MANAGER
        typeof(AbpWebMvcModule),
        typeof(AbpRedisCacheModule))]
    public class LearningMpaAbpWebModule : AbpModule
    {
        public override void PreInitialize()
        {
            //Eliminate other configuration code

            //Configure to use Redis cache
            Configuration.Caching.UseRedis();

            //The default expiration time for configuring all Cache s is 2 hours
            Configuration.Caching.ConfigureAll(cache =>
            {
                cache.DefaultSlidingExpireTime = TimeSpan.FromHours(2);
            });

            //Configure the specified Cache expiration time to be 10 minutes
            Configuration.Caching.Configure("ControllerCache", cache =>
            {
                cache.DefaultSlidingExpireTime = TimeSpan.FromMinutes(10);
            });            
        }
  ....
}

The last step is to add a connection string to Abp.Redis.Cache at the Connection Strings node of the Web.Config file, as follows:

  <connectionStrings>
    <add name="Default" connectionString="Server=.\sqlexpress; Database=LearningMpaAbp; Trusted_Connection=True;" providerName="System.Data.SqlClient" />
    <add name="Abp.Redis.Cache" connectionString="localhost"/>
  </connectionStrings>

After starting Redis Server, F5 runs the web project, debugging breakpoints and finds that Redis cache has been successfully applied.
If Redis Server is not started, Error: It was not possible to connect to the redis server (s); to create a disconnected multiplexer, disable AbortOn Connect Fail. Socket Failure on PING

7. summary

This article mainly combs how to manage the cache in Abp, and briefly introduces the cache mechanism in Abp, and makes a brief comparison with the [Outputcache] cache in Asp.net mvc, and carries out a cache management practice. Finally, Redis is briefly introduced, and how to switch Redis cache is introduced.

Posted by DapperDanMan on Fri, 12 Apr 2019 02:33:33 -0700