这篇文章(主要翻译于官网,水平有限,见谅)讲解asp.net core 中的 Cache in-memory (内存缓存).web
Caching 能够显著的提高应用的performance(表现) 和 scalability,经过减小生成内容所必需的作的工做。Caching 在变更比较的数据上工做的最好。Caching 能够作一个备份数据,使得数据比从原来的地方取的快一些。api
ASP.NET Core支持几种不一样的缓存。最简单的缓存是基于IMemoryCache, 它表明一个存储在web服务器的内存上的cache(缓存)。当使用in-memory cache时,运行在多个服务器上的服务器集群应该确保sessions是不动的,不动的sessions(Sticky sessions)确保随后的从一个client发来的请求全都到同一台服务器。例如,Azure Web apps用Application Request Routing(ARR)来路由全部随后的请求到同一个服务器。缓存
在一个web集群上的Non-sticky sessions 要求一个distributed cache(分布式缓存)来避免缓存一致性问题。对于一些应用,a distributed cache 能够支持更高的扩展比in-memory cache. 用一个分布式缓存卸载内存缓存到一个外部处理中。服务器
In-memory cache 能够存储任意对象;distributed cache interface 仅限于byte[]. 对于in-memory和distributed cache 存储cache items为key-value pairs. session
System.Runtime.Caching/MemoryCache能够被用在:app
.NET Standard 2.0 or laterasp.net
Any .NET implementation that targets .NET Standard 2.0 or later. 例如, ASP.NET Core 2.0 or laterless
.NET Framework 4.5 or laterasync
Microsoft.Extensions.Caching.Memory/IMemoryCache 被推荐在System.Runtime.Cachign/MemoryCache之上使用, 由于Microsoft.Extensions.Caching.Memory/IMemoryCache是更好的集成在ASP.NET Core中。例如,IMemory 天生能够用ASP.NET Core的依赖注入工做。分布式
用System.Runtime.Caching/MemoryCache做为一个兼容桥梁,当移植代码从ASP.NET 4.X 到ASP.NET Core时。
代码应该总有一个可靠的选项来取数据而且不是依赖于缓存的可获得的值
缓存使用稀少的资源,内存。限制缓存增加(cache growth)(内存是稀缺资源, 若是在内存中使用缓存,须要限制缓存增加):
不要使用外部输入做为cache keys.
使用expirations(过时时间)限制缓存增加
使用SetSize, Size和SizeLimit来限制cache size. ASP.NET Core runtime不会根据memory pressure(内存压力)来限制cache size,它取决于开发者限制cache size.
In-memory caching 是一个从你的应用中使用依赖注入引入的服务(service)。在ConfigureServices中调用 AddMemoryCache:
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddMemoryCache(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } public void Configure(IApplicationBuilder app) { app.UseMvcWithDefaultRoute(); } }
Request the IMemoryCache实例在构造函数中:
public class HomeController : Controller { private IMemoryCache _cache; public HomeController(IMemoryCache memoryCache) { _cache = memoryCache; }
IMemoryCache要求有NuGet package Microsoft.Extensions.Caching.Memory, 它在Microsoft.AspNetCore.App metapackage也是可用的。
下面的代码使用 TryGetValue 来检验 if a time is in the cache. If a time isn’t cached, a new entry is created and added to the cache with Set. 检验一个时间值是否在缓存中。若是时间值没有被缓存,一个新的entry被建立而且with Set加入到缓存中。(即,若是没被缓存,则加入缓存)
public static class CacheKeys { public static string Entry { get { return "_Entry"; } } public static string CallbackEntry { get { return "_Callback"; } } public static string CallbackMessage { get { return "_CallbackMessage"; } } public static string Parent { get { return "_Parent"; } } public static string Child { get { return "_Child"; } } public static string DependentMessage { get { return "_DependentMessage"; } } public static string DependentCTS { get { return "_DependentCTS"; } } public static string Ticks { get { return "_Ticks"; } } public static string CancelMsg { get { return "_CancelMsg"; } } public static string CancelTokenSource { get { return "_CancelTokenSource"; } } }
public IActionResult CacheTryGetValueSet() { DateTime cacheEntry; // Look for cache key. if (!_cache.TryGetValue(CacheKeys.Entry, out cacheEntry)) { // Key not in cache, so get data. cacheEntry = DateTime.Now; // Set cache options. var cacheEntryOptions = new MemoryCacheEntryOptions() // Keep in cache for this time, reset time if accessed. .SetSlidingExpiration(TimeSpan.FromSeconds(3)); // Save data in cache. _cache.Set(CacheKeys.Entry, cacheEntry, cacheEntryOptions); } return View("Cache", cacheEntry); }
当前时间和缓存时间都被展现了:
@model DateTime? <div> <h2>Actions</h2> <ul> <li><a asp-controller="Home" asp-action="CacheTryGetValueSet">TryGetValue and Set</a></li> <li><a asp-controller="Home" asp-action="CacheGet">Get</a></li> <li><a asp-controller="Home" asp-action="CacheGetOrCreate">GetOrCreate</a></li> <li><a asp-controller="Home" asp-action="CacheGetOrCreateAsync">GetOrCreateAsync</a></li> <li><a asp-controller="Home" asp-action="CacheRemove">Remove</a></li> </ul> </div> <h3>Current Time: @DateTime.Now.TimeOfDay.ToString()</h3> <h3>Cached Time: @(Model == null ? "No cached entry found" : Model.Value.TimeOfDay.ToString())</h3>
当requests在超时时间以内时,缓存的时间值保留在缓存中。下面的图片展现了当前时间和从缓存中检索的更早的时间。
下面的代码使用GetOrCreate和GetOrCreateAsync来缓存数据。
public IActionResult CacheGetOrCreate() { var cacheEntry = _cache.GetOrCreate(CacheKeys.Entry, entry => { entry.SlidingExpiration = TimeSpan.FromSeconds(3); return DateTime.Now; }); return View("Cache", cacheEntry); } public async Task<IActionResult> CacheGetOrCreateAsync() { var cacheEntry = await _cache.GetOrCreateAsync(CacheKeys.Entry, entry => { entry.SlidingExpiration = TimeSpan.FromSeconds(3); return Task.FromResult(DateTime.Now); }); return View("Cache", cacheEntry); }
下面的代码调用Get来取到缓存时间:
public IActionResult CacheGet() { var cacheEntry = _cache.Get<DateTime?>(CacheKeys.Entry); return View("Cache", cacheEntry); }
GetOrCreate, GetOrCreateAsyc, 和Get 是CacheExtensions类的扩展方法的一部分,CacheExtension类扩展了IMemory的能力。
下面的例子(用来设置内存缓存的一些选项):
public IActionResult CreateCallbackEntry() { var cacheEntryOptions = new MemoryCacheEntryOptions() // Pin to cache. .SetPriority(CacheItemPriority.NeverRemove) // Add eviction callback .RegisterPostEvictionCallback(callback: EvictionCallback, state: this); _cache.Set(CacheKeys.CallbackEntry, DateTime.Now, cacheEntryOptions); return RedirectToAction("GetCallbackEntry"); } public IActionResult GetCallbackEntry() { return View("Callback", new CallbackViewModel { CachedTime = _cache.Get<DateTime?>(CacheKeys.CallbackEntry), Message = _cache.Get<string>(CacheKeys.CallbackMessage) }); } public IActionResult RemoveCallbackEntry() { _cache.Remove(CacheKeys.CallbackEntry); return RedirectToAction("GetCallbackEntry"); } private static void EvictionCallback(object key, object value, EvictionReason reason, object state) { var message = $"Entry was evicted. Reason: {reason}."; ((HomeController)state)._cache.Set(CacheKeys.CallbackMessage, message); }
一个MemoryCache实例能够选择指定或者强制一个size limit。 The memory size limit 没有一个定义的测量单元,由于cache没有结构来测量记录(entries)大小(size). 若是cache memory size limit被设置了,全部的entries必须指定size. ASP.NET Core runtime不会根据memory pressure来limit cache size . 它取决于开发者limit cache size. The size spcified is in units the developer chooses.
A MemoryCache
instance may optionally specify and enforce a size limit. The memory size limit does not have a defined unit of measure because the cache has no mechanism to measure the size of entries. If the cache memory size limit is set, all entries must specify size. The ASP.NET Core runtime does not limit cache size based on memory pressure. It's up to the developer to limit cache size. The size specified is in units the developer chooses.
例如:
若是一个web应用主要caching string , 每一个cache entry size应该是字符串长度
应用能够指定the size of all entries (全部的entry)为1,而且这个size limit是the count of entries. (注意:这里指定全部的entry的大小为1,则size limit能够用entry的数量表示。即二者一个是cache entry size(单个entry大小),另外一个是limit size(缓存限制的大小))
下面的代码建立了一个unitless fiexed size MemoryCache accessible(易接近的) by dependency injection:
// using Microsoft.Extensions.Caching.Memory; public class MyMemoryCache { public MemoryCache Cache { get; set; } public MyMemoryCache() { Cache = new MemoryCache(new MemoryCacheOptions { SizeLimit = 1024 }); } }
SizeLimit没有 units . 若是cache memory size 已经被设置,Cached entries 必须指定最合适的size in whatever units they deem(认为)。一个cache 实例的全部用户应该用一样的unit system . 如果the sum of the cached entry sizes 超过经过SizeLimit指定的值, An entry 将不会被缓存. 若是no cache size limit被设置,the cache size set on the entry 将会被忽略。
SizeLimit does not have units. Cached entries must specify size in whatever units they deem most appropriate if the cache memory size has been set. All users of a cache instance should use the same unit system. An entry will not be cached if the sum of the cached entry sizes exceeds the value specified by SizeLimit
. If no cache size limit is set, the cache size set on the entry will be ignored.
下面的代码使用依赖注入容器注册MyMemoryCache.
public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); services.AddSingleton<MyMemoryCache>(); }
MyMemoryCache被建立为一个independent memory cache 的组件,这个组件了解size limited cahce而且知道怎么合适的设置cache entry size 。
下面是使用MyMemoryCache的代码:
public class AboutModel : PageModel { private MemoryCache _cache; public static readonly string MyKey = "_MyKey"; public AboutModel(MyMemoryCache memoryCache) { _cache = memoryCache.Cache; } [TempData] public string DateTime_Now { get; set; } public IActionResult OnGet() { if (!_cache.TryGetValue(MyKey, out string cacheEntry)) { // Key not in cache, so get data. cacheEntry = DateTime.Now.TimeOfDay.ToString(); var cacheEntryOptions = new MemoryCacheEntryOptions() // Set cache entry size by extension method. .SetSize(1) // Keep in cache for this time, reset time if accessed. .SetSlidingExpiration(TimeSpan.FromSeconds(3)); // Set cache entry size via property. // cacheEntryOptions.Size = 1; // Save data in cache. _cache.Set(MyKey, cacheEntry, cacheEntryOptions); } DateTime_Now = cacheEntry; return RedirectToPage("./Index"); } }
The size of the cache entry 能够被设置,经过Size和SetSize扩展方法
public IActionResult OnGet() { if (!_cache.TryGetValue(MyKey, out string cacheEntry)) { // Key not in cache, so get data. cacheEntry = DateTime.Now.TimeOfDay.ToString(); var cacheEntryOptions = new MemoryCacheEntryOptions() // Set cache entry size by extension method. .SetSize(1) // Keep in cache for this time, reset time if accessed. .SetSlidingExpiration(TimeSpan.FromSeconds(3)); // Set cache entry size via property. // cacheEntryOptions.Size = 1; // Save data in cache. _cache.Set(MyKey, cacheEntry, cacheEntryOptions); } DateTime_Now = cacheEntry; return RedirectToPage("./Index"); }
下面的示例展现了怎么设置过时一个缓存记录(how to expire a cache entry)若是一个dependent entry expires(过时). 一个CancellationChangeToken 被加入到cached item. 当Cancel 在CancellationTokenSource上被调用,两个cache entry 都被抛弃.
public IActionResult CreateDependentEntries() { var cts = new CancellationTokenSource(); _cache.Set(CacheKeys.DependentCTS, cts); using (var entry = _cache.CreateEntry(CacheKeys.Parent)) { // expire this entry if the dependant entry expires. entry.Value = DateTime.Now; entry.RegisterPostEvictionCallback(DependentEvictionCallback, this); _cache.Set(CacheKeys.Child, DateTime.Now, new CancellationChangeToken(cts.Token)); } return RedirectToAction("GetDependentEntries"); } public IActionResult GetDependentEntries() { return View("Dependent", new DependentViewModel { ParentCachedTime = _cache.Get<DateTime?>(CacheKeys.Parent), ChildCachedTime = _cache.Get<DateTime?>(CacheKeys.Child), Message = _cache.Get<string>(CacheKeys.DependentMessage) }); } public IActionResult RemoveChildEntry() { _cache.Get<CancellationTokenSource>(CacheKeys.DependentCTS).Cancel(); return RedirectToAction("GetDependentEntries"); } private static void DependentEvictionCallback(object key, object value, EvictionReason reason, object state) { var message = $"Parent entry was evicted. Reason: {reason}."; ((HomeController)state)._cache.Set(CacheKeys.DependentMessage, message); }
使用CancellationTokenSource容许多个cache entries做为一组被抛弃。使用代码中有用的模式,cache entires
Created inside the using block will inherit triggers and expiration settings.
能够发现多个requests缓存的键的value 是空.由于回调没有完成。
这可能会致使几个线程增长cached item
用PostEvictionCallback来设置callback, 这个callback将会被触发。
参考资料:
https://docs.microsoft.com/en-us/aspnet/core/performance/caching/memory?view=aspnetcore-2.2