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

В предыдущей части мы познакомились с использованием кэша в памяти и написали небольшое тестовое приложение, демонстрирующее кэширование в ASP.NET Core. Теперь разберемся с тем, какие методы IMemoryCache мы можем использовать для управления кэшем, а также настройки кэширования.

Методы IMemoryCache

У IMemoryCache определены следующие методы:

CreateEntry() Создает или перезаписывает запись в кэше.
GetCurrentStatistics() Возвращает snapshot статистики кэша, если она доступна.
Remove() Удаляет объект, связанный с указанным ключом.
TryGetValue() Возвращает элемент, связанный с этим ключом при его наличии.

а также, методы расширения:

Get() Возвращает значение, связанное с этим ключом, если оно имеется.
GetOrCreate<T>() Возвращает значение, связанное с этим ключом, если оно существует, или создает новую запись с использованием предоставленного ключа и значение из заданной фабрики, если ключ не найден.
GetOrCreateAsync<T>() Асинхронно получает значение, связанное с этим ключом, если он существует, или создает новую запись, используя предоставленный ключ, и значение из заданной фабрики, если ключ не найден.
Set<TItem>() Связывает значение с указанным ключом в IMemoryCache .
TryGetValue<T>() Пытается получить значение, связанное с указанным ключом.

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

Настройки In-Memory кэша в ASP.NET Core

Одной из возможных проблем использования кэширования в памяти является то, что объем кэша может разрастаться до больших размеров, что, в итоге, может привести к тому, что ваше приложение просто перестанет работать. В связи с этим, в ASP.NET Core предусмотрено использование политики вытеснения (второе название — политик удаления) элементов кэша:

  • Политика абсолютного истечения срока (Absolute Expiration). В соответствии с этой политикой, элемент кэша удаляется через фиксированный промежуток времени, несмотря ни на что.
  • Политика скользящего истечения срока (Sliding Expiration). В соответствии с этой политикой, элемент кэша удаляется, если к нему не был осуществлен доступ в течение определенного периода времени.
  • Политика ограничения размера (Size Limit). Эта политика ограничивает размер кэш-памяти.

Мы можем применять как отдельные политики вытеснения, так и все сразу, используя настройки кэширования и каждого элемента кэша, в частности. Для настроек используются два класса: MemoryCacheOptions — для настроек сервиса кэширования и MemoryCacheEntryOptions — для настроек элемента кэша. Рассмотрим применение этих классов в нашем приложении.

MemoryCacheOptions

Класс MemoryCacheOptions используется для настроек сервиса кэширования в памяти. Рассмотрим следующий пример:

builder.Services.AddMemoryCache(options=> 
{ 
    options.TrackStatistics = true;  //вести статистику использования кэша
    options.SizeLimit = 1024; //максимальный размер
    options.CompactionPercentage = 0.1; //сжать кэш на 10% когда размер достигнет максимального значения
});

При добавлении сервиса кэширования мы передали в метод AddMemoryCache делегат в котором определили настройки кэширования — вести статистику использования кэша, а также определили максимальный размер и процент сжатия кэша при достижении максимального размера. Здесь стоит ответить два момента:

  1. после того, как мы указали значение SizeLimit все элементы кэша обязаны передавать в сервис кэширования свой размер.
  2. Кэш не имеет механизмов подсчета размера записей, поэтому значение SizeLimit — это не килобайты и байты, а, скорее, условный размер по которому может отслеживаться наполнение кэша. Например, если мы для каждого элемента кэша будем использовать размер 1, то наш кэш будет содержать максимум 1024 элемента.

Теперь посмотрим, как можно настраивать политики вытеснения для элементов кэша.

MemoryCacheEntryOptions

Настроим элемент кэша, который мы использовали в предыдущей части в сервисе UserService:

public class UserService
 {
     AppUsersContext _usersContext;
     ILogger _logger;
     IMemoryCache _cache;
     public UserService(AppUsersContext context, ILogger<UserService> logger, IMemoryCache memoryCache) 
     {
         _usersContext = context;
         _logger = logger;
         _cache = memoryCache;
     }

     public UserModel GetUser(int id)
     {
         UserModel user = null;
         _cache.TryGetValue(id, out user);
         if (user == null)  //не смогли получить данные из кэша
         {
             user = _usersContext.Users.FirstOrDefault(x => x.Id == id);
             if (user != null) //данные в БД есть
             {
                 _cache.Set(id, user, 
                     new MemoryCacheEntryOptions()
                     .SetAbsoluteExpiration(TimeSpan.FromSeconds(30)) //через 30 секунд элемент будет удален
                     .SetSlidingExpiration(TimeSpan.FromSeconds(10)) //если в течение 10 секунд к объекту не будет обращения - он будет удален
                     .SetSize(1) //размер элемента = 1
                     .SetPriority(CacheItemPriority.High) //приоритет элемента - высокий
                     );
                 _logger.LogInformation($"Пользователь {user.Name} помещен в кэш");
             }
         }
         else
         {
             _logger.LogInformation($"Пользователь {user.Name} был извлечен из кэша");
         }
         return user;
     }
 }

здесь необходимо обратить внимание на то, как мы добавляем очередной элемент в кэш:

_cache.Set(id, user, 
    new MemoryCacheEntryOptions()
    .SetAbsoluteExpiration(TimeSpan.FromSeconds(30)) //через 30 секунд элемент будет удален
    .SetSlidingExpiration(TimeSpan.FromSeconds(10)) //если в течение 10 секунд к объекту не будет обращения - он будет удален
    .SetSize(1) //размер элемента = 1
    .SetPriority(CacheItemPriority.High) //приоритет элемента - высокий
    );

Как будет работать в этом случае кэширование?

  1. SetSlidingExpiration(TimeSpan.FromSeconds(10)) — говорит о том, что, если в течение 10 секунд к объекту кэша не было обращения, то он будет удален
  2. SetAbsoluteExpiration(TimeSpan.FromSeconds(30))— при этом, через 30 секунд элемент будет удален несмотря ни на что
  3. SetSize(1) — размер элемента. Учитывая настройку сервиса (SizeLimit = 1024), таких элементов может быть в кэше до 1024. При этом, для разных элементов кэша мы можем указывать разный размер и, в этом, случае, количество элементов может быть меньше 1024.
  4. SetPriority(CacheItemPriority.High) — для элемента установлен высокий приоритет. При этом CacheItemPriority влияет на порядок удаления элементов кэша в случае, если достигнут предельный размер кэша и может принимать следующие значения:
High 2 Запись кэша должна удаляться только в том случае, если во время очистки, активировавшейся нехваткой памяти, отсутствуют другие записи кэша с низким или нормальным приоритетом.
Low 0 Запись кэша должна быть удалена как можно скорее во время очистки, активировав нехватку памяти.
NeverRemove 3 Запись кэша никогда не должна удаляться во время очистки, вызываемой нехваткой памяти.
Normal 1 Запись кэша должна быть удалена, если во время очистки, активировавшейся нехваткой памяти, отсутствуют другие записи кэша с низким приоритетом.

Теперь допишем наше приложение, использующее In-Memory кэш с учётом рассмотренных выше настроек кэширования и посмотрим на результат:

Приложение ASP.NET Core с кэшированием

Структура проекта:

Ниже приведен исходный код приложения, использующего кэширование. Класс Program:

using AspCache;

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

namespace ASpCache
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);

            builder.Services.AddDbContext<AppUsersContext>(options => options.UseSqlite("Data Source=users.sqlite"));
            builder.Services.AddTransient<UserService>();

            //включаем кэширование
            builder.Services.AddMemoryCache(options=> 
            { 
                options.TrackStatistics = true;  //вести статистику использования кэша
                options.SizeLimit = 20; //максимальный размер
                options.CompactionPercentage = 0.1; //сжать кэш на 10% когда размер достигнет максимального значения
            });
            
            var app = builder.Build();

            app.MapGet("/", () => "Hello ASP.NET Core Cache!");
            
            app.MapGet("/user/{id}", (int id, [FromServices] UserService data) => 
            {
                var _user = data.GetUser(id);
                if (_user == null) 
                {
                    return Results.Content($"Пользователь не найден");
                }
                return Results.Json(_user);
            });

            app.Run();
        }
    }
}

Класс UserService

using Microsoft.Extensions.Caching.Memory;

namespace AspCache
{
    public class UserService
    {
        AppUsersContext _usersContext;
        ILogger _logger;
        IMemoryCache _cache;
        public UserService(AppUsersContext context, ILogger<UserService> logger, IMemoryCache memoryCache) 
        {
            _usersContext = context;
            _logger = logger;
            _cache = memoryCache;
        }

        public UserModel GetUser(int id)
        {
            UserModel user = null;
            _cache.TryGetValue(id, out user);
            if (user == null)  //не смогли получить данные из кэша
            {
                user = _usersContext.Users.FirstOrDefault(x => x.Id == id);
                if (user != null) //данные в БД есть
                {
                    _cache.Set(id, user, 
                        new MemoryCacheEntryOptions()
                        .SetAbsoluteExpiration(TimeSpan.FromSeconds(30)) //через 30 секунд элемент будет удален
                        .SetSlidingExpiration(TimeSpan.FromSeconds(10)) //если в течение 10 секунд к объекту не будет обращения - он будет удален
                        .SetSize(1) //размер элемента = 1
                        .SetPriority(CacheItemPriority.High) //приоритет элемента - высокий
                        );
                    var stat = _cache.GetCurrentStatistics();
                    if (stat != null)
                    {
                        _logger.LogInformation($"Количество элементов в кэше {stat.CurrentEntryCount}");
                        _logger.LogInformation($"Размер элементов в кэше {stat.CurrentEstimatedSize}");
                    }
                    _logger.LogInformation($"Пользователь {user.Name} помещен в кэш");
                }
            }
            else
            {
                _logger.LogInformation($"Пользователь {user.Name} был извлечен из кэша");
            }
            return user;
        }
    }
}

Класс UserModel

namespace AspCache
{
    public class UserModel
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Email { get; set; }
    }
}

Класс AppUsersContext 

using Microsoft.EntityFrameworkCore;

namespace AspCache
{
    public class AppUsersContext: DbContext
    {
        public DbSet<UserModel> Users { get; set; }

        public AppUsersContext(DbContextOptions<AppUsersContext> options) : base(options) =>
        Database.EnsureCreated();
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            // инициализация БД начальными данными
            modelBuilder.Entity<UserModel>().HasData(
                    new UserModel { Id = 1, Name = "Tom", Email = "tom@mail.com" },
                    new UserModel { Id = 2, Name = "John", Email = "john@mail.com" },
                    new UserModel { Id = 3, Name = "Sam", Email = "sam@mail.com" },
                    new UserModel { Id = 4, Name = "Bob", Email = "bob@mail.com" }
            );
        }
    }
}

Итого

Сегодня мы рассмотрели настройки кэширования в ASP.NET Core. Для дого, чтобы кэш не разрастался в памяти до неприемлемых размеров мы можем ограничить его размер, используя свойство SizeLimit. Для каждого элемента кэша мы можем настроить различные политики вытеснения — указать абсолютное и скользящее время вытеснения элемента из кэша, приоритет удаления и размер.

Подписаться
Уведомить о
guest
0 Комментарий
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии