创建任务时我们需要指定分配给谁,Demo 中我们使用一个下拉列表用来显示当前系统的所有用户,以供用户选择。我们每创建一个任务时都要去数据库取一次用户列表,然后绑定到用户下拉列表显示。如果就单单对一个 demo 来说,这样实现也无可厚非,但是在正式项目中,显然是不合理的,浪费程序性能,有待优化。
说到优化,你肯定立马就想到了使用缓存。是的,缓存是提高程序性能的高效方式之一。
这一节我们就针对这一案例来看一看 Abp 中如何使用缓存来提高程序性能。
在直接使用缓存之前,我们还是来简单梳理下 Abp 的缓存机制。
Abp 之所以能成为一个优秀的 DDD 框架,我想跟作者详细的文档有很大关系,
作者已经在 ABP 官方文档介绍了如何使用,英文水平好的就直接看官方的吧。
Abp 对缓存进行抽象定义了
接口,位于
- ICache
命名空间。 并对
- Abp.Runtime.Caching
提供了默认的实现
- ICache
,
- AbpMemoryCache
是基于的一种实现方式。
- AbpMemoryCache
是微软的一套缓存机制,定义在
- MemoryCache
命名空间,顾名思义 ,在内存中进行高速缓存。我们通过类型依赖图来看下 Abp 对 Cache 的实现:
- System.Runtime.Caching
从图中可以看出主要包括四个部分:
定位到我们的
,其中有两种创建 Task 的 Action,代码如下:
- TasksController
- 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");
- }
可以看到两个方法都需要调用
来获取用户列表。 现在我们来使用缓存技术对其优化。首先我们应该想到了 Asp.net mvc 自带的一套缓存机制,OutputCache。
- _userAppService.GetUsers();
如果对 OutputCache 不了解,可以参考我的这篇文章。
我们可以简单在 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");
- }
这句代码的意思是该 action 只缓存 1200s。1200s 后,ASP.NET MVC 会重新执行 action 并再次缓存。因为是在 [ChildActionOnly] 中使用[OutputCache],所以该缓存属于 Donut Hole caching。 在该方法内部打个断点,测试只有第一次调用会进入方法内部,之后 1200s 内都不会再进入该方法,1200s 后会再次进入,说明缓存成功!
- [OutputCache(Duration = 1200, VaryByParam = "none")]
按照上面对 Abp 缓存机制的梳理,我们可以在需要使用缓存的地方注入
来进行缓存管理。 现在我们就在 TasksController 中注入
- ICacheManager
。 申明私有变量,并在构造函数中注入,代码如下:
- 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;
- }
下面修改
action 如下:
- RemoteCreate
- 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
进行包装封装了个
- ICache
以实现类型安全。代码种进行了 5 种实现,可以一探究竟:
- ITypedCache
- 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 为我们提供了
,实体缓存机制。 当我们需要通过 ID 获取实体数据而又不想经常去数据库查询时,我们就可以使用
- IEntityCache
。 换句话说,
- IEntityCache
支持按实体 Id 进行动态缓存。
- IEntityCache
在演示具体操作之前,我们先来讲解下
的缓存原理:
- IEntityCache
既然是缓存实体,基于我们这个 demo,我们就拿 Task 实体玩一下吧。
在这里我们先要复习下什么是 DTO,重申下 DDD 为什么引入 DTO。
Data Transfer Objects(DTO)用来在应用层和展现层之间传输数据。
DTO 的必要性:
那这个 DTO 跟要讲的实体缓存有什么关系呢?
不绕弯子了,就是说实体缓存不应直接对 Entity 进行缓存,以避免缓存时序列化了不该序列化的对象和实体。
那具体怎么操作呢?我们就直接上 Demo 吧。
我们定义一个
,用来缓存 Title、Description、State。并定义映射规则
- TaskCacheItem
。
- [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,我们可以简单把它理解为基于内存的速度非常快性能非常棒的 Key-Value 数据库。
有一点需要说明,Redis 官方仅支持 Linux 系统不支持 Windows 系统。
但是呢,微软大法好啊,微软开源技术团队(Microsoft Open Tech group)开发和维护了一个 Win64 的版本,我们可以在上下载 Win64 版本来玩一玩。
想了解更多,请参考或。
打开微软开源技术团队维护的 Redis Github,找到 Releases 目录,下载最新版本的 msi 安装即可。
下载后,一直下一步安装即可。
找到安装目录,打开 cmd 并进入到安装目录,输入
,即可启动 Redis 服务。Redis 服务默认启动在
- redis-server redis.windows.conf
端口。
- 6379
再启动一个 cmd 窗口,执行
即可开一个 Redis 客户端。 执行
- redis-cli.exe
命令进行缓存设置; 执行
- set
命令进行缓存读取; 执行
- get
命令进行频道监听;
- subscribe
命令向指定频道发布消息; 具体步骤详参下图:
- publish
跟着我的步伐,对 Redis 也算有了基本的认识,咱们下面就进入今天的压轴主题,介绍 Abp 下如何使用 redis 进行缓存。
首先我们要知道为什么要用 Redis 进行缓存。
默认的缓存管理是在内存中(in-memory)进行缓存。当你有不止一个并发 web 服务器需要运行同一个应用程序,默认的缓存管理就不满足你的需求。你可能需要一个分布式 / 中央缓存服务器来进行缓存管理,这时 Redis 就可以粉墨登场了。
首先打开 Web 层,下载 Abp.RedisCache Nuget 包安装。
修改
,在 DependsOn 特性上添加对
- XxxWebModule.cs
的依赖,并在模块的
- 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
这篇文章中主要梳理了 Abp 中如何进行缓存管理,并简要介绍了 Abp 中的缓存机制,并与 Asp.net mvc 自带的 [Outputcache] 缓存进行简要对比,并进行了缓存管理实战演练。最后对 Redis 进行了简要介绍,并介绍了如何切换 Redis 缓存。
来源: http://www.cnblogs.com/sheng-jie/p/6508241.html