Кэширование в памяти в ASP.NET Core

Рик Андерсон, Джон Луо и Стив Смит

Кэширование может значительно повысить производительность и масштабируемость приложения, сокращая работу, необходимую для создания содержимого. Кэширование лучше всего работает с данными, которые часто изменяются и являются дорогостоящими для создания. Кэширование делает копию данных, которые могут быть возвращены гораздо быстрее, чем из источника. Приложения должны записываться и тестироваться, чтобы никогда не зависеть от кэшированных данных.

ASP.NET Core поддерживает несколько разных кэшей. Самый простой кэш основан на IMemoryCache. IMemoryCache представляет кэш, хранящийся в памяти веб-сервера. Приложения, работающие на ферме серверов (несколько серверов), должны гарантировать, что сеансы закреплены при использовании кэша в памяти. Липкие сеансы гарантируют, что запросы от клиента отправляются на один и тот же сервер. Например, веб-приложения Azure используют маршрутизацию запросов приложений (ARR) для маршрутизации всех запросов на один и тот же сервер.

Нелипкие сеансы в веб-ферме требуют распределенного кэша , чтобы избежать проблем согласованности кэша. Для некоторых приложений распределенный кэш может поддерживать более высокий масштаб, чем кэш в памяти. Использование распределенного кэша отгрузит память кэша во внешний процесс.

Кэш в памяти может хранить любой объект. Интерфейс распределенного кэша ограничен byte[]. Элементы кэша в памяти и распределенном кэше хранятся в виде пар "ключ-значение".

System.Runtime.Caching/MemoryCache

System.Runtime.Caching/MemoryCache (пакет NuGet) можно использовать с:

  • .NET Standard 2.0 или более поздней версии.
  • Любая реализация .NET, предназначенная для .NET Standard 2.0 или более поздней версии. Например, ASP.NET Core 3.1 или более поздней версии.
  • .NET Framework 4.5 или более поздней версии.

Microsoft.Extensions.Caching.MemoryIMemoryCache/ (описанная в этой статье) рекомендуется использоватьSystem.Runtime.Caching/MemoryCache, так как он лучше интегрирован в ASP.NET Core. Например, IMemoryCache работает в собственном коде с внедрением ASP.NET Core.

Используйте System.Runtime.Caching/MemoryCache в качестве моста совместимости при переносе кода из ASP.NET 4.x в ASP.NET Core.

Рекомендации по кэшу

  • Код всегда должен иметь резервный вариант для получения данных и не зависит от кэшированного значения.
  • Кэш использует дефицитный ресурс, память. Ограничение роста кэша:
    • Не вставлять внешние входные данные в кэш. Например, использование произвольных пользовательских входных данных в качестве ключа кэша не рекомендуется, так как входные данные могут использовать непредсказуемый объем памяти.
    • Используйте срок действия, чтобы ограничить рост кэша.
    • Используйте SetSize, Size и SizeLimit, чтобы ограничить размер кэша. Среда выполнения ASP.NET Core не ограничивает размер кэша на основе давления памяти. Это до разработчика, чтобы ограничить размер кэша.

Использование IMemoryCache

Предупреждение

Использование общего кэша памяти из внедрения зависимостей и вызова SetSizeили SizeSizeLimit ограничения размера кэша может привести к сбою приложения. Если для кэша задано ограничение размера, все записи должны указать размер при добавлении. Это может привести к проблемам, так как разработчики могут не иметь полного контроля над тем, что использует общий кэш. При использовании SetSize( Sizeили SizeLimit для ограничения кэша) создайте одноэлементный кэш для кэширования. Дополнительные сведения и пример см. в разделе Use SetSize, Size и SizeLimit, чтобы ограничить размер кэша. Общий кэш является одним из общих платформ или библиотек.

Кэширование в памяти — это служба , на которую ссылается приложение с помощью внедрения зависимостей. IMemoryCache Запросите экземпляр в конструкторе:

public class IndexModel : PageModel
{
    private readonly IMemoryCache _memoryCache;

    public IndexModel(IMemoryCache memoryCache) =>
        _memoryCache = memoryCache;

    // ...

Следующий код используется TryGetValue для проверка, если время находится в кэше. Если время не кэшируется, создается новая запись и добавляется в кэш с помощью Set:

public void OnGet()
{
    CurrentDateTime = DateTime.Now;

    if (!_memoryCache.TryGetValue(CacheKeys.Entry, out DateTime cacheValue))
    {
        cacheValue = CurrentDateTime;

        var cacheEntryOptions = new MemoryCacheEntryOptions()
            .SetSlidingExpiration(TimeSpan.FromSeconds(3));

        _memoryCache.Set(CacheKeys.Entry, cacheValue, cacheEntryOptions);
    }

    CacheCurrentDateTime = cacheValue;
}

В приведенном выше коде запись кэша настраивается с скользящим сроком действия в три секунды. Если запись кэша не обращается более чем на три секунды, она вытесна из кэша. При каждом доступе к записи кэша он остается в кэше еще на 3 секунды. Класс CacheKeys является частью примера скачивания.

Отображается текущее время и кэшированное время:

<ul>
    <li>Current Time: @Model.CurrentDateTime</li>
    <li>Cached Time: @Model.CacheCurrentDateTime</li>
</ul>

Следующий код использует Set метод расширения для кэширования данных относительного времени без MemoryCacheEntryOptions:

_memoryCache.Set(CacheKeys.Entry, DateTime.Now, TimeSpan.FromDays(1));

В приведенном выше коде запись кэша настраивается с относительным сроком действия в один день. Запись кэша получает вытеснение из кэша через один день, даже если он находится в течение этого периода ожидания.

Следующий код использует GetOrCreate и GetOrCreateAsync кэширует данные.

public void OnGetCacheGetOrCreate()
{
    var cachedValue = _memoryCache.GetOrCreate(
        CacheKeys.Entry,
        cacheEntry =>
        {
            cacheEntry.SlidingExpiration = TimeSpan.FromSeconds(3);
            return DateTime.Now;
        });

    // ...
}

public async Task OnGetCacheGetOrCreateAsync()
{
    var cachedValue = await _memoryCache.GetOrCreateAsync(
        CacheKeys.Entry,
        cacheEntry =>
        {
            cacheEntry.SlidingExpiration = TimeSpan.FromSeconds(3);
            return Task.FromResult(DateTime.Now);
        });

    // ...
}

Следующий код вызывает Get получение кэшированного времени:

var cacheEntry = _memoryCache.Get<DateTime?>(CacheKeys.Entry);

Следующий код получает или создает кэшированный элемент с абсолютным истечением срока действия:

var cachedValue = _memoryCache.GetOrCreate(
    CacheKeys.Entry,
    cacheEntry =>
    {
        cacheEntry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(20);
        return DateTime.Now;
    });

Кэшированный набор элементов с истечением срока действия с скользящим сроком действия не может быть прекращен. Если кэшированный элемент неоднократно обращается к скользящему интервалу истечения срока действия, срок действия элемента никогда не истекает. Объедините скользящий срок действия с абсолютным сроком действия, чтобы гарантировать истечение срока действия элемента. Абсолютный срок действия задает верхнюю границу по времени, в течение которого элемент может быть кэширован, пока элемент не истекает раньше, если он не запрашивается в течение скользящего интервала. Если либо скользящий интервал истечения срока действия, либо абсолютный период истечения срока действия, элемент вытеснился из кэша.

Следующий код получает или создает кэшированный элемент с скользящим и абсолютным сроком действия:

var cachedValue = _memoryCache.GetOrCreate(
    CacheKeys.CallbackEntry,
    cacheEntry =>
    {
        cacheEntry.SlidingExpiration = TimeSpan.FromSeconds(3);
        cacheEntry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(20);
        return DateTime.Now;
    });

Предыдущий код гарантирует, что данные не будут кэшироваться дольше абсолютного времени.

GetOrCreate, GetOrCreateAsyncи Get являются методами CacheExtensions расширения в классе. Эти методы расширяют возможности IMemoryCache.

MemoryCacheEntryOptions

Следующий пример:

  • Задает приоритет CacheItemPriority.NeverRemoveкэша.
  • Задает объект PostEvictionDelegate , который вызывается после вытеснения записи из кэша. Обратный вызов выполняется в другом потоке кода, который удаляет элемент из кэша.
public void OnGetCacheRegisterPostEvictionCallback()
{
    var memoryCacheEntryOptions = new MemoryCacheEntryOptions()
        .SetPriority(CacheItemPriority.NeverRemove)
        .RegisterPostEvictionCallback(PostEvictionCallback, _memoryCache);

    _memoryCache.Set(CacheKeys.CallbackEntry, DateTime.Now, memoryCacheEntryOptions);
}

private static void PostEvictionCallback(
    object cacheKey, object cacheValue, EvictionReason evictionReason, object state)
{
    var memoryCache = (IMemoryCache)state;

    memoryCache.Set(
        CacheKeys.CallbackMessage,
        $"Entry {cacheKey} was evicted: {evictionReason}.");
}

Использование SetSize, Size и SizeLimit для ограничения размера кэша

Экземпляр MemoryCache может дополнительно указать и применить ограничение размера. Ограничение размера кэша не имеет определенной единицы измерения, так как кэш не имеет механизма измерения размера записей. Если задано ограничение размера кэша, все записи должны указать размер. Среда выполнения ASP.NET Core не ограничивает размер кэша на основе давления памяти. Это до разработчика, чтобы ограничить размер кэша. Размер, указанный в единицах, выбирает разработчик.

Например:

  • Если веб-приложение было в основном кэширование строк, каждый размер записи кэша может быть строковой длиной.
  • Приложение может указать размер всех записей как 1, а ограничение размера — количество записей.

Если SizeLimit это не задано, кэш растет без привязки. Среда выполнения ASP.NET Core не обрезает кэш, если системная память низка. Приложения должны быть архитекторами:

  • Ограничение роста кэша.
  • Вызов Compact или Remove при ограничении доступной памяти.

Следующий код создает необъединенный фиксированный размер MemoryCache , доступный путем внедрения зависимостей:

public class MyMemoryCache
{
    public MemoryCache Cache { get; } = new MemoryCache(
        new MemoryCacheOptions
        {
            SizeLimit = 1024
        });
}

SizeLimit не имеет единиц. Кэшированные записи должны указывать размер в любых единицах, которые они считают наиболее подходящими, если задано ограничение размера кэша. Все пользователи экземпляра кэша должны использовать одну и ту же систему единиц. Запись не будет кэширована, если сумма кэшированных размеров записи превышает значение, указанное в SizeLimitпараметре . Если ограничение размера кэша не задано, размер кэша, заданный в записи, игнорируется.

Следующий код регистрируется MyMemoryCache в контейнере внедрения зависимостей:

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddSingleton<MyMemoryCache>();

MyMemoryCache создается в качестве независимого кэша памяти для компонентов, которые знают об этом ограниченном кэше и знают, как задать размер записи кэша соответствующим образом.

Размер записи кэша можно задать с помощью SetSize метода расширения или Size свойства:

if (!_myMemoryCache.Cache.TryGetValue(CacheKeys.Entry, out DateTime cacheValue))
{
    var cacheEntryOptions = new MemoryCacheEntryOptions()
        .SetSize(1);

    // cacheEntryOptions.Size = 1;

    _myMemoryCache.Cache.Set(CacheKeys.Entry, cacheValue, cacheEntryOptions);
}

В приведенном выше коде две выделенные строки обеспечивают одинаковый результат задания размера записи кэша. SetSize предоставляется для удобства при подключении вызовов к new MemoryCacheOptions()цепочке.

MemoryCache.Compact

MemoryCache.Compact пытается удалить указанный процент кэша в следующем порядке:

  • Все элементы с истекшим сроком действия.
  • Элементы по приоритету. Сначала удаляются элементы с наименьшим приоритетом.
  • Наименее недавно использованные объекты.
  • Элементы с самым ранним абсолютным сроком действия.
  • Элементы с самым ранним скользящим сроком действия.

Закрепленные элементы с приоритетом NeverRemove никогда не удаляются. Следующий код удаляет элемент кэша и вызывает Compact удаление 25 % кэшированных записей:

_myMemoryCache.Cache.Remove(CacheKeys.Entry);
_myMemoryCache.Cache.Compact(.25);

Дополнительные сведения см. в источнике Compact на сайте GitHub.

Кэширование зависимостей

В следующем примере показано, как истекать срок действия записи кэша при истечении срока действия зависимой записи. Объект CancellationChangeToken добавляется в кэшированный элемент. При Cancel вызове CancellationTokenSourceобоих записей кэша удаляются:

public void OnGetCacheCreateDependent()
{
    var cancellationTokenSource = new CancellationTokenSource();

    _memoryCache.Set(
        CacheKeys.DependentCancellationTokenSource,
        cancellationTokenSource);

    using var parentCacheEntry = _memoryCache.CreateEntry(CacheKeys.Parent);

    parentCacheEntry.Value = DateTime.Now;

    _memoryCache.Set(
        CacheKeys.Child,
        DateTime.Now,
        new CancellationChangeToken(cancellationTokenSource.Token));
}

public void OnGetCacheRemoveDependent()
{
    var cancellationTokenSource = _memoryCache.Get<CancellationTokenSource>(
        CacheKeys.DependentCancellationTokenSource);

    cancellationTokenSource.Cancel();
}

Использование функции CancellationTokenSource позволяет вытеснить несколько записей кэша в качестве группы. using При использовании шаблона в приведенном выше коде записи кэша, созданные внутри using область наследуют триггеры и параметры истечения срока действия.

Дополнительные примечания

  • Срок действия не происходит в фоновом режиме. Таймер, который активно сканирует кэш для просроченных элементов. Любое действие в кэше (Get, Set, ) Removeможет активировать фоновое сканирование просроченных элементов. Таймер на CancellationTokenSource (CancelAfter) также удаляет запись и запускает проверку просроченных элементов. В следующем примере используется CancellationTokenSource(TimeSpan) для зарегистрированного токена. Когда этот маркер срабатывает, он немедленно удаляет запись и запускает обратные вызовы вытеснения:

    if (!_memoryCache.TryGetValue(CacheKeys.Entry, out DateTime cacheValue))
    {
        cacheValue = DateTime.Now;
    
        var cancellationTokenSource = new CancellationTokenSource(
            TimeSpan.FromSeconds(10));
    
        var cacheEntryOptions = new MemoryCacheEntryOptions()
            .AddExpirationToken(
                new CancellationChangeToken(cancellationTokenSource.Token))
            .RegisterPostEvictionCallback((key, value, reason, state) =>
            {
                ((CancellationTokenSource)state).Dispose();
            }, cancellationTokenSource);
    
        _memoryCache.Set(CacheKeys.Entry, cacheValue, cacheEntryOptions);
    }
    
  • При использовании обратного вызова для повторного заполнения элемента кэша:

    • Несколько запросов могут найти кэшированное значение ключа пустым, так как обратный вызов не завершен.
    • Это может привести к повтору нескольких потоков, повторяющих кэшированный элемент.
  • Если одна запись кэша используется для создания другого, дочерний элемент копирует маркеры окончания срока действия родительской записи и параметры срока действия на основе времени. Дочерний элемент не истек вручную или обновление родительской записи.

  • Используется PostEvictionCallbacks для задания обратных вызовов, которые будут запущены после удаления записи кэша из кэша.

  • Для большинства приложений IMemoryCache включена. Например, вызов AddMvc, AddRazorPagesAddControllersWithViews, AddMvcCore().AddRazorViewEngineи многие другие Add{Service} методы IMemoryCacheв Program.cs, включает . Для приложений, которые не вызывают один из предыдущих Add{Service} методов, может потребоваться вызвать AddMemoryCache в Program.cs.

Обновление фонового кэша

Используйте фоновую службу , например IHostedService для обновления кэша. Фоновая служба может перекомпитировать записи, а затем назначить их кэшу только после их готовности.

Дополнительные ресурсы

Просмотреть или скачать образец кода (описание загрузки)

Основы кэширования

Кэширование может значительно повысить производительность и масштабируемость приложения, сокращая работу, необходимую для создания содержимого. Кэширование лучше всего работает с данными, которые часто изменяются и являются дорогостоящими для создания. Кэширование делает копию данных, которые могут быть возвращены гораздо быстрее, чем из источника. Приложения должны записываться и тестироваться, чтобы никогда не зависеть от кэшированных данных.

ASP.NET Core поддерживает несколько разных кэшей. Самый простой кэш основан на IMemoryCache. IMemoryCache представляет кэш, хранящийся в памяти веб-сервера. Приложения, работающие на ферме серверов (несколько серверов), должны гарантировать, что сеансы закреплены при использовании кэша в памяти. Липкие сеансы гарантируют, что последующие запросы от клиента отправляются на один и тот же сервер. Например, веб-приложения Azure используют маршрутизацию запросов приложений (ARR) для маршрутизации всех последующих запросов на один и тот же сервер.

Нелипкие сеансы в веб-ферме требуют распределенного кэша , чтобы избежать проблем согласованности кэша. Для некоторых приложений распределенный кэш может поддерживать более высокий масштаб, чем кэш в памяти. Использование распределенного кэша отгрузит память кэша во внешний процесс.

Кэш в памяти может хранить любой объект. Интерфейс распределенного кэша ограничен byte[]. Элементы кэша в памяти и распределенном кэше хранятся в виде пар "ключ-значение".

System.Runtime.Caching/MemoryCache

System.Runtime.Caching/MemoryCache (пакет NuGet) можно использовать с:

  • .NET Standard 2.0 или более поздней версии.
  • Любая реализация .NET, предназначенная для .NET Standard 2.0 или более поздней версии. Например, ASP.NET Core 3.1 или более поздней версии.
  • .NET Framework 4.5 или более поздней версии.

Microsoft.Extensions.Caching.MemoryIMemoryCache/ (описанная в этой статье) рекомендуется использоватьSystem.Runtime.Caching/MemoryCache, так как он лучше интегрирован в ASP.NET Core. Например, IMemoryCache работает в собственном коде с внедрением ASP.NET Core.

Используйте System.Runtime.Caching/MemoryCache в качестве моста совместимости при переносе кода из ASP.NET 4.x в ASP.NET Core.

Рекомендации по кэшу

  • Код всегда должен иметь резервный вариант для получения данных и не зависит от кэшированного значения.
  • Кэш использует дефицитный ресурс, память. Ограничение роста кэша:
    • Не используйте внешние входные данные в качестве ключей кэша.
    • Используйте срок действия, чтобы ограничить рост кэша.
    • Используйте SetSize, Size и SizeLimit, чтобы ограничить размер кэша. Среда выполнения ASP.NET Core не ограничивает размер кэша на основе давления памяти. Это до разработчика, чтобы ограничить размер кэша.

Использование IMemoryCache

Предупреждение

Использование общего кэша памяти из внедрения зависимостей и вызова SetSizeили SizeSizeLimit ограничения размера кэша может привести к сбою приложения. Если для кэша задано ограничение размера, все записи должны указать размер при добавлении. Это может привести к проблемам, так как разработчики могут не иметь полного контроля над тем, что использует общий кэш. При использовании SetSize( Sizeили SizeLimit для ограничения кэша) создайте одноэлементный кэш для кэширования. Дополнительные сведения и пример см. в разделе Use SetSize, Size и SizeLimit, чтобы ограничить размер кэша. Общий кэш является одним из общих платформ или библиотек.

Кэширование в памяти — это служба , на которую ссылается приложение с помощью внедрения зависимостей. IMemoryCache Запросите экземпляр в конструкторе:

public class HomeController : Controller
{
    private IMemoryCache _cache;

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

Следующий код используется TryGetValue для проверка, если время находится в кэше. Если время не кэшируется, создается новая запись и добавляется в кэш.Set Класс CacheKeys является частью примера скачивания.

public static class CacheKeys
{
    public static string Entry => "_Entry";
    public static string CallbackEntry => "_Callback";
    public static string CallbackMessage => "_CallbackMessage";
    public static string Parent => "_Parent";
    public static string Child => "_Child";
    public static string DependentMessage => "_DependentMessage";
    public static string DependentCTS => "_DependentCTS";
    public static string Ticks => "_Ticks";
    public static string CancelMsg => "_CancelMsg";
    public static string CancelTokenSource => "_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>

Следующий код использует Set метод расширения для кэширования данных относительного времени без создания MemoryCacheEntryOptions объекта:

public IActionResult SetCacheRelativeExpiration()
{
    DateTime cacheEntry;

    // Look for cache key.
    if (!_cache.TryGetValue(CacheKeys.Entry, out cacheEntry))
    {
        // Key not in cache, so get data.
        cacheEntry = DateTime.Now;

        // Save data in cache and set the relative expiration time to one day
        _cache.Set(CacheKeys.Entry, cacheEntry, TimeSpan.FromDays(1));
    }

    return View("Cache", cacheEntry);
}

Кэшированное DateTime значение остается в кэше во время выполнения запросов в течение периода ожидания.

Следующий код использует 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> 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);
}

Предыдущий код гарантирует, что данные не будут кэшироваться дольше абсолютного времени.

GetOrCreate, GetOrCreateAsyncи Get являются методами CacheExtensions расширения в классе. Эти методы расширяют возможности 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 может дополнительно указать и применить ограничение размера. Ограничение размера кэша не имеет определенной единицы измерения, так как кэш не имеет механизма измерения размера записей. Если задано ограничение размера кэша, все записи должны указать размер. Среда выполнения ASP.NET Core не ограничивает размер кэша на основе давления памяти. Это до разработчика, чтобы ограничить размер кэша. Размер, указанный в единицах, выбирает разработчик.

Например:

  • Если веб-приложение было в основном кэширование строк, каждый размер записи кэша может быть строковой длиной.
  • Приложение может указать размер всех записей как 1, а ограничение размера — количество записей.

Если SizeLimit это не задано, кэш растет без привязки. Среда выполнения ASP.NET Core не обрезает кэш, если системная память низка. Приложения должны быть архитекторами:

  • Ограничение роста кэша.
  • Вызов Compact или Remove при ограничении доступной памяти:

Следующий код создает необъединенный фиксированный размер MemoryCache , доступный путем внедрения зависимостей:

// using Microsoft.Extensions.Caching.Memory;
public class MyMemoryCache 
{
    public MemoryCache Cache { get; private 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");
    }
}

Размер записи кэша можно задать с SetSize помощью Size методов расширения:

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.Compact

MemoryCache.Compact пытается удалить указанный процент кэша в следующем порядке:

  • Все элементы с истекшим сроком действия.
  • Элементы по приоритету. Сначала удаляются элементы с наименьшим приоритетом.
  • Наименее недавно использованные объекты.
  • Элементы с самым ранним абсолютным сроком действия.
  • Элементы с самым ранним скользящим сроком действия.

Закрепленные элементы с приоритетом NeverRemove никогда не удаляются. Следующий код удаляет элемент кэша и вызывает Compact:

_cache.Remove(MyKey);

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

Дополнительные сведения см. в источнике Compact на сайте GitHub.

Кэширование зависимостей

В следующем примере показано, как истекать срок действия записи кэша при истечении срока действия зависимой записи. Объект 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 блока, наследуют триггеры и параметры срока действия.

Дополнительные примечания

  • Срок действия не происходит в фоновом режиме. Таймер, который активно сканирует кэш для просроченных элементов. Любое действие в кэше (Get, Set, ) Removeможет активировать фоновое сканирование просроченных элементов. Таймер на CancellationTokenSource (CancelAfter) также удаляет запись и запускает проверку просроченных элементов. В следующем примере используется 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 для задания обратных вызовов, которые будут запущены после удаления записи кэша из кэша. В примере кода CancellationTokenSource.Dispose() вызывается для освобождения неуправляемых ресурсов, используемых .CancellationTokenSource Однако он CancellationTokenSource не удаляется немедленно, так как он по-прежнему используется записью кэша. Передается CancellationToken для MemoryCacheEntryOptions создания записи кэша, срок действия которого истекает через определенное время. Поэтому Dispose не следует вызывать до тех пор, пока запись кэша не будет удалена или истекла. В примере кода RegisterPostEvictionCallback вызывается метод для регистрации обратного вызова, который будет вызываться при вытеснения записи кэша, и он удаляет CancellationTokenSource этот обратный вызов.

  • Для большинства приложений IMemoryCache включена. Например, вызов AddMvc, AddRazorPagesAddControllersWithViews, AddMvcCore().AddRazorViewEngineи многие другие Add{Service} методы IMemoryCacheв ConfigureServices, включает . Для приложений, которые не вызывают один из предыдущих Add{Service} методов, может потребоваться вызватьAddMemoryCache.ConfigureServices

Обновление фонового кэша

Используйте фоновую службу , например IHostedService для обновления кэша. Фоновая служба может перекомпитировать записи, а затем назначить их кэшу только после их готовности.

Дополнительные ресурсы