缓存在内存中 ASP.NET Core

作者:Rick AndersonJohn Luo,和Steve Smith

查看或下载示例代码如何下载

缓存基础知识

通过减少生成内容所需的工作,缓存可以显著提高应用的性能和可伸缩性。 缓存最适用于不经常更改的数据,生成成本很高。 通过缓存,可以比从数据源返回的数据的副本速度快得多。 应该对应用进行编写和测试,使其永不依赖于缓存的数据。

ASP.NET Core 支持多种不同的缓存。 最简单的缓存基于IMemoryCache IMemoryCache 表示存储在 web 服务器的内存中的缓存。 使用内存中缓存时,在服务器场(多台服务器)上运行的应用应确保会话是粘滞的。 粘性会话可确保来自客户端的后续请求都转到同一台服务器。 例如,Azure Web 应用使用应用程序请求路由(ARR) 将所有的后续请求路由到同一台服务器。

Web 场中的非粘性会话需要分布式缓存以避免缓存一致性问题。 对于某些应用,分布式缓存可支持比内存中缓存更高的向外扩展。 使用分布式缓存可将缓存内存卸载到外部进程。

内存中缓存可以存储任何对象。 分布式缓存接口仅限 byte[] 内存中和分布式缓存将缓存项作为键值对。

System.Runtime.Caching/MemoryCache

System.Runtime.Caching/MemoryCacheNuGet 包)可用于:

  • .NET Standard 2.0 或更高版本。
  • 面向 .NET Standard 2.0 或更高版本的任何.net 实现 例如,ASP.NET Core 2.0 或更高版本。
  • .NET Framework 4.5 或更高版本。

建议对 System.Runtime.Caching/MemoryCache 使用 Microsoft.Extensions.Caching.Memory/IMemoryCache (本文中所述), 因为它更好地集成到 ASP.NET Core 中。 例如,IMemoryCache 与 ASP.NET Core依赖关系注入一起使用。

将 ASP.NET 4.x 中的代码移植到 ASP.NET Core 时,使用 System.Runtime.Caching/MemoryCache 作为兼容性桥。

缓存指南

  • 代码应始终具有回退选项,以获取数据,而是依赖于可用的缓存值。
  • 缓存使用稀有资源内存。 限制缓存增长:

使用 IMemoryCache

警告

使用依赖关系注入中的共享内存缓存并调用 SetSizeSizeSizeLimit 来限制缓存大小可能会导致应用程序失败。 在缓存上设置大小限制时,在添加时,所有项都必须指定大小。 这可能会导致问题,因为开发人员可能无法完全控制使用共享缓存的内容。 例如,Entity Framework Core 使用共享缓存并且未指定大小。 如果应用设置了缓存大小限制并使用 EF Core,则应用将引发 InvalidOperationException 使用 SetSizeSizeSizeLimit 限制缓存时,为缓存创建一个缓存单独。 有关详细信息和示例,请参阅使用 SetSize、Size 和 SizeLimit 限制缓存大小 共享缓存由其他框架或库共享。 例如,EF Core 使用共享缓存并且未指定大小。

内存中缓存是从应用程序中使用依赖关系注入引用的一种服务 在构造函数中请求 IMemoryCache实例:

public class HomeController : Controller
{
    private IMemoryCache _cache;

    public HomeController(IMemoryCache memoryCache)
    {
        _cache = memoryCache;
    }

以下代码使用TryGetValue来检查缓存中是否有时间。 如果未缓存时间,则将创建一个新条目,并将其设置为已添加到缓存中。 CacheKeys 类是下载示例的一部分。

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="CacheGetOrCreateAsynchronous">CacheGetOrCreateAsynchronous</a></li>
        <li><a asp-controller="Home" asp-action="CacheRemove">Remove</a></li>
        <li><a asp-controller="Home" asp-action="CacheGetOrCreateAbs">CacheGetOrCreateAbs</a></li>
        <li><a asp-controller="Home" asp-action="CacheGetOrCreateAbsSliding">CacheGetOrCreateAbsSliding</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>

如果在超时期限内存在请求,则缓存 DateTime 值将保留在缓存中。

以下代码使用GetOrCreateGetOrCreateAsync来缓存数据。

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> CacheGetOrCreateAsynchronous()
{
    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);
}

下面的代码获取或创建具有绝对过期的缓存项:

public IActionResult CacheGetOrCreateAbs()
{
    var cacheEntry = _cache.GetOrCreate(CacheKeys.Entry, entry =>
    {
        entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(10);
        return DateTime.Now;
    });

    return View("Cache", cacheEntry);
}

只有具有可调过期的缓存项集存在过时的风险。 如果访问的时间比滑动过期时间间隔更频繁,则该项将永不过期。 将弹性过期与绝对过期组合在一起,以保证项目在其绝对过期时间通过后过期。 绝对过期会将项的上限设置为可缓存项的时间,同时仍允许项在可调整过期时间间隔内未请求时提前过期。 如果同时指定了绝对过期和可调过期时间,则过期时间以逻辑方式运算。 如果滑动过期时间间隔绝对过期时间通过,则从缓存中逐出该项。

下面的代码获取或创建具有可调绝对过期的缓存项:

public IActionResult CacheGetOrCreateAbsSliding()
{
    var cacheEntry = _cache.GetOrCreate(CacheKeys.Entry, entry =>
    {
        entry.SetSlidingExpiration(TimeSpan.FromSeconds(3));
        entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(20);
        return DateTime.Now;
    });

    return View("Cache", cacheEntry);
}

前面的代码保证数据的缓存时间不超过绝对时间。

GetOrCreateGetOrCreateAsyncGetCacheExtensions 类中的扩展方法。 这些方法扩展了 IMemoryCache的功能。

MemoryCacheEntryOptions

下面的示例执行以下操作:

  • 设置可调过期时间。 访问此缓存项的请求将重置可调过期时钟。
  • 将缓存优先级设置为CacheItemPriority. NeverRemove
  • 设置一个PostEvictionDelegate它将在条目从缓存中清除后调用。 在代码中运行该回调的线程不同于从缓存中移除条目的线程。
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);
}

使用 SetSize、Size 和 SizeLimit 限制缓存大小

MemoryCache 实例可以选择指定并强制实施大小限制。 缓存大小限制没有定义的度量单位,因为缓存没有度量条目大小的机制。 如果设置了缓存大小限制,则所有条目都必须指定 size。 ASP.NET Core 运行时不会根据内存压力限制缓存大小。 开发人员需要限制缓存大小。 指定的大小以开发人员选择的单位为单位。

例如:

  • 如果 web 应用主要是缓存字符串,则每个缓存条目大小都可以是字符串长度。
  • 应用可以将所有条目的大小指定为1,而大小限制则为条目的计数。

如果未设置 SizeLimit,则缓存将不受限制。 当系统内存不足时,ASP.NET Core 运行时不会剪裁缓存。 应用必须构建为:

  • 限制缓存增长。
  • 如果可用内存有限,请调用 CompactRemove

下面的代码创建一个无单位固定大小 MemoryCache 可通过依赖关系注入进行访问:

// using Microsoft.Extensions.Caching.Memory;
public class MyMemoryCache 
{
    public MemoryCache Cache { get; set; }
    public MyMemoryCache()
    {
        Cache = new MemoryCache(new MemoryCacheOptions
        {
            SizeLimit = 1024
        });
    }
}

SizeLimit 没有单位。 如果已设置缓存大小限制,则缓存条目必须以其认为最适合的任何单位指定大小。 缓存实例的所有用户都应使用同一单元系统。 如果缓存条目大小的总和超出 SizeLimit指定的值,则不会缓存条目。 如果未设置任何缓存大小限制,则将忽略在该项上设置的缓存大小。

下面的代码向依赖关系注入容器注册 MyMemoryCache

public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();
    services.AddSingleton<MyMemoryCache>();
}

对于识别此大小限制缓存并知道如何适当设置缓存条目大小的组件,MyMemoryCache 创建为独立的内存缓存。

以下代码使用 MyMemoryCache

public class SetSize : PageModel
{
    private MemoryCache _cache;
    public static readonly string MyKey = "_MyKey";

    public SetSize(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");
    }
}

缓存项的大小可以 SizeSetSize 扩展方法进行设置:

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");
}

MemoryCache

MemoryCache.Compact 尝试按以下顺序删除缓存的指定百分比:

  • 所有过期项。
  • 按优先级排序。 首先删除最低优先级项。
  • 最近最少使用的对象。
  • 绝对过期的项。
  • 具有最早的可调过期项的项。

永远不会删除优先级为 NeverRemove 的固定项。 以下代码将删除缓存项并调用 Compact

_cache.Remove(MyKey);

// Remove 33% of cached items.
_cache.Compact(.33);   
cache_size = _cache.Count;

有关详细信息,请参阅GitHub 上的 Compact 源

缓存依赖关系

以下示例演示在依赖项过期时如何使缓存项过期。 会将 CancellationChangeToken添加到缓存项。 Cancel上调用 CancellationTokenSource,时,这两个缓存条目都将被清除。

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可以将多个缓存条目作为一个组来清除。 使用上面代码中的 using模式时using块中创建的缓存条目会继承触发器和过期时间设置。

附加说明

  • 不会在后台进行过期。 没有计时器可主动扫描过期项目的缓存。 缓存中的任何活动(GetSetRemove)都可以触发过期项的后台扫描。 CancellationTokenSourceCancelAfter)上的计时器还会删除项,并触发扫描过期项。 下面的示例使用CancellationTokenSource (TimeSpan)作为已注册令牌。 此令牌激发后,会立即删除该条目,并激发逐出回调:
public IActionResult CacheAutoExpiringTryGetValueSet()
{
    DateTime cacheEntry;

    if (!_cache.TryGetValue(CacheKeys.Entry, out cacheEntry))
    {
        cacheEntry = DateTime.Now;

        var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));

        var cacheEntryOptions = new MemoryCacheEntryOptions()
            .AddExpirationToken(new CancellationChangeToken(cts.Token));

        _cache.Set(CacheKeys.Entry, cacheEntry, cacheEntryOptions);
    }

    return View("Cache", cacheEntry);
}
  • 使用回调重新填充缓存项时:

    • 多个请求可能会发现缓存的键值为空,因为回调尚未完成。
    • 这可能会导致多个线程重新填充缓存的项。
  • 使用一个缓存条目创建另一个缓存条目时,子条目会复制父条目的过期令牌以及基于时间的过期设置。 手动删除或更新父项时,子级不会过期。

  • 使用 PostEvictionCallbacks 设置从缓存中逐出缓存项后将触发的回调。

  • 对于大多数应用,IMemoryCache 已启用。 例如,在 Add{Service} 中调用 AddMvcAddControllersWithViewsAddRazorPagesAddMvcCore().AddRazorViewEngine以及许多其他 ConfigureServices方法会启用 IMemoryCache 对于未调用上述某个 Add{Service} 方法的应用,可能需要在 ConfigureServices调用 AddMemoryCache

其他资源

作者:Rick AndersonJohn Luo,和Steve Smith

查看或下载示例代码如何下载

缓存基础知识

通过减少生成内容所需的工作,缓存可以显著提高应用的性能和可伸缩性。 缓存对不经常更改的数据效果最佳。 缓存生成的数据副本的返回速度可以比从原始源返回更快。 应编写并测试代码,使其绝不会依赖于缓存的数据。

ASP.NET Core 支持多种不同的缓存。 最简单的缓存基于 IMemoryCache,它表示存储在 Web 服务器内存中的缓存。 在服务器场中运行的应用(多台服务器)应确保会话在使用内存中缓存时处于粘滞。 粘滞会话确保以后来自客户端的请求都发送到相同的服务器。 例如,Azure Web 应用使用应用程序请求路由(ARR)将来自用户代理的所有请求路由到同一服务器。

Web 场中的非粘性会话需要分布式缓存以避免缓存一致性问题。 对于某些应用,分布式缓存可支持比内存中缓存更高的向外扩展。 使用分布式缓存可将缓存内存卸载到外部进程。

内存中缓存可以存储任何对象。 分布式缓存接口仅限 byte[] 内存中和分布式缓存将缓存项作为键值对。

System.Runtime.Caching/MemoryCache

System.Runtime.Caching/MemoryCacheNuGet 包)可用于:

  • .NET Standard 2.0 或更高版本。
  • 面向 .NET Standard 2.0 或更高版本的任何.net 实现 例如,ASP.NET Core 2.0 或更高版本。
  • .NET Framework 4.5 或更高版本。

建议对 System.Runtime.Caching/MemoryCache 使用 Microsoft.Extensions.Caching.Memory/IMemoryCache (本文中所述), 因为它更好地集成到 ASP.NET Core 中。 例如,IMemoryCache 与 ASP.NET Core依赖关系注入一起使用。

将 ASP.NET 4.x 中的代码移植到 ASP.NET Core 时,使用 System.Runtime.Caching/MemoryCache 作为兼容性桥。

缓存指南

  • 代码应始终具有回退选项,以获取数据,而是依赖于可用的缓存值。
  • 缓存使用稀有资源内存。 限制缓存增长:

使用 IMemoryCache

警告

使用依赖关系注入中的共享内存缓存并调用 SetSizeSizeSizeLimit 来限制缓存大小可能会导致应用程序失败。 在缓存上设置大小限制时,在添加时,所有项都必须指定大小。 这可能会导致问题,因为开发人员可能无法完全控制使用共享缓存的内容。 例如,Entity Framework Core 使用共享缓存并且未指定大小。 如果应用设置了缓存大小限制并使用 EF Core,则应用将引发 InvalidOperationException 使用 SetSizeSizeSizeLimit 限制缓存时,为缓存创建一个缓存单独。 有关详细信息和示例,请参阅使用 SetSize、Size 和 SizeLimit 限制缓存大小

内存中缓存是使用依赖关系注入从应用中引用的服务。 请在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();
    }
}

在构造函数中请求 IMemoryCache实例:

public class HomeController : Controller
{
    private IMemoryCache _cache;

    public HomeController(IMemoryCache memoryCache)
    {
        _cache = memoryCache;
    }

IMemoryCache需要 NuGet 包Microsoft.Extensions.Caching.Memory, 此包可在Microsoft.AspNetCore.App metapackage中找到的。

以下代码使用TryGetValue来检查缓存中是否有时间。 如果未缓存时间,则将创建一个新条目,并将其设置为已添加到缓存中。

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>

如果在超时期限内存在请求,则缓存 DateTime 值将保留在缓存中。 下图显示当前时间以及从缓存中检索的较早时间:

显示了两个不同时间的索引视图

以下代码使用GetOrCreateGetOrCreateAsync来缓存数据。

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);
}

GetOrCreateGetOrCreateAsyncGet是扩展方法的一部分,扩展方法是扩展 IMemoryCache功能的CacheExtensions类的一部分。 有关其他缓存方法的说明,请参阅IMemoryCache 方法CacheExtensions 方法

MemoryCacheEntryOptions

下面的示例执行以下操作:

  • 设置可调过期时间。 访问此缓存项的请求将重置可调过期时钟。
  • 将缓存优先级设置为 CacheItemPriority.NeverRemove
  • 设置一个PostEvictionDelegate它将在条目从缓存中清除后调用。 在代码中运行该回调的线程不同于从缓存中移除条目的线程。
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);
}

使用 SetSize、Size 和 SizeLimit 限制缓存大小

MemoryCache 实例可以选择指定并强制实施大小限制。 缓存大小限制没有定义的度量单位,因为缓存没有度量条目大小的机制。 如果设置了缓存大小限制,则所有条目都必须指定 size。 ASP.NET Core 运行时不会根据内存压力限制缓存大小。 开发人员需要限制缓存大小。 指定的大小以开发人员选择的单位为单位。

例如:

  • 如果 web 应用主要是缓存字符串,则每个缓存条目大小都可以是字符串长度。
  • 应用可以将所有条目的大小指定为1,而大小限制则为条目的计数。

如果未设置 SizeLimit,则缓存将不受限制。 当系统内存不足时,ASP.NET Core 运行时不会剪裁缓存。 应用程序的体系结构非常多:

  • 限制缓存增长。
  • 如果可用内存有限,请调用 CompactRemove

下面的代码创建一个无单位固定大小 MemoryCache 可通过依赖关系注入进行访问:

// using Microsoft.Extensions.Caching.Memory;
public class MyMemoryCache 
{
    public MemoryCache Cache { get; set; }
    public MyMemoryCache()
    {
        Cache = new MemoryCache(new MemoryCacheOptions
        {
            SizeLimit = 1024
        });
    }
}

SizeLimit 没有单位。 如果已设置缓存大小限制,则缓存条目必须以其认为最适合的任何单位指定大小。 缓存实例的所有用户都应使用同一单元系统。 如果缓存条目大小的总和超出 SizeLimit指定的值,则不会缓存条目。 如果未设置任何缓存大小限制,则将忽略在该项上设置的缓存大小。

下面的代码向依赖关系注入容器注册 MyMemoryCache

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

    services.AddSingleton<MyMemoryCache>();
}

对于识别此大小限制缓存并知道如何适当设置缓存条目大小的组件,MyMemoryCache 创建为独立的内存缓存。

以下代码使用 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");
    }
}

缓存项的大小可以通过大小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");
}

MemoryCache

MemoryCache.Compact 尝试按以下顺序删除缓存的指定百分比:

  • 所有过期项。
  • 按优先级排序。 首先删除最低优先级项。
  • 最近最少使用的对象。
  • 绝对过期的项。
  • 具有最早的可调过期项的项。

永远不会删除优先级为 NeverRemove 的固定项。

_cache.Remove(MyKey);

// Remove 33% of cached items.
_cache.Compact(.33);   
cache_size = _cache.Count;

有关详细信息,请参阅GitHub 上的 Compact 源

缓存依赖关系

以下示例演示在依赖项过期时如何使缓存项过期。 会将 CancellationChangeToken添加到缓存项。 Cancel上调用 CancellationTokenSource,时,这两个缓存条目都将被清除。

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可以将多个缓存条目作为一个组来清除。 使用上面代码中的 using模式时using块中创建的缓存条目会继承触发器和过期时间设置。

附加说明

  • 使用回调重新填充缓存项时:

    • 多个请求可能会发现缓存的键值为空,因为回调尚未完成。
    • 这可能会导致多个线程重新填充缓存的项。
  • 使用一个缓存条目创建另一个缓存条目时,子条目会复制父条目的过期令牌以及基于时间的过期设置。 手动删除或更新父项时,子级不会过期。

  • 使用PostEvictionCallbacks设置从缓存中逐出缓存项后将触发的回调。

其他资源

上一篇:响应缓存在 ASP.NET Core

下一篇:ASP.NET Core 中的分布式缓存

关注微信小程序
程序员编程王-随时随地学编程

扫描二维码
程序员编程王

扫一扫关注最新编程教程