ABP入门系列目录——学习Abp框架之实操演练
源码路径:Github-LearningMpaAbpgit
建立任务时咱们须要指定分配给谁,Demo中咱们使用一个下拉列表用来显示当前系统的全部用户,以供用户选择。咱们每建立一个任务时都要去数据库取一次用户列表,而后绑定到用户下拉列表显示。若是就单单对一个demo来讲,这样实现也无可厚非,可是在正式项目中,显然是不合理的,浪费程序性能,有待优化。
说到优化,你确定立马就想到了使用缓存。是的,缓存是提升程序性能的高效方式之一。
这一节咱们就针对这一案例来看一看Abp中如何使用缓存来提升程序性能。github
在直接使用缓存以前,咱们仍是来简单梳理下Abp的缓存机制。
Abp之因此能成为一个优秀的DDD框架,我想跟做者详细的文档有很大关系,
做者已经在ABP官方文档介绍了如何使用Caching,英文水平好的就直接看官方的吧。web
Abp对缓存进行抽象定义了ICache
接口,位于Abp.Runtime.Caching
命名空间。
并对ICache
提供了默认的实现AbpMemoryCache
,AbpMemoryCache
是基于MemoryCache的一种实现方式。MemoryCache
是微软的一套缓存机制,定义在System.Runtime.Caching
命名空间,顾名思义 ,在内存中进行高速缓存。咱们经过类型依赖图来看下Abp对Cache的实现:redis
从图中能够看出主要包括四个部分:sql
定位到咱们的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
若是对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后会再次进入,说明缓存成功!安全
按照上面对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; }
下面修改RemoteCreate
action以下:
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); });
上面的两种缓存方式,咱们通常用于存储自定义缓存,但有一个局限性,受到具体缓存过时时间的限制。
思考一下,咱们缓存的用户列表,它是一个实时会变化的集合,而这个实时是不定时的,可能1mins以内就有新用户注册,也有可能几天没有用户注册(好比咱们这个Demo),这个时候就很差设置缓存过时(刷新)时间。
但因为咱们是Demo性质只是为了演示用法,因此咱们设定缓存过时时间为10mins也无可厚非。
那有没有一种缓存机制,不须要设置缓存过时时间,当数据变化的时候就能自动从新缓存呢?
答案是确定的,Abp为咱们提供了IEntityCache
,实体缓存机制。
当咱们须要经过ID获取实体数据而又不想常常去数据库查询时,咱们就可使用IEntityCache
。
换句话说,IEntityCache
支持按实体Id进行动态缓存。
在演示具体操做以前,咱们先来说解下IEntityCache
的缓存原理:
既然是缓存实体,基于咱们这个demo,咱们就拿Task实体玩一下吧。
在这里咱们先要复习下什么是DTO,重申下DDD为何引入DTO。
Data Transfer Objects(DTO)用来在应用层和展示层之间传输数据。
DTO的必要性:
那这个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查询生成,说明实体缓存成功。
可能读到这里,你可能会问,说好的『Redis缓存用起来』,你讲了半天,跟Redis没有半毛钱关系啊。
Redis这么厉害的技能,固然要压轴出场啊,下面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版原本玩一玩。
打开微软开源技术团队维护的Redis Github连接,找到Releases目录,下载最新版本的msi安装便可。
下载后,一直下一步安装便可。
找到安装目录,打开cmd并进入到安装目录,输入redis-server redis.windows.conf
,便可启动Redis 服务。Redis服务默认启动在6379
端口。
再启动一个cmd窗口,执行redis-cli.exe
便可开一个Redis客户端。
执行set
命令进行缓存设置;
执行get
命令进行缓存读取;
执行subscribe
命令进行频道监听;
执行publish
命令向指定频道发布消息;
具体步骤详参下图:
跟着个人步伐,对Redis也算有了基本的认识,我们下面就进入今天的压轴主题,介绍Abp下如何使用redis进行缓存。
首先咱们要知道为何要用Redis进行缓存。
默认的缓存管理是在内存中(in-memory)进行缓存。当你有不止一个并发web服务器须要运行同一个应用程序,默认的缓存管理就不知足你的需求。你可能须要一个分布式/中央缓存服务器来进行缓存管理,这时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就是这么简单、灵活、松藕合!
这篇文章中主要梳理了Abp中如何进行缓存管理,并简要介绍了Abp中的缓存机制,并与Asp.net mvc自带的[Outputcache]缓存进行简要对比,并进行了缓存管理实战演练。最后对Redis进行了简要介绍,并介绍了如何切换Redis缓存。