修改日期 | 修改人 | 备注 |
2021-08-18 18:25:17[当前版本] | 潘帅 | 1.1 |
2021-08-18 18:24:11 | 潘帅 | 1.0 |
目前部门使用的后端开发框架是在ABP开源框架的基础上进行调整的,在使用习惯上更符合内部实际需求,总体上仍然沿用ABP框架的各种特性,也持续使用了两年左右。但在实际的代码审查过程中发现框架本身的一些优秀特性并没有被完全发挥出来,没有充分发挥ABP框架的优势。后续将通过一系列的总结对框架使用过程中的一些问题、误区进行说明,也欢迎小伙伴们各抒己见、共同探讨。
现在相当一部分小伙伴听到缓存立刻想到Redis,反应很快,但容易进入一个误区“处理缓存就要用Redis”。Redis作为缓存数据库使用方便,性能也好,但并不是只有Redis才能处理缓存,服务器自身也有缓存的。
ABP框架提供了通用的缓存管理器ICacheManager,在应用层、领域层通过构造函数直接注入后就可以使用。
/// <summary> /// 缓存管理器 /// </summary> protected readonly ICacheManager _cacheManager; protected CoreAppServiceBase() { LocalizationSourceName = CoreConsts.LocalizationSourceName; } protected CoreAppServiceBase(ICacheManager cacheManager) { LocalizationSourceName = CoreConsts.LocalizationSourceName; _cacheManager = cacheManager; }
自带的缓存管理器ICacheManager以接口约定形式定义缓存的操作方法,在应用层(xx.Application)、领域层(xx.Core)通过构造函数的形式依赖注入,直接引用使用。由于缓存的操作是通过接口约束的,使用时不需要关心如何实现或通过哪种途径实现。ABP框架默认使用的是服务器缓存。如果需要使用Redis进行缓存管理,只需要在分布式服务层(xx.Web)引用Abp.RedisCahce模块,并在模块类(xxxModule)的预初始化方法(PreInitialize)中进行配置。
//Redis缓存设置 Configuration.Caching.UseRedis(options => { options.ConnectionString = _appConfiguration["RedisSetting:RedisHost"] + ",password=" + _appConfiguration["RedisSetting:RedisPassWord"]; options.DatabaseId = _appConfiguration.GetValue<int>("RedisSetting:RedisDefaultDB"); });对于应用层和领域层来讲,不论分布式服务层使用哪种缓存实现,在代码逻辑上不需要有任何改变。因此ICacheManager在有没有Redis的情况下都是可以使用的。这也体现了分层结构的思想,各层之间通过接口约束,至于上层或者下层如何实现当前层不会去关注。
缓存管理器ICahceManager提供了两个层次的方法,第一个层次的方法用来获取缓存对象,方法有两个,区别在于单个缓存对象和多个缓存对象(集合)。返回的也都是泛型的缓存对象,这里的缓存对象也是抽象的概念,对象本身可能就是个集合,所以灵活性也是很强的。
public interface ICacheManager<TCache> : IDisposable where TCache : class { /// <summary>Gets all caches.</summary> /// <returns>List of caches</returns> IReadOnlyList<TCache> GetAllCaches(); /// <summary> /// Gets a <see cref="T:Abp.Runtime.Caching.ICache" /> instance. /// It may create the cache if it does not already exists. /// </summary> /// <param name="name">Unique and case sensitive name of the cache.</param> /// <returns>The cache reference</returns> TCache GetCache(string name); }在获取缓存对象后就可以进行相关的操作了。常用的缓存操作有Get、Set和Remove,同时也提供了同步和异步的方法。提供的方法有几个注意点:
TValue GetOrDefault(TKey key):通过键查询值,如果键存在则返回对应的值,如果键不存在则返回null。
TValue Get(TKey key, Func<TKey, TValue> factory):通过键查询值,如果键存在则返回对应的值,如果键不存在则创建一个缓存键值对,值则有作为第二个参数的函数获取。
void Set(TKey key, TValue value, TimeSpan? slidingExpireTime = null, TimeSpan? absoluteExpireTime = null):设置键值,如果键存在则覆盖,如果键不存在则创建
void Remove(TKey key):移除键值,如果存在则移除,如果不存在则不做任何操作
这几个方法的贴心之处在于不用在每次操作前手动去判断键是否存在了。尤其是Get系列方法,键存在就返回值,键不存在就创建键同时通过函数返回值设置值很方便,举个例子:
string firstRole = _cacheManager.GetCache(RedisKeys.Account_FirstRole).Get(account.Id, () => { return _mpRepository.GetAll().Where(t => t.ModelAId == account.Id ).FirstOrDefault()?.ModelBId; });
实际使用中往往不止一个缓存键值对,有时为了方便管理还会分组分类。一般的缓存键名命名规则是以英文冒号‘:’隔开,比如AdminAuth:SessionToken:xxxxxxxxxx,这就是这个键值在缓存中的唯一标识。取值时可以这样写_cacheManager.GetCache("AdminAuth:SessionToken:xxxxxxxxxx"),也可以这样写_cacheManager.GetCache(" AdminAuth:SessionToken " ).GetOrDefault("xxxxxxxxxxx")。
不论是服务器缓存还是专用的缓存数据库,毫无疑问在读写速率上要强于常规的数据库和磁盘读写的,因此引入缓存往往也是为了提升软件的读写速度或者作为中间的缓冲层存在。同时缓存空间也是极其珍贵的,在引入缓存的初期就要考虑好缓存的用途、处理流程。主要从以下几个方面考虑缓存的使用:
在现有的项目代码中能检查到有不少小伙伴还在使用RedisHelp这个帮助类,常用的方法都封装了,但在性能测试的时候发现了潜在隐患:高并发情况下会出现资源抢占的情况,导致读取键值失败,代码报错。建议谨慎使用。
ABP框架提供的ICacheManager在同样条件下还没发现有性能的问题。