anyushengcms

为什么要用缓存

为什么要用缓存呢,说缓存之前先说使用缓存的优点。

  • 减少寄宿服务器的往返调用(round-trips)。
  • 如果缓存在客户端或是代理,将减少对服务器的请求,减少带宽。
  • 减少对数据库服务器的往返调用(round-trips)。
  • 当内容缓存在web服务器,能够减轻对数据库的请求。
  • 减少网络带宽。
  • 避免了重新生成可重用内容的时耗。
  • 提高性能
  • 因为缓存减少了round-trips, network traffic(网络带宽),并避免- 了生成可重用内容的时耗,所以对性能有巨大的提高。

传统的缓存方式

传统的缓存方式如下面这张图
image.png
之前我们处理方式处理起来也很简单

  1. 页面输出缓存,直接在 ASP.NET中页面缓存的使用OutputCache 在aspx页的顶部加这样一句即可:
    <%@ OutputCache Duration="60" VaryByParam="none" %>
    Duration 表示缓存的时间秒,必选,否则报错。
  2. 第二种方式
    if (this.Cache["Keys"] == null) { this.Cache.Insert("Keys", List, null, DateTime.Now.AddHours(2), TimeSpan.Zero); }

这里是检查缓存中Keys是否存在,如果不存在,则写入一个新的值List.还有其他的一些使用方法。
image.png

上面两种方式显然不在现在使用范畴,也不在我想说的范畴之内。,年代貌似有点久远,不用webform基本用不到。现在我们更多是的使用MVC。
我们想说的是MVC输出缓存。

MVC缓存

输出缓存Outputcache ,分为Action输出缓存和Controller输出缓存。使用的场景包括某个页面的数据更新不是很频繁,不需要每次都从数据库区查询。缓存起来从内存中读取。

数据缓存:是相对于全局的。任何地方需要调用的时候都可以去调用。使用的场景包括权限管理这种模块的。每个角色对于菜单的访问都是固定的,所以有必要将角色,权限,菜单这种数据做一个全局的数据缓存。修改时再做缓存的更新。

输出缓存和数据缓存区别:打个比方输出缓存就像是“局部变量”,数据缓存就像是全局变量(只是个比喻)。

Controller输出缓存和 Action缓存使用方式是一样的,就是Controller 或Action上打[OutPutCache]特性标签。但是他们之间又是有区别的。

一、控制器缓存

Control缓存的作用域是整个控制器,所以在这个控制器下的所有Action都会被缓存起来。Control缓存的粒度比较粗,应用也比较少些。
[OutputCache(Duration = 10)] public class HomeController : Controller { public ActionResult Index() { ViewBag.CurrentTime = DateTime.Now; return View(); } }
二、Action缓存
将[OutPutCache]特性标签打在Action上,这样,只有加缓存的Action才会有缓存,其他的Action是没有的。

Outputcache特性常用的属性参数

名称 描述
AllowMultiple 获取或设置一个值,该值指示是否可指定筛选器特性的多个实例。
CacheProfile 获取或设置缓存配置文件名称。
ChildActionCache 获取或设置子操作缓存。
Duration 获取或设置缓存持续时间(以秒为单位)。
Location 获取或设置位置。
NoStore 获取或设置一个值,该值指示是否存储缓存。
Order 获取或者设置执行操作筛选器的顺序。
SqlDependency 获取或设置 SQL 依赖项。
TypeId (从Attribute继承。)
VaryByContentEncoding 获取或设置基于内容变化的编码。
VaryByCustom 获取或设置基于自定义项变化的值。
VaryByHeader 获取或设置基于标头变化的值。
VaryByParam 获取或设置基于参数变化的值。

输出缓存CacheProfile使用配置文件设置缓存

举例其中的CacheProfile,这种方式便于统一配置,当然也可以设置参数duration、location 、varybyparam等。我们需要在system.web 节点下加入这些

<!---CacheProfile配置文件中设置缓存--> <caching> <outputCacheSettings> <outputCacheProfiles> <add name="TestConfigCache" duration="20" location="Any" enabled="true"/> </outputCacheProfiles> </outputCacheSettings> </caching> <!---CacheProfile配置文件中设置缓存end-->
其实作用和效果还是一样,无非就是方便点,统一的配置参数都直接写webconfig文件里面。其实也可以Controller中写。
配置好了之后我们直接在控制器调用相应的名字的OutputCache特性标签即可。
[OutputCache(CacheProfile= "TestConfigCache")] public ActionResult Index() { ViewBag.CurrentTime = DateTime.Now; return View(); }
更多的方式,需要下去再研究下。

ABP中使用ICacheManager进行缓存管理

ABP中有两种cache的实现方式:MemroyCacheRedisCache,两者都继承至ICache接口(准确说是CacheBase抽象类)。ABP核心模块封装了MemroyCache来实现ABP中的默认缓存功能。 Abp.RedisCache这个模块封装RedisCache来实现缓存(通过StackExchange.Redis这个类库访问redis)。

ABP给出了一个抽象缓存基类。并在内部使用了该抽象基类。使用 MemoryCache 来实现了该抽象基类。它能够被任何其它的缓存类来扩展。Abp.RedisCache 包就扩展了该缓存基类。
ABP对外提供了一个缓存接口ICacheMananger。我们通过构造函数注入这个接口来获取缓存。示例如下:
image.png

在这个示例中,我们注入了 ICacheManager接口,s并且获取了一个名称为ControllerCache的缓存。首先我们先对ControllerCache进行清除,然后存入缓存,缓存的名字是大小写敏感的,那就是"ControllerCache"和"CONTROLLERCACHE"取得的缓存内容是不同的。

注意:GetCache方法 千万不要在你的构造函数中使用GetCache方法。如果类不是一个单例对象那么该缓存可能会被dispose掉。

ICache

ICacheManager.GetCache方法返回了一个ICache对象。每一个缓存都是基于名称单例存在的。只有首次访问时才会被创建,以后你每次用相同的名称去获取的缓存都是相同的。所以我们可以在不同的类中使用相同的名称来共享相同的缓存。

在示例代码中,我们简单的使用了ICache.Get方法,它有两个参数:

  • key : 要获取的缓存项的唯一标识符
  • factory:如果根据给定的key获取到的缓存项为空,那么factory将会创建一个标识符为key的缓存,并且返回该缓存

ICache接口还有其它方法,如前面Clear(),Get(),GetOrDefaultSetRemoveClear。当然也有这些方法的异步(async)版本。如下图,我就懒得写了。
image.png

ITypedCache

ICache 接口用key(字符串类型)来获取缓存value(object类型)。ITypedCacheICahe提供了一个 类型安全 的包装;为了使类型安全转换(ICacheITypedCache),我们可以用扩展方法 AsTyped,而不需要写其它强制类型转换的代码,如下所示:
ITypedCache<int, Item> myCache = _cacheManager.GetCache("MyCache").AsTyped<int, Item>();

Configuration

缓存的过期时间默认是60分钟。它是变化的。如果你在60分钟内没有使用该缓存,该缓存会被自动的移除。如果你想改变所有的缓存或者指定的缓存来的默认过期时间,你可以这样做,实现如下:
//对所有缓存的配置 Configuration.Caching.ConfigureAll(cache => { cache.DefaultSlidingExpireTime = TimeSpan.FromHours(2); });
//对指定缓存的配置 Configuration.Caching.Configure("MyCache", cache => { cache.DefaultSlidingExpireTime = TimeSpan.FromHours(8); });

这段代码你应该放在模块(module)的 PreInitialize 方法中。如上所示:MyCache将会在8小时后过期,而其他的缓存将在2小时后过期。

这些配置将会在首次创建缓存的时候生效。配置不仅仅局限于DefaultSlidingExpireTime,你可以利用ICache接口中的属性获取方法来*的配置并且初始化它们。

Entity Caching

ABP的缓存系统是以通用为目的,它有一个 EntityCache 基类,如果你需要的话,这个基类可以帮助你缓存实体。使用这个基类,我们可以通过ID取得实体,并且我们通过ID来缓存实体,这样以后就不需要频繁的查询数据库去取得实体。假设我们有个Person实体,像下面一样:
public class Person : Entity { public string Name { get; set; } public int Age { get; set; } }

并且,假设我们通过该实体的Id,需要频繁调用取得Person实体的Name。首先,我们应该创建一个类来存储 cache items:
[AutoMapFrom(typeof(Person))] public class PersonCacheItem { public string Name { get; set; } }

我们 不应该直接存储实体到缓存中 因为缓存的时候需要序列化缓存对象而实体可能不能被序列化(尤其是实体的导航属性)。这就是为什么我们定义了一个简单的像DTO的类来存储数据到缓存中。我们添加了 AutoMapFrom 特性,这是因为我们想使用 AutoMapper 来自动的转换 Person 实体为 PersonCacheItem 对象。如果我们不使用 AutoMapper,那么我们应该重写 EntityCache 类的 MapToCacheItem 方法手动转换/映射它。

然而这不是必须的,我们可能想定义一个接口为缓存类:
public interface IPersonCache : IEntityCache<PersonCacheItem> { }
最后,我们可以创建缓存类来缓存Person实体:
public class PersonCache : EntityCache<Person, PersonCacheItem>, IPersonCache, ITransientDependency { public PersonCache(ICacheManager cacheManager, IRepository<Person> repository) : base(cacheManager, repository) { } }

这样就OK了,我们的person缓存已经准备好可以使用了。缓存类可以使瞬时(如同这个例子)或者是单例。这不是说缓存数据是瞬态的。在你的应用程序中它一直是全局缓存并且是线程安全的。

现在,无论在什么地方我们需要取得Person的Name,我们可以通过Person的Id从缓存中取得它。如下所示:
public class MyPersonService : ITransientDependency { private readonly IPersonCache _personCache; public MyPersonService(IPersonCache personCache) { _personCache = personCache; } public string GetPersonNameById(int id) { return _personCache[id].Name; //alternative: _personCache.Get(id).Name; } }
我们很容易的注入 IPersonCache 接口,通过该接口取得缓存项和Name属性。

那么EntityCache是怎么工作的?

  • 在首次调用的时候我们通过仓储从数据库中取得实体。那么随后的调用都是从缓存中取得。
  • 如果实体被更新或者删除,它会自动的无效实体。因此,它会在下次调用的时候重新从数据库中检索数据。
  • 使用 IObjectMapper 接口来映射实体到缓存项。IObjectMapper 接口在 AutoMapper 中被实现。所以,如果你使用了自动映射,那么就需要 AutoMapper模块。你可以重写 MapToCacheItem 方法手动映射它到缓存项。
  • 使用缓存类的FullName作为缓存的Name,你可以通过传入的缓存名到基类的构造函数来改变它。
  • 它是线程安全的。
    如果你有更复杂的缓存需求,那么你需要扩展 EntityCache 类或者创建你自己的解决方案。

Redis Cache 集成

Redis是什么,Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。它可以用作数据库、缓存和消息中间件。它支持多种类型的数据结构,如字符串(strings)、散列(hashes)、列表(lists)、集合(sets)、有序集合(sorted sets)与范围查询、bitmaps、hyperloglogs和地理空间(geospatial)索引半径查询。
Redis 是完全开源免费的,遵守BSD协议,是一个高性能的key-value数据库。
Redis 与其他 key - value 缓存产品有以下三个特点

  • Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
  • Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
  • Redis支持数据的备份,即master-slave模式的数据备份。
    Redis 优势
  • 性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。
  • 丰富的数据类型 – Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。
  • 原子 – Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。
  • 丰富的特性 – Redis还支持 publish/subscribe, 通知, key 过期等等特性。
  • 多实用工具 - Redis是一个多实用工具,可用于多种用例,如:缓存,消息队列(Redis本地支持发布/订阅),应用程序中的任何短期数据,例如,web应用程序中的会话,网页命中计数等。

(1)首先,我们前往https://github.com/dmajkic/redis/downloads下载安装包,直接下一步下一步就可以了。
然后打开安装的地址就可以看到如下的文件:
image.png
(2)然后启动Redis服务,我们cmd到安装目录下,然后输入命令redis-server.exe redis.windows.conf
就会看到下面的画面证明我们启动服务成功。
image.png
abp默认Cache Mananger是使用in-memory来缓存。所以,这可能会成为一个问题,如果有多个并发的Web服务运行在同一个应用中。在这种情况下,你可能想要一个分布式/*缓存服务器。那么,你可以使用Redis来作为你的缓存服务。
首先,你需要安装
Abp.RedisCachenuget package 到你的项目中(你可以安装它到你的Web项目)。这里我遇到一个错误。
image.png
开始的时候我搞了半天不知道为什么会出现这个莫名其妙的错误,后来才发现,原来我引入Abp.RedisCache版本和abp版本不一致。才导致的这个错误,比如你abp是3.1.1,那么你的Abp.RedisCache最好也是对应的版本,最好的话把abp和Abp.RedisCache都升级到最新版本,就不会有错误了。
然后我们看看Abp.Runtime.Caching.Redis;依赖项以及之间的关系。
image.png
然后在ABPCMSWebModule配置一下。
image.png
ABPCMSApplicationModule中引入。
image.png
Web.config中配置
image.png
你也可以添加配置到appSettings来设置Redis数据库的Id。如:
<add key="Abp.Redis.Cache.DatabaseId" value="2"/>
在同一个服务器上使用不同的数据库Id是非常有用的这可以创建不同的Key Spaces(隔离缓存)。
UseRedis有一个重载方法,你可以通过这个方法来传入配置参数,这可以覆盖掉配置文件中的配置。关于Redis的其他配置可以查看Redis文档

在下面UserList打下断点调试进去。
image.png
看到效果如下图,证明我们AbpRedisCache引入成功。
image.png
当然为了更好的进行可视化操作,我建议使用跨平台开源Redis DB管理工具(Redis Desktop Manager)地址:https://redisdesktop.com/download
下载下来直接下一步下一步安装即可。
image.png
然后运行项目,然后我们在看下Redis Desktop Manager工具,效果如下图:
image.png
使用可视化工具很方便

  1. 新建连接,输入redis主机host,端口号port,再起个生动形象,简明达意的别名。
  2. 该工具支持根据筛选条件查询key,add new key,reload等。
  3. 支持常用redis操作,针对目标key执行rename,delete,addrow,reload value操作。
  4. 命令控制台操作 !大家感兴趣可以自己玩一下。
    另外关于实体修改后自动更新缓存的实现远离可以参考
    http://www.cnblogs.com/loyldg/p/using-redis-in-abp-2.html
    这个文章。

    Github项目地址:https://github.com/Jimmey-Jiang/ABP-ASP.NET-Boilerplate-Project-CMS

相关文章: