ABP開發(fā)框架前后端開發(fā)系列---(15)ABP框架的服務(wù)端和客戶端緩存的使用

緩存在一個(gè)大型一點(diǎn)的系統(tǒng)里面是必然會(huì)涉及到的,合理的使用緩存能夠給我們的系統(tǒng)帶來更高的響應(yīng)速度。由于數(shù)據(jù)提供服務(wù)涉及到數(shù)據(jù)庫(kù)的相關(guān)操作,如果客戶端的并發(fā)數(shù)量超過一定的數(shù)量,那么數(shù)據(jù)庫(kù)的請(qǐng)求處理則以爆發(fā)式增長(zhǎng),如果數(shù)據(jù)庫(kù)服務(wù)器無法快速處理這些并發(fā)請(qǐng)求,那么將會(huì)增加客戶端的請(qǐng)求時(shí)間,嚴(yán)重者可能導(dǎo)致數(shù)據(jù)庫(kù)服務(wù)或者應(yīng)用服務(wù)直接癱瘓。緩存方案就是為這個(gè)而誕生,隨著緩存的引入,可以把數(shù)據(jù)庫(kù)的IO耗時(shí)操作,轉(zhuǎn)換為內(nèi)存數(shù)據(jù)的快速響應(yīng)操作,或者把整個(gè)頁面緩存到緩存系統(tǒng)里面。本篇隨筆主要介紹利用ABP框架的支持實(shí)現(xiàn)的服務(wù)端緩存處理和Winform客戶端緩存的處理。

1、緩存文章回顧

緩存的重要性不言而喻,我在博客園里面也寫了很多緩存相關(guān)的文章,都是基于實(shí)際系統(tǒng)的總結(jié)處理。

Winform里面的緩存使用

使用ConcurrentDictionary替代Hashtable對(duì)多線程的對(duì)象緩存處理

在.NET項(xiàng)目中使用PostSharp,使用MemoryCache實(shí)現(xiàn)緩存的處理

.NET緩存框架CacheManager在混合式開發(fā)框架中的應(yīng)用(1)-CacheManager的介紹和使用

在.NET項(xiàng)目中使用PostSharp,使用CacheManager實(shí)現(xiàn)多種緩存框架的處理

在Winform開發(fā)框架中下拉列表綁定字典以及使用緩存提高界面顯示速度

C#開發(fā)微信門戶及應(yīng)用(48) - 在微信框架中整合CacheManager 緩存框架

上面這些都是和緩存相關(guān)的內(nèi)容,一般來說,緩存有很多方式的實(shí)現(xiàn),如MemoryCache、Redis、Memcached、Couchbase、System.Web.Caching等,為了方便我們一般使用.net的內(nèi)存緩存處理,如果我們需要序列化緩存內(nèi)容,那么可以采用MemoryCache或者Redis緩存等。后來我們通過綜合考慮,基于配置方式選擇不同緩存方式,在后端一般可以使用CacheManager 的緩存處理。

如下面是基于常規(guī)架構(gòu)的緩存處理分層,如果是基于Web API的服務(wù)端,那么緩存一般可以在Web API層或者它的下面一層。


image.png

如果是基于可序列化的緩存處理,它在IIS或者其他Web 容器重新啟動(dòng)后,緩存不會(huì)丟失,如在Redis里面,有相關(guān)的緩存記錄如下所示。


image.png

2、ABP服務(wù)端緩存處理

ABP提供了緩存的抽象,它內(nèi)部使用了這個(gè)緩存抽象。雖然默認(rèn)的實(shí)現(xiàn)使用了MemoryCache,通過配置也可以使用Redis等緩存,緩存的主要接口ICacheManager。
我們可以在應(yīng)用服務(wù)層的構(gòu)造函數(shù)里面,注入該接口,然后使用該接口獲得一個(gè)緩存對(duì)象。
官方簡(jiǎn)單的應(yīng)用服務(wù)層代碼如下所示。

public class TestAppService : ApplicationService
{
    private readonly ICacheManager _cacheManager;

    public TestAppService(ICacheManager cacheManager)
    {
        _cacheManager = cacheManager;
    }

實(shí)際上,我們應(yīng)用服務(wù)層應(yīng)該會(huì)更加復(fù)雜一些,如下是我們ABP快速開發(fā)框架的應(yīng)用服務(wù)層的代碼

    [AbpAuthorize]
    public class DictDataAppService : MyAsyncServiceBase<DictData, DictDataDto, string, DictDataPagedDto, CreateDictDataDto, DictDataDto>, IDictDataAppService
    {
        /// <summary>
        /// 緩存管理接口
        /// </summary>
        private readonly ICacheManager _cacheManager;
        private readonly IRepository<DictData, string> _repository;

        public DictDataAppService(IRepository<DictData, string> repository, ICacheManager cacheManager) : base(repository)
        {
            _repository = repository;
            _cacheManager = cacheManager;//依賴注入緩存
        }

對(duì)于字典模塊,我們一般獲取接口如下所示。

/// <summary>
/// 根據(jù)字典類型ID獲取所有該類型的字典列表集合(Key為名稱,Value為值)
/// </summary>
/// <param name="dictTypeId">字典類型ID</param>
/// <returns></returns>
public async Task<Dictionary<string, string>> GetDictByTypeID(string dictTypeId)
{
    IList<DictData> list = await Repository.GetAllListAsync(s => s.DictType_ID == dictTypeId);

    Dictionary<string, string> dict = new Dictionary<string, string>();
    foreach (DictData info in list)
    {
        if (!dict.ContainsKey(info.Name))
        {
            dict.Add(info.Name, info.Value);
        }
    }
    return dict;
}

如果我們需要把它構(gòu)建一個(gè)緩存接口,那么處理方式就是對(duì)它進(jìn)行一個(gè)簡(jiǎn)單包裝即可,如下代碼所示。

/// <summary>
/// 根據(jù)字典類型ID獲取所有該類型的字典列表集合(使用緩存)
/// </summary>
/// <param name="dictTypeId">字典類型ID</param>
/// <returns></returns>
public async Task<Dictionary<string, string>> GetDictByTypeIDCached(string dictTypeId)
{
    //系統(tǒng)緩存默認(rèn)為60分鐘,可以在模塊中配置具體的時(shí)間,配置后則是具體配置時(shí)間
    return await _cacheManager.GetCache("DictDataAppService")
            .GetAsync(dictTypeId, () => GetDictByTypeID(dictTypeId));
}

默認(rèn)緩存超時(shí)是60分鐘,它可以改。如果你超過60分鐘沒有使用緩存中的項(xiàng),會(huì)從緩存中自動(dòng)移除。你可以配置指定的緩存或是全部的緩存。

我們可以在應(yīng)用服務(wù)層模塊類ApplicationModule類里面進(jìn)行修改,實(shí)現(xiàn)對(duì)緩存的過期設(shè)置。

//系統(tǒng)緩存默認(rèn)為60分鐘,可以在模塊中配置具體的時(shí)間,配置后則是具體配置時(shí)間
//所有緩存設(shè)置為2小時(shí)
Configuration.Caching.ConfigureAll(cache =>
{
    cache.DefaultSlidingExpireTime = TimeSpan.FromHours(2);
});

//特殊指定為5分鐘
Configuration.Caching.Configure("DictDataAppService", cache =>
{
    cache.DefaultSlidingExpireTime = TimeSpan.FromMinutes(5);
});

Redis 緩存集成

默認(rèn)緩存管理使用的是內(nèi)存緩存。所以,如果你有多個(gè)并發(fā)的Web服務(wù)器使用同個(gè)應(yīng)用,可能會(huì)成為一個(gè)問題,在這種情況下,你需要一個(gè)分布/集中緩存服務(wù),你就可以簡(jiǎn)單的使用Redis做為你的緩存服務(wù)器。

Redis是一個(gè)開源的使用ANSI C語言編寫、支持網(wǎng)絡(luò)、可基于內(nèi)存亦可持久化的日志型、Key-Value數(shù)據(jù)庫(kù),和Memcached類似,它支持存儲(chǔ)的value類型相對(duì)更多,包括string(字符串)、list(鏈表)、set(集合)、zset(sorted set --有序集合)和hash(哈希類型)。在此基礎(chǔ)上,redis支持各種不同方式的排序。與memcached一樣,為了保證效率,數(shù)據(jù)都是緩存在內(nèi)存中。區(qū)別的是redis會(huì)周期性的把更新的數(shù)據(jù)寫入磁盤或者把修改操作寫入追加的記錄文件,并且在此基礎(chǔ)上實(shí)現(xiàn)了master-slave(主從)同步。

Redis的代碼遵循ANSI-C編寫,可以在所有POSIX系統(tǒng)(如Linux, <tt>*</tt>BSD, Mac OS X, Solaris等)上安裝運(yùn)行。而且Redis并不依賴任何非標(biāo)準(zhǔn)庫(kù),也沒有編譯參數(shù)必需添加。

下載地址:https://github.com/MSOpenTech/redis/releases下載,安裝為Windows服務(wù)即可。

安裝后作為Windows服務(wù)運(yùn)行,安裝后可以在系統(tǒng)的服務(wù)里面看到Redis的服務(wù)在運(yùn)行了,如下圖所示。

image.png

安裝好Redis后,還有一個(gè)Redis伴侶Redis Desktop Manager需要安裝,這樣可以實(shí)時(shí)查看Redis緩存里面有哪些數(shù)據(jù),具體地址如下:http://redisdesktop.com/download

下載屬于自己平臺(tái)的版本即可


image.png

下載安裝后,打開運(yùn)行界面,如果我們往里面添加鍵值的數(shù)據(jù),那么可以看到里面的數(shù)據(jù)了。

image.png

我們來看看如何在ABP框架中使用Redis緩存

我們現(xiàn)在應(yīng)用服務(wù)層模塊里面配置好使用Redis,如下代碼所示

[DependsOn(
    ................
    typeof(AbpRedisCacheModule) //Redis緩存加入
)]
public class ApplicationModule : AbpModule
{
    public override void PreInitialize()
    {
        ............

        //使用Redis緩存
        int DatabaseId = -1;
        int.TryParse(AppSettingConfig.GetAppSetting("RedisCache", "DatabaseId"), out DatabaseId);
        string connectionString = AppSettingConfig.GetAppSetting("RedisCache", "ConnectionString");
        Configuration.Caching.UseRedis(options =>
        {
            options.ConnectionString = connectionString;
            options.DatabaseId = DatabaseId;
        });

        //系統(tǒng)緩存默認(rèn)為60分鐘,可以在模塊中配置具體的時(shí)間,配置后則是具體配置時(shí)間
        //所有緩存設(shè)置為2小時(shí)
        //Configuration.Caching.ConfigureAll(cache =>
        //{
        //    cache.DefaultSlidingExpireTime = TimeSpan.FromHours(2);
        //});
        //特殊指定為5分鐘
        Configuration.Caching.Configure("DictDataAppService", cache =>
        {
            cache.DefaultSlidingExpireTime = TimeSpan.FromMinutes(5);
        });
    }

Host項(xiàng)目配置文件,Appsetting.json配置文件如下所示,增加RedisCache的配置節(jié)點(diǎn)。

image

使用緩存處理的應(yīng)用服務(wù)層接口實(shí)現(xiàn)如下所示

/// <summary>
/// 根據(jù)字典類型ID獲取所有該類型的字典列表集合(使用緩存)
/// </summary>
/// <param name="dictTypeId">字典類型ID</param>
/// <returns></returns>
public async Task<Dictionary<string, string>> GetDictByTypeIDCached(string dictTypeId)
{
    //系統(tǒng)緩存默認(rèn)為60分鐘,可以在模塊中配置具體的時(shí)間,配置后則是具體配置時(shí)間
    return await _cacheManager.GetCache("DictDataAppService").GetAsync(dictTypeId, () => GetDictByTypeID(dictTypeId));
}

在測(cè)試接口頁面中進(jìn)行測(cè)試

image

查看緩存管理里面的內(nèi)容,可以發(fā)現(xiàn)已經(jīng)具有值了,如下所示。


image

這樣我們就可以很容易的從內(nèi)存緩存切換到Redis的緩存了。

實(shí)體緩存

雖然ABP緩存系統(tǒng)出于普通的目的,但有一個(gè)EntityCache基類,可幫你緩存實(shí)體。如果我們通過它們的Id獲取的實(shí)體,我們可以用這個(gè)基類緩存它們,就不用再頻繁地從數(shù)據(jù)庫(kù)查詢。

不過這里不對(duì)這個(gè)進(jìn)行細(xì)講了。

3、Winform客戶端的緩存處理

除了在服務(wù)端進(jìn)行緩存測(cè)試外,為了提高客戶端的響應(yīng)速度,我們還可以在Winform客戶端中使用內(nèi)存緩存進(jìn)行緩存一些不常變化的內(nèi)容的,這樣可以避免頻繁的請(qǐng)求網(wǎng)絡(luò)接口,獲取接口數(shù)據(jù)。

ABP基礎(chǔ)模塊里面也提供了一個(gè)簡(jiǎn)單的緩存類,我們可以使用它進(jìn)行緩存處理。

我曾經(jīng)在之前一篇隨筆《在Winform開發(fā)框架中下拉列表綁定字典以及使用緩存提高界面顯示速度》對(duì)字典模塊中使用緩存進(jìn)行了說明,這個(gè)我們也可以調(diào)整為ABP快速開發(fā)框架中Winform客戶端的字典處理方式。

ABP中有兩種cache的實(shí)現(xiàn)方式:MemroyCache 和 RedisCache. 如下圖,兩者都繼承至ICache接口。ABP核心模塊封裝了MemroyCache 來實(shí)現(xiàn)ABP中的默認(rèn)緩存功能。 Abp.RedisCache這個(gè)模塊封裝RedisCache來實(shí)現(xiàn)緩存。


image

我們可以在Winform客戶端中使用AbpMemoryCache是實(shí)現(xiàn)內(nèi)存緩存的處理。

例如我們?cè)诮缑婺K中使用一個(gè)字典輔助類來封裝對(duì)字典模塊的調(diào)用,同時(shí)可以使用緩存方式進(jìn)行獲取。

image

使用緩存處理的邏輯,如下所示

image

主要就是判斷鍵值是否存在,否則就設(shè)置內(nèi)存緩存即可。

然后在編寫一個(gè)字典控件的擴(kuò)展函數(shù),如下所示。

/// <summary>
/// 綁定下拉列表控件為指定的數(shù)據(jù)字典列表
/// </summary>
/// <param name="control">下拉列表控件</param>
/// <param name="dictTypeName">數(shù)據(jù)字典類型名稱</param>
/// <param name="defaultValue">控件默認(rèn)值</param>
/// <param name="emptyFlag">是否添加空行</param>
public static void BindDictItems(this ComboBoxEdit control, string dictTypeName, string defaultValue, bool isCache = true, bool emptyFlag = true)
{
    var dict = GetDictByDictType(dictTypeName, isCache);

    List<CListItem> itemList = new List<CListItem>();
    foreach (string key in dict.Keys)
    {
        itemList.Add(new CListItem(key, dict[key]));
    }

    control.BindDictItems(itemList, defaultValue, emptyFlag);
}

綁定字典控件使用的時(shí)候,就非常簡(jiǎn)單了,如下代碼是實(shí)際項(xiàng)目中對(duì)字典列表綁定的操作,字典數(shù)據(jù)在字典模塊里面統(tǒng)一定義的。

/// <summary>
/// 初始化數(shù)據(jù)字典
/// </summary>
private void InitDictItem()
{
    txtInDiagnosis.BindDictItems("入院診斷");
    txtLeaveDiagnosis.BindDictItems("最后診斷");

    //初始化代碼
    this.txtFollowType.BindDictItems("隨訪方式");
    this.txtFollowStatus.BindDictItems("隨訪狀態(tài)");
}

這樣就非常簡(jiǎn)化了我們對(duì)字典數(shù)據(jù)源的綁定操作了,非常方便易讀,下面是其中一個(gè)功能界面的下拉列表展示。

image

使用緩存接口,對(duì)于大量字典數(shù)據(jù)顯示的界面,界面顯示速度有了不錯(cuò)的提升。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容