.NET Core 的缓存篇之MemoryCache
前言
对于缓存我们都已经很熟悉了,缓存分为很多种,浏览器缓存、试图缓存、服务器缓存、数据库缓存等等一些,那今天我们先介绍一下视图缓存和MemoryCache内存缓存的概念和用法:
视图缓存
在老的版本的MVC里面,有一种可以缓存视图的特性(OutputCache),可以保持同一个参数的请求,在N段时间内,直接从mvc的缓存中读取,不去走视图的逻辑。
//老版本的.NET 做法 [OutputCache(Duration =20)]//设置过期时间为20秒 public ActionResult ExampleCacheAction() { var time=DateTime.Now.ToString("yyyy年MM月dd日 HH时mm分ss秒"); ViewBag.time= time; return View(); }
在Asp.Net core 2.1中,官方文档上称:响应缓存可减少客户端或代理对 web 服务器的请求数。 响应缓存还可减少量工作的 web 服务器执行程序生成响应。 响应缓存由标头,指定你希望客户端、 代理和缓存响应的中间件如何控制。
在Asp.Net Core 2.1 中,没有了OutputCache,换成了ResponseCache,ResponseCache必须带一个参数:Duration 单位为秒,最少设置一秒钟
//.NET Core2.1做法 [ResponseCache(Duration = 5)] public IActionResult About() { ViewBag.time = DateTime.Now.ToString("yyyy年MM月dd日 HH时mm分ss秒"); return View(); }
然后再浏览器请求这个视图
在浏览器的响应头的Cache-Control 中出现max-age=5, Http协议对此的解释是
客户端将不会接受其保留时间大于指定的秒数的响应。 示例:
max-age=60
(60 秒),max-age=2592000
(1 个月)
如果在浏览器中禁用缓存,那么ResponseCache不会有任何效果
Vary过滤
[ResponseCache(VaryByHeader = "User-Agent", Duration = 5)] public IActionResult About() { ViewBag.time = DateTime.Now.ToString("yyyy年MM月dd日 HH时mm分ss秒"); return View(); }
关于vary在Http响应头的作用就是:告诉缓存服务器或者CDN,我还是同一个浏览器的请求,你给我缓存就行了,如果你换个浏览器去请求,那么vary的值肯定为空,那么缓存服务器就会认为你是一个新的请求,就会去读取最新的数据给浏览器
参考资料:http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
禁用缓存(NoStore 和 Location.None)
在Http中 :no-store,请求和响应的信息都不应该被存储在对方的磁盘系统中
[ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] public IActionResult About() { ViewBag.time = DateTime.Now.ToString("yyyy年MM月dd日 HH时mm分ss秒"); return View(); }
ResponseCacheLocation.None是在Cache-Control设置一个no-cache属性,让浏览器不缓存当前这个URL
缓存配置(CacheProfiles)
在一个正常的项目中,肯定有很多个控制器,但是不可能每个控制器的缓存策略都一样,这时候,我们就需要一个缓存的配置来灵活应对这个问题
在mvc的服务注入的时候,我们可以在option里面注入进我们的缓存策略
services.AddMvc(option=> { option.CacheProfiles.Add("test1", new CacheProfile() { Duration = 5 }); option.CacheProfiles.Add("test2", new CacheProfile() { Location = ResponseCacheLocation.None, NoStore = true }); });
然后我们在使用的时候,直接使用配置策略的名称就好了
[ResponseCache(CacheProfileName = "test1")] public IActionResult About() { ViewBag.time = DateTime.Now.ToString("yyyy年MM月dd日 HH时mm分ss秒"); return View(); }
这样我们就能和之前在特性后边配置一样了,这是视图缓存,下面我们就来看看MemoryCache 是个什么东东
MemoryCache
如果回到老版本的.NET,说到内存缓存大家可能立马想到了HttpRuntime.Cache,它位于System.Web命名空间下,但是在ASP.NET Core中System.Web已经不复存在。今儿个就简单的聊聊如何在ASP.NET Core中使用内存缓存
有几个问题我们需要先进行了解:
1.什么时候需要用到缓存?
一般将经常访问但是又不是经常改变的数据放进缓存是再好不过了,这样可以明显提高应用程序的性能。
2.缓存的好处?
建议http://www.baidu.com
使用
不同于 ASP.NET Web 窗体和 ASP.NET MVC,ASP.NET Core 没有内置的 Cache 对象,可以拿来在控制器里面直接使用。 这里,内存缓存时通过依赖注入来启用的,因此第一步就是在 Startup 类中注册内存缓存的服务。如此,就得打开 Startup 类然后定位到 ConfigureServices() 方法,像下面这样修改 ConfigureServices() 方法:
①首先需要在ConfigureServices中注册缓存服务
services.AddMemoryCache()
为了向你的应用程序加入内存缓存能力,你需要在服务集合上调用 AddMemoryCache() 方法。采用这种办法就可以让一个内存缓存(它是一个 IMemoryCache 对象)的默认实现可以被注入到控制器中去。
②在下面的代码中从Home控制器的构造函中获取IMemoryCache实例(内存缓存使用依赖注入来注入缓存对象)
private readonly IMemoryCache _memoryCache; public HomeController(IMemoryCache memoryCache) { _memoryCache = memoryCache; }
如你所见,上述代码声明了一个 ImemoryCache 的私有变量。该变量会被构造器中被赋值。构造器会通过 DI(依赖注入)接收到缓存参数,然后被存储在本地变量总,提供后续使用。
③关于缓存的使用常用的就是Set Get Remove,一般有以下几种做法可以参考:
⑴可以使用 Set() 方法来在缓存中存东西
等你有了这个 IMemoryCache 对象,就可以读取或者向它写入数据了。向缓存写入数据项是相当直接的
public IActionResult Index() { _memoryCache.Set<string>("timestamp", DateTime.Now.ToString()); return View(); }
上述代码在 Index() 这个 action 中设置了一个缓存项。这是通过使用 IMemoryCache 的 Set<T>() 来完成的。Set() 方法的第一个参数是键名,用来标识该数据项。第二个参数是键的取值。在此例中,我们存储一个字符串的键和一个字符串的值,而你也可以存储其它类型 (原生以及自定义的类型) 的键值对。
⑵可以使用 Get 方法来从缓存中获取到一个数据项
等你向缓存中添加好了数据,也许会想要在应用程序的其它地方去获取到该数据,可以用 Get() 来做到。如下代码会告诉你如何来做这件事情。
public IActionResult Show() { string timestamp = _memoryCache.Get<string>("timestamp"); return View("Show",timestamp); }
上述代码从 HomeController 的另外一个action(Show)那里获取到了一个缓存的数据项。Get() 方法会指定数据项的类型以及它的键名。如果该数据项存在的话,就会被返回并且被赋值给 timestamp 这个字符串变量。然后这个 timestamp 的值就会被传递给 Show 视图。
Show 视图只是简单地输出了 timestamp 的值,如下所示:
<h1>TimeStamp : @Model</h1> <h2>@Html.ActionLink("Go back", "Index", "Home")</h2>
如果你观察前面的示例,会发现每次你导航至 /Home/Index 的时候, 都会有一个新的 timestamp 被赋值给了缓存项。这是因为我们并没有对此进行检查,规定只有在数据项不存在的时候才赋值。许多时候你都会想要这样做的。这里有两种办法可以在 Index() 这个 action 里面来做这样的检查。我们把两种办法都在下面列了出来
可以使用 TryGet() 来检查缓存中是否存在特定的键值
//first way if (string.IsNullOrEmpty (_memoryCache.Get<string>("timestamp"))) { _memoryCache.Set<string>("timestamp", DateTime.Now.ToString()); } //second way if (!_memoryCache.TryGetValue<string> ("timestamp", out string timestamp)) { _memoryCache.Set<string>("timestamp", DateTime.Now.ToString()); }
第一种办法使用了你早先用过的同一个 Get() 方法,这一次它被拿来跟 if 块一起用。如果 Get() 不能在缓存中找到指定的数据项,IsNullOrEmpty() 就会返回 true。而只有这时候 Set() 才会被调用,一次来添加数据项。
第二种办法更加优雅一点。它使用 TryGet() 方法来获取一个数据项。TryGet() 方法会返回一个布尔值来指明数据项有没有被找到。实际的数据项可以使用一个输出参数拉取出来。如果 TryGet() 返回false,Set() 就会被用来添加数据。
如果不存在的话,可以使用 GetOrCreate() 来添加一项
有时你需要从缓存中检索现有项。如果该项目不存在,则希望添加该项。这两个任务 - 如果它存在获取值,否则创建之 - 可以使用 GetOrCreate() 方法来实现。修改后的 Show() 方法展示了如何实现的
public IActionResult Show() { string timestamp = cache.GetOrCreate<string> ("timestamp", entry => { return DateTime.Now.ToString(); }); return View("Show",timestamp); }
Show() 动作现在使用 GetOrCreate() 方法。 GetOrCreate() 方法将检查时间戳的键值是否存在。如果是,现有值将被赋值给局部变量。否则,将根据第二个参数中指定的逻辑创建一个新条目并将其添加到缓存中。
在缓存的数据项上面设置绝对和滚动的过期时间
一个缓存项只要被添加到缓存就会一直存储,除非它被明确地使用 Remove() 从缓存中移除。你也可以在一个缓存项上面设置一个绝对和滚动的过期时间。一个绝对的过期设置意味着该缓存项会在严格指定的日期和时间点被移除,而滚动过期设置则意味着它在给定的一段时间量处于空闲状态(也就是没人去访问)之后被移除。
为了能在一个缓存项上面设置这两种过期策略,你要用到 MemoryCacheEntryOptions 对象。如下代码向你展示了如何去使用。
MemoryCacheEntryOptions options = new MemoryCacheEntryOptions(); options.AbsoluteExpiration = DateTime.Now.AddMinutes(1); options.SlidingExpiration = TimeSpan.FromMinutes(1); _memoryCache.Set<string>("timestamp", DateTime.Now.ToString(), options);
上述代码来自于修改过的 Index() action,它创建了一个 MemoryCacheEntryOptions 的对象,然后将它的 AbsoluteExpiration 属性设置为从此刻到一分钟之后的一个 DateTime 值,它还将 SlidingExpiration 属性设置为一分钟。这些值都指定了该缓存项会在一分钟之后从缓存移除,不管其是否会被访问。此外,如果该缓存项如初持续空闲了有一分钟,它也会被从缓存中移除。
等你将 AbsoluteExpiration 和 SlidingExpiration 的值设置后, Set() 方法就可以被用来将一个数据项添加到缓存。这一次 MemoryCacheEntryOptions 对象会被作为第三个参数传递给 Set() 方法。
当缓存项会被移除时,可以连接回调
有时你会想要在缓存项从缓存中被移除时收到通知。可能会有多种原因需要从缓存中移除数据项。例如,因为明确地执行了 Remove() 方法而移除了一个缓存项, 也有可能是因为它的 AbsoluteExpiration 和 SlidingExpiration 值已经到期而被移除,诸如此类的原因。
为了能知道项目是何时从缓存移除的,你需要编写一个缓存函数。如下代码向你展示了如何去做这件事情
MemoryCacheEntryOptions options = new MemoryCacheEntryOptions(); options.AbsoluteExpiration =DateTime.Now.AddMinutes(1);options.SlidingExpiration = TimeSpan.FromMinutes(1); options.RegisterPostEvictionCallback(MyCallback, this); _memoryCache.Set<string>("timestamp", DateTime.Now.ToString(), options); private static void MyCallback(object key, object value,EvictionReason reason, object state) { var message = $"Cache entry was removed : {reason}"; ((HomeController)state). _memoryCache.Set("callbackMessage", message); }
请仔细观察这段代码。 MyCallback() 是 HomeController 类里面的一个私有静态函数,它有四个参数。前面两个参数表示刚刚删除的缓存项的键和值,第三个参数表示的是该数据项被删除的原因。EvictionReason 是一个枚举类型,它维护者各种可能的删除原因,如过期,删除以及替换。
在回调函数的内部,我们会基于删除的原因构造一个字符串消息。我们想要将此消息设置成另外一个缓存项。这样做的话就需要访问
HomeController 的缓存对象,此时状态参数就可以排上用场了。使用状态对象,你可以对 HomeController
的缓存对象进行控制,并使用 Set() 增加一个 callbackMessage 缓存项。
你可以通过 Show() 这个 action 来访问到 callbackMessage,如下所示:
public IActionResult Show() { string timestamp = cache.Get<string>("timestamp"); ViewData["callbackMessage"] = _memoryCache.Get<string>("callbackMessage"); return View("Show",timestamp); }
最后就可以在 Show 视图中显示出来了:
<h1>TimeStamp : @Model</h1> <h3>@ViewData["callbackMessage"]</h3> <h2>@Html.ActionLink("Go back", "Index", "Home")</h2>
一些建议,像上面提到的设置缓存数据项的过期时间那块,如果一个项目中所有的缓存过期时间是一致的,我们可以有更简单的做法,而不是每个地方都去写一堆这个:
MemoryCacheEntryOptions options = new MemoryCacheEntryOptions(); options.AbsoluteExpiration = DateTime.Now.AddMinutes(1); options.SlidingExpiration = TimeSpan.FromMinutes(1);
可以在头部定义一个共有的
readonly MemoryCacheEntryOptions _options = Cache.GetMemoryCacheEntryOptions();
Cache是自定义的一个缓存类,其中GetMemoryCacheEntryOptions就是获取当前缓存设置项的,代码如下:
public static MemoryCacheEntryOptions GetMemoryCacheEntryOptions() { var options = new MemoryCacheEntryOptions { AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(double.Parse($"{SiteConfig.GetSite("ExpiredTime")}")), SlidingExpiration = TimeSpan.FromMinutes(double.Parse($"{SiteConfig.GetSite("ExpiredTime")}")) }; return options; }
这里面我们将过期时间放到配置文件中,这里读取配置文件可以参考前面的博客:https://www.cnblogs.com/zhangxiaoyong/p/9411036.html
这样,我们的写法就可以简写很多,当然,刚才提到了是使用的 IMemoryCache 的 Set<T>() 来完成的,所以,这里我们不仅可以传String,还可以按需传递,比如:
var homeCache = _memoryCache.Get<Task<HomeInfo>>("HomeCache");//获取HomeCache if (homeCache == null)//判断是否存在 { homeCache = _dadaServices.GetHomeData();//调用API获取数据 _memoryCache.Set<Task<HomeInfo>>("HomeCache", homeCache, _options);//存放HomeCache,传入data数据和设置的数据项 } return View("~/Views/Home/Index.cshtml", homeCache.Result);//返回Cache
到这里,你已经大概知道了MemoryCache 的简单使用方式,剩下的就自行研究吧~
- 感谢你的阅读。如果你觉得这篇文章对你有帮助或者有启发,就请推荐一下吧~你的精神支持是博主强大的写作动力。欢迎转载!
- 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。
- 欢迎加入.NET 从入门到精通技术讨论群→523490820 期待你的加入
- 不舍得打乱,就永远学不会复原。被人嘲笑的梦想,才更有实现的价值。
- 我的博客:http://www.cnblogs.com/zhangxiaoyong/