ABP开发框架前后端开发系列---(15)ABP框架的服务端和客户端缓存的使用

缓存在一个大型一点的系统里面是必然会涉及到的,合理的使用缓存能够给我们的系统带来更高的响应速度。由于数据提供服务涉及到数据库的相关操作,如果客户端的并发数量超过一定的数量,那么数据库的请求处理则以爆发式增长,如果数据库服务器无法快速处理这些并发请求,那么将会增加客户端的请求时间,严重者可能导致数据库服务或者应用服务直接瘫痪。缓存方案就是为这个而诞生,随着缓存的引入,可以把数据库的IO耗时操作,转换为内存数据的快速响应操作,或者把整个页面缓存到缓存系统里面。本篇随笔主要介绍利用ABP框架的支持实现的服务端缓存处理和Winform客户端缓存的处理。

1、缓存文章回顾

缓存的重要性不言而喻,我在博客园里面也写了很多缓存相关的文章,都是基于实际系统的总结处理。

Winform里面的缓存使用

使用ConcurrentDictionary替代Hashtable对多线程的对象缓存处理

在.NET项目中使用PostSharp,使用MemoryCache实现缓存的处理

.NET缓存框架CacheManager在混合式开发框架中的应用(1)-CacheManager的介绍和使用

在.NET项目中使用PostSharp,使用CacheManager实现多种缓存框架的处理

在Winform开发框架中下拉列表绑定字典以及使用缓存提高界面显示速度

C#开发微信门户及应用(48) - 在微信框架中整合CacheManager 缓存框架

上面这些都是和缓存相关的内容,一般来说,缓存有很多方式的实现,如MemoryCache、Redis、Memcached、Couchbase、System.Web.Caching等,为了方便我们一般使用.net的内存缓存处理,如果我们需要序列化缓存内容,那么可以采用MemoryCache或者Redis缓存等。后来我们通过综合考虑,基于配置方式选择不同缓存方式,在后端一般可以使用CacheManager 的缓存处理。

如下面是基于常规架构的缓存处理分层,如果是基于Web API的服务端,那么缓存一般可以在Web API层或者它的下面一层。


image.png

如果是基于可序列化的缓存处理,它在IIS或者其他Web 容器重新启动后,缓存不会丢失,如在Redis里面,有相关的缓存记录如下所示。


image.png

2、ABP服务端缓存处理

ABP提供了缓存的抽象,它内部使用了这个缓存抽象。虽然默认的实现使用了MemoryCache,通过配置也可以使用Redis等缓存,缓存的主要接口ICacheManager。
我们可以在应用服务层的构造函数里面,注入该接口,然后使用该接口获得一个缓存对象。
官方简单的应用服务层代码如下所示。

public class TestAppService : ApplicationService
{
    private readonly ICacheManager _cacheManager;

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

实际上,我们应用服务层应该会更加复杂一些,如下是我们ABP快速开发框架的应用服务层的代码

    [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;//依赖注入缓存
        }

对于字典模块,我们一般获取接口如下所示。

/// <summary>
/// 根据字典类型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;
}

如果我们需要把它构建一个缓存接口,那么处理方式就是对它进行一个简单包装即可,如下代码所示。

/// <summary>
/// 根据字典类型ID获取所有该类型的字典列表集合(使用缓存)
/// </summary>
/// <param name="dictTypeId">字典类型ID</param>
/// <returns></returns>
public async Task<Dictionary<string, string>> GetDictByTypeIDCached(string dictTypeId)
{
    //系统缓存默认为60分钟,可以在模块中配置具体的时间,配置后则是具体配置时间
    return await _cacheManager.GetCache("DictDataAppService")
            .GetAsync(dictTypeId, () => GetDictByTypeID(dictTypeId));
}

默认缓存超时是60分钟,它可以改。如果你超过60分钟没有使用缓存中的项,会从缓存中自动移除。你可以配置指定的缓存或是全部的缓存。

我们可以在应用服务层模块类ApplicationModule类里面进行修改,实现对缓存的过期设置。

//系统缓存默认为60分钟,可以在模块中配置具体的时间,配置后则是具体配置时间
//所有缓存设置为2小时
Configuration.Caching.ConfigureAll(cache =>
{
    cache.DefaultSlidingExpireTime = TimeSpan.FromHours(2);
});

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

Redis 缓存集成

默认缓存管理使用的是内存缓存。所以,如果你有多个并发的Web服务器使用同个应用,可能会成为一个问题,在这种情况下,你需要一个分布/集中缓存服务,你就可以简单的使用Redis做为你的缓存服务器。

Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。

Redis的代码遵循ANSI-C编写,可以在所有POSIX系统(如Linux, <tt>*</tt>BSD, Mac OS X, Solaris等)上安装运行。而且Redis并不依赖任何非标准库,也没有编译参数必需添加。

下载地址:https://github.com/MSOpenTech/redis/releases下载,安装为Windows服务即可。

安装后作为Windows服务运行,安装后可以在系统的服务里面看到Redis的服务在运行了,如下图所示。

image.png

安装好Redis后,还有一个Redis伴侣Redis Desktop Manager需要安装,这样可以实时查看Redis缓存里面有哪些数据,具体地址如下:http://redisdesktop.com/download

下载属于自己平台的版本即可


image.png

下载安装后,打开运行界面,如果我们往里面添加键值的数据,那么可以看到里面的数据了。

image.png

我们来看看如何在ABP框架中使用Redis缓存

我们现在应用服务层模块里面配置好使用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;
        });

        //系统缓存默认为60分钟,可以在模块中配置具体的时间,配置后则是具体配置时间
        //所有缓存设置为2小时
        //Configuration.Caching.ConfigureAll(cache =>
        //{
        //    cache.DefaultSlidingExpireTime = TimeSpan.FromHours(2);
        //});
        //特殊指定为5分钟
        Configuration.Caching.Configure("DictDataAppService", cache =>
        {
            cache.DefaultSlidingExpireTime = TimeSpan.FromMinutes(5);
        });
    }

Host项目配置文件,Appsetting.json配置文件如下所示,增加RedisCache的配置节点。

image

使用缓存处理的应用服务层接口实现如下所示

/// <summary>
/// 根据字典类型ID获取所有该类型的字典列表集合(使用缓存)
/// </summary>
/// <param name="dictTypeId">字典类型ID</param>
/// <returns></returns>
public async Task<Dictionary<string, string>> GetDictByTypeIDCached(string dictTypeId)
{
    //系统缓存默认为60分钟,可以在模块中配置具体的时间,配置后则是具体配置时间
    return await _cacheManager.GetCache("DictDataAppService").GetAsync(dictTypeId, () => GetDictByTypeID(dictTypeId));
}

在测试接口页面中进行测试

image

查看缓存管理里面的内容,可以发现已经具有值了,如下所示。


image

这样我们就可以很容易的从内存缓存切换到Redis的缓存了。

实体缓存

虽然ABP缓存系统出于普通的目的,但有一个EntityCache基类,可帮你缓存实体。如果我们通过它们的Id获取的实体,我们可以用这个基类缓存它们,就不用再频繁地从数据库查询。

不过这里不对这个进行细讲了。

3、Winform客户端的缓存处理

除了在服务端进行缓存测试外,为了提高客户端的响应速度,我们还可以在Winform客户端中使用内存缓存进行缓存一些不常变化的内容的,这样可以避免频繁的请求网络接口,获取接口数据。

ABP基础模块里面也提供了一个简单的缓存类,我们可以使用它进行缓存处理。

我曾经在之前一篇随笔《在Winform开发框架中下拉列表绑定字典以及使用缓存提高界面显示速度》对字典模块中使用缓存进行了说明,这个我们也可以调整为ABP快速开发框架中Winform客户端的字典处理方式。

ABP中有两种cache的实现方式:MemroyCache 和 RedisCache. 如下图,两者都继承至ICache接口。ABP核心模块封装了MemroyCache 来实现ABP中的默认缓存功能。 Abp.RedisCache这个模块封装RedisCache来实现缓存。


image

我们可以在Winform客户端中使用AbpMemoryCache是实现内存缓存的处理。

例如我们在界面模块中使用一个字典辅助类来封装对字典模块的调用,同时可以使用缓存方式进行获取。

image

使用缓存处理的逻辑,如下所示

image

主要就是判断键值是否存在,否则就设置内存缓存即可。

然后在编写一个字典控件的扩展函数,如下所示。

/// <summary>
/// 绑定下拉列表控件为指定的数据字典列表
/// </summary>
/// <param name="control">下拉列表控件</param>
/// <param name="dictTypeName">数据字典类型名称</param>
/// <param name="defaultValue">控件默认值</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);
}

绑定字典控件使用的时候,就非常简单了,如下代码是实际项目中对字典列表绑定的操作,字典数据在字典模块里面统一定义的。

/// <summary>
/// 初始化数据字典
/// </summary>
private void InitDictItem()
{
    txtInDiagnosis.BindDictItems("入院诊断");
    txtLeaveDiagnosis.BindDictItems("最后诊断");

    //初始化代码
    this.txtFollowType.BindDictItems("随访方式");
    this.txtFollowStatus.BindDictItems("随访状态");
}

这样就非常简化了我们对字典数据源的绑定操作了,非常方便易读,下面是其中一个功能界面的下拉列表展示。

image

使用缓存接口,对于大量字典数据显示的界面,界面显示速度有了不错的提升。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341