ABP学习实践(十五)--缓存使用总结

近期在工作过程中对ABP框架的缓存功能又有了深一步的理解,做一个小小的总结。

1.关于缓存

现在相当一部分小伙伴听到缓存立刻想到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;
   }

1.1 缓存管理的实现机制

在这里插入图片描述

自带的缓存管理器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的情况下都是可以使用的。这也体现了分层结构的思想,各层之间通过接口约束,至于上层或者下层如何实现当前层不会去关注。

1.2 缓存管理提供的方法

缓存管理器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;
});

1.3 缓存键的规则

实际使用中往往不止一个缓存键值对,有时为了方便管理还会分组分类。一般的缓存键名命名规则是以英文冒号‘:’隔开,比如AdminAuth:SessionToken:xxxxxxxxxx,这就是这个键值在缓存中的唯一标识。取值时可以这样写_cacheManager.GetCache(“AdminAuth:SessionToken:xxxxxxxxxx”),也可以这样写_cacheManager.GetCache(" AdminAuth:SessionToken " ).GetOrDefault(“xxxxxxxxxxx”)。

1.4 使用缓存的时机

不论是服务器缓存还是专用的缓存数据库,毫无疑问在读写速率上要强于常规的数据库和磁盘读写的,因此引入缓存往往也是为了提升软件的读写速度或者作为中间的缓冲层存在。同时缓存空间也是极其珍贵的,在引入缓存的初期就要考虑好缓存的用途、处理流程。主要从以下几个方面考虑缓存的使用:

  • 不要将缓存数据库作为持久化数据库使用:缓存是放在内存的,所以很快,但是断电就会消失。把缓存数据库当作持久化数据库来使用绝对是不负责任的考虑。近期在排查问题时就发现有个数据在数据库中一直找不到,但是接口返回的数据中就有它,最终发现是放在缓存里的,更诡异的是接口对外提供了两个方法,一个获取一个保存都是直接操作的缓存,也就是用户操作提交的这部分数据是在缓存里!可怕!
  • 缓存的使用范围:如果有些数据写入的频率比较低同时读取的频率比较高时就可以考虑放到缓存中,但是把图片、文件这些大量的数据放到缓存中就纯粹是坑人了。
  • 缓存的处理流程要完整:缓存中的数据往往是数据库中持久化数据的拷贝,两边肯定要保持一致,完善的缓存处理流程也是很重要的。常见的例子是将用户权限菜单放在缓存里,可以很快速的读取,做新增操作的时候要同步新增缓存,做更新操作时要同步更新缓存,做删除操作时要同步删除缓存,务必使数据库与缓存中的数据保持一致。
  • 缓存的过期时间:缓存一般都是要有过期时间的,因为它本质上是缓冲、是过渡,将缓存设置为永不过期实在是讲不通,还是要根据业务的实际需要设置不同的过期时间。
  • 缓存的安全性:使用缓存是很快,但是不一定安全,也不能因为图方便把数据放到缓存里。用户登录的Token令牌放到缓存也算是常规做法了,但是自己能看到不代表别人看不到,安全措施也是有必要加的。那些把密码明文放到缓存中的操作也是不得不让人佩服开发人员的“勇气”。

细微之处见功夫

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值