ABP入門系列(13)——Redis緩存用起來

ABP入門系列目錄——學習Abp框架之實操演練
源碼路徑:Github-LearningMpaAbp
git


1. 引言

建立任務時咱們須要指定分配給誰,Demo中咱們使用一個下拉列表用來顯示當前系統的全部用戶,以供用戶選擇。咱們每建立一個任務時都要去數據庫取一次用戶列表,而後綁定到用戶下拉列表顯示。若是就單單對一個demo來講,這樣實現也無可厚非,可是在正式項目中,顯然是不合理的,浪費程序性能,有待優化。
說到優化,你確定立馬就想到了使用緩存。是的,緩存是提升程序性能的高效方式之一。
這一節咱們就針對這一案例來看一看Abp中如何使用緩存來提升程序性能。github

2. Abp的緩存機制

在直接使用緩存以前,咱們仍是來簡單梳理下Abp的緩存機制。
Abp之因此能成爲一個優秀的DDD框架,我想跟做者詳細的文檔有很大關係,
做者已經在ABP官方文檔介紹瞭如何使用Caching,英文水平好的就直接看官方的吧。web

Abp對緩存進行抽象定義了ICache接口,位於Abp.Runtime.Caching命名空間。
並對ICache提供了默認的實現AbpMemoryCacheAbpMemoryCache是基於MemoryCache的一種實現方式。MemoryCache是微軟的一套緩存機制,定義在System.Runtime.Caching命名空間,顧名思義 ,在內存中進行高速緩存。咱們經過類型依賴圖來看下Abp對Cache的實現:redis

Abp.Runtime.Caching 類型依賴圖

從圖中能夠看出主要包括四個部分:sql

  • ICache->CacheBase->AbpMemoryCache:對緩存的抽象以及實現;
  • ITypedCache:緩存的泛型實現;
  • ICacheManager->CacheManagerBase->AbpMemoryCacheManager:緩存管理類的抽象和實現,代碼中能夠經過注入ICacheManager來獲取緩存;
  • ICachingConfiguration->CachingConfiguration:用來配置使用哪一種緩存。

3. Abp緩存實操演練

3.1. 定位優化點

定位到咱們的TasksController,其中有兩種建立Task的Action,代碼以下:數據庫

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");
}

能夠看到兩個方法都須要調用_userAppService.GetUsers();來獲取用戶列表。
如今咱們來使用緩存技術對其優化。首先咱們應該想到了Asp.net mvc自帶的一套緩存機制,OutputCache。express

3.2. 使用[OutputCache]進行緩存

若是對OutputCache不瞭解,能夠參考個人這篇文章Asp.net mvc 知多少(九)windows

咱們能夠簡單在Action上添加[OutputCache]特性便可。緩存

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

[OutputCache(Duration = 1200, VaryByParam = "none")]這句代碼的意思是該action只緩存1200s。1200s後,ASP.NET MVC會從新執行action並再次緩存。由於是在[ChildActionOnly]中使用[OutputCache],因此該緩存屬於Donut Hole caching。
在該方法內部打個斷點,測試只有第一次調用會進入方法內部,以後1200s內都不會再進入該方法,1200s後會再次進入,說明緩存成功!安全

3.3. 使用ICacheManager進行緩存

按照上面對Abp緩存機制的梳理,咱們能夠在須要使用緩存的地方注入ICacheManager來進行緩存管理。
如今咱們就在TasksController中注入ICacheManager
申明私有變量,並在構造函數中注入,代碼以下:

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;
}

下面修改RemoteCreateaction以下:

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");
}

分析代碼發現咱們在經過上面代碼中獲取的緩存是須要進行類型轉換的。原來_cacheManager.GetCache返回的是ICache類型,而ICache定義key-value對應的是string-object類型,因此天然從緩存獲取完數據後要進行類型轉換了(注:最新Abp版本爲ICache提供了擴展方法,再也不須要顯示進行類型轉換)。那有沒有泛型版本?聰明如你,做者對ICache進行包裝封裝了個ITypedCache以實現類型安全。代碼種進行了5種實現,能夠一探究竟:

public PartialViewResult RemoteCreate()
{
    //1.1 註釋該段代碼,使用下面緩存的方式
    //var userList = _userAppService.GetUsers();

    //1.2 同步調用異步解決方案(最新Abp建立的模板項目已經去掉該同步方法,因此能夠經過下面這種方式獲取用戶列表)
    //var userList = AsyncHelper.RunSync(() => _userAppService.GetUsersAsync());

    //1.3 緩存版本
    var userList = _cacheManager.GetCache("ControllerCache").Get("AllUsers", () => _userAppService.GetUsers());

    //1.4 轉換爲泛型版本
    //var userList = _cacheManager.GetCache("ControllerCache").AsTyped<string, ListResultDto<UserListDto>>().Get("AllUsers", () => _userAppService.GetUsers());

    //1.5 泛型緩存版本
    //var userList = _cacheManager.GetCache<string, ListResultDto<UserListDto>>("ControllerCache").Get("AllUsers", () => _userAppService.GetUsers());

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

經測試,用戶列表正確緩存。

與[OutputCache]相比,咱們很天然就會問Abp提供的緩存怎麼沒有配置緩存過時時間,你想到的框架確定也想到了,Abp的默認緩存過時時間是60mins,咱們能夠經過在使用緩存項目的Module(模塊)中自定義緩存時間。
由於咱們是在Web項目中使用的Cache,因此定位到XxxWebModule.cs,在PreInitialize方法中進行緩存配置。

//配置全部Cache的默認過時時間爲2小時
Configuration.Caching.ConfigureAll(cache =>
{
    cache.DefaultSlidingExpireTime = TimeSpan.FromHours(2);
});

//配置指定的Cache過時時間爲10分鐘
Configuration.Caching.Configure("ControllerCache", cache =>
{
    cache.DefaultSlidingExpireTime = TimeSpan.FromMinutes(10);
});

3.4. 使用IEntityCache對實體進行緩存

3.4.1. 緩存方式的思考

上面的兩種緩存方式,咱們通常用於存儲自定義緩存,但有一個侷限性,受到具體緩存過時時間的限制。
思考一下,咱們緩存的用戶列表,它是一個實時會變化的集合,而這個實時是不定時的,可能1mins以內就有新用戶註冊,也有可能幾天沒有用戶註冊(好比咱們這個Demo),這個時候就很差設置緩存過時(刷新)時間。
但因爲咱們是Demo性質只是爲了演示用法,因此咱們設定緩存過時時間爲10mins也無可厚非。

那有沒有一種緩存機制,不須要設置緩存過時時間,當數據變化的時候就能自動從新緩存呢?
答案是確定的,Abp爲咱們提供了IEntityCache,實體緩存機制。
當咱們須要經過ID獲取實體數據而又不想常常去數據庫查詢時,咱們就可使用IEntityCache
換句話說,IEntityCache支持按實體Id進行動態緩存。

3.4.2. IEntityCache緩存原理

在演示具體操做以前,咱們先來說解下IEntityCache的緩存原理:

  • 首先它第一次從數據庫中獲取實體,而後後續調用將會從緩存獲取。
  • 當實體更新或刪除時它自動將緩存的實體置爲無效狀態,所以它將會再下一次請求中從數據庫中從新獲取。
  • 它使用緩存的類的完整類名做爲緩存名稱,能夠經過爲構造函數傳參來修改緩存名稱。
  • 它是線程安全的。
  • 它使用IObjectMapper將實體映射到緩存項。 IObjectMapper由AutoMapper模塊實現。因此,若是你使用它,你須要AutoMapper模塊。您能夠覆蓋MapToCacheItem方法以手動將實體映射到緩存項。

3.4.3. IEntityCache上手實戰

既然是緩存實體,基於咱們這個demo,咱們就拿Task實體玩一下吧。
在這裏咱們先要複習下什麼是DTO,重申下DDD爲何引入DTO。
Data Transfer Objects(DTO)用來在應用層和展示層之間傳輸數據。

DTO的必要性:

  1. 領域層的抽象
  2. 數據隱藏
  3. 序列化和延遲加載問題

那這個DTO跟要講的實體緩存有什麼關係呢?
不繞彎子了,就是說實體緩存不該直接對Entity進行緩存,以免緩存時序列化了不應序列化的對象和實體。
那具體怎麼操做呢?咱們就直接上Demo吧。
咱們定義一個TaskCacheItem,用來緩存Title、Description、State。並定義映射規則[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; }
    }
}

下面咱們定義一個針對TaskCacheItem的緩存接口。

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

實現ITaskCache緩存接口:

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)
        {
        }
    }
}

如今,當咱們須要根據TaskId獲取Title、Description、State,咱們就能夠經過在須要的類中注入注入ITaskCache,來從緩存中獲取。
下面咱們在ITaskAppService中添加一個接口TaskCacheItem GetTaskFromCacheById(int taskId);
而後在TaskAppService中實現它,申明變量並在構造函數注入ITaskCache,實現定義的接口:

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];
}

測試以下,直接在即時窗口調用方法,發現只有一條Sql查詢生成,說明實體緩存成功。

即時窗口和跟蹤Sql輸出

可能讀到這裏,你可能會問,說好的『Redis緩存用起來』,你講了半天,跟Redis沒有半毛錢關係啊。

Redis這麼厲害的技能,固然要壓軸出場啊,下面Redis開講。

4. Redis是什麼玩意

Redis 是一個開源(BSD許可)的,內存中的數據結構存儲系統,它能夠用做數據庫、緩存和消息中間件。它支持多種類型的數據結構,如字符串(strings)、散列(hashes)、列表(lists)、集合(sets)、有序集合(sorted sets)與範圍查詢、bitmaps、hyperloglogs和地理空間(geospatial)索引半徑查詢。

官方的解釋就是這麼拗口,對於初識Redis,咱們能夠簡單把它理解爲基於內存的速度很是快性能很是棒的Key-Value數據庫。

有一點須要說明,Redis官方僅支持Linux系統不支持Windows系統。
可是呢,微軟大法好啊,微軟開源技術團隊(Microsoft Open Tech group)開發和維護了一個Win64 的版本,咱們能夠在https://github.com/MSOpenTech/redis上下載Win64版原本玩一玩。

想了解更多,請參考中文官方文檔英文官方文檔

5. 動手試玩Redis

5.1. 安裝Redis

打開微軟開源技術團隊維護的Redis Github連接,找到Releases目錄,下載最新版本的msi安裝便可。

下載後,一直下一步安裝便可。

5.2. 簡單試玩

找到安裝目錄,打開cmd並進入到安裝目錄,輸入redis-server redis.windows.conf,便可啓動Redis 服務。Redis服務默認啓動在6379端口。

啓動Redis Server

再啓動一個cmd窗口,執行redis-cli.exe便可開一個Redis客戶端。
執行set命令進行緩存設置;
執行get命令進行緩存讀取;
執行subscribe命令進行頻道監聽;
執行publish命令向指定頻道發佈消息;
具體步驟詳參下圖:

簡單試玩

6. ABP上試玩Redis緩存

跟着個人步伐,對Redis也算有了基本的認識,我們下面就進入今天的壓軸主題,介紹Abp下如何使用redis進行緩存。
首先咱們要知道爲何要用Redis進行緩存。
默認的緩存管理是在內存中(in-memory)進行緩存。當你有不止一個併發web服務器須要運行同一個應用程序,默認的緩存管理就不知足你的需求。你可能須要一個分佈式/中央緩存服務器來進行緩存管理,這時Redis就能夠粉墨登場了。

6.1. Abp集成Redis

首先打開Web層,下載Abp.RedisCache Nuget包安裝。
修改XxxWebModule.cs,在DependsOn特性上添加對AbpRedisCacheModule的依賴,並在模塊的PreInitialize方法中調用UseRedis擴展方法,代碼以下:

[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()
        {
            //省略其餘配置代碼

            //配置使用Redis緩存
            Configuration.Caching.UseRedis();

            //配置全部Cache的默認過時時間爲2小時
            Configuration.Caching.ConfigureAll(cache =>
            {
                cache.DefaultSlidingExpireTime = TimeSpan.FromHours(2);
            });

            //配置指定的Cache過時時間爲10分鐘
            Configuration.Caching.Configure("ControllerCache", cache =>
            {
                cache.DefaultSlidingExpireTime = TimeSpan.FromMinutes(10);
            });            
        }
  ....
}

最後一步在Web.Config文件的【connectionStrings】節點爲Abp.Redis.Cache添加鏈接字符串,以下:

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

啓動Redis Server後,F5運行web項目,斷點調試,發現已經成功應用Redis緩存。
若未啓動Redis Server,會報Error:It was not possible to connect to the redis server(s); to create a disconnected multiplexer, disable AbortOnConnectFail. SocketFailure on PING

這樣咱們就用Redis代替了默認的MemoryCache緩存方案,而不須要改動其它代碼,Abp就是這麼簡單、靈活、鬆藕合!

7. 總結

這篇文章中主要梳理了Abp中如何進行緩存管理,並簡要介紹了Abp中的緩存機制,並與Asp.net mvc自帶的[Outputcache]緩存進行簡要對比,並進行了緩存管理實戰演練。最後對Redis進行了簡要介紹,並介紹瞭如何切換Redis緩存。

相關文章
相關標籤/搜索