Что такое In-Memory Cache?

Ответ

In-Memory Cache — это механизм хранения данных в оперативной памяти (RAM) процесса приложения для сверхбыстрого повторного доступа, позволяющий избежать повторных затратных операций (тяжёлых вычислений, запросов к БД, вызовов внешних API).

Характеристики:

  • Скорость: Доступ к данным в RAM на порядки быстрее, чем к диску или сети.
  • Волатильность: Данные теряются при остановке или перезапуске приложения.
  • Ограниченность: Кэш конкурирует за память с самим приложением.

Типичные сценарии использования в ASP.NET Core:

  • Кэширование редко меняющихся справочников из БД.
  • Хранение результатов сложных вычислений или отчётов.
  • Временное хранение состояния сессии пользователя.
  • Кэширование ответов от внешних HTTP-сервисов.

Пример реализации с IMemoryCache:

  1. Регистрация сервиса:

    // В Program.cs или Startup.ConfigureServices
    builder.Services.AddMemoryCache();
  2. Использование в сервисе:

    public class CatalogService
    {
    private readonly IMemoryCache _cache;
    private readonly IProductRepository _repository;
    private readonly TimeSpan _cacheDuration = TimeSpan.FromMinutes(5);
    
    public CatalogService(IMemoryCache cache, IProductRepository repository)
    {
        _cache = cache;
        _repository = repository;
    }
    
    public async Task<List<Product>> GetTopProductsAsync()
    {
        // Попытка получить данные из кэша
        string cacheKey = "top_products";
        if (!_cache.TryGetValue(cacheKey, out List<Product> products))
        {
            // Данных в кэше нет -> получаем из источника (БД)
            products = await _repository.GetTopSellingProductsAsync(10);
    
            // Настраиваем параметры кэширования и сохраняем
            var cacheOptions = new MemoryCacheEntryOptions()
                .SetAbsoluteExpiration(_cacheDuration) // Удалится через 5 минут
                .SetPriority(CacheItemPriority.High); // При нехватке памяти удалится в последнюю очередь
    
            _cache.Set(cacheKey, products, cacheOptions);
        }
        return products; // Данные либо из кэша, либо только что загружены
    }
    }

    Важные аспекты:

    • Стратегии вытеснения: Absolute Expiration (удаление в заданное время), Sliding Expiration (обновление срока при каждом обращении).
    • Зависимости: Можно инвалидировать кэш при изменении данных, но IMemoryCache не предоставляет встроенного механизма для распределённых сценариев.
    • Распределённый кэш: Для приложений, работающих на нескольких серверах (ферма), используйте IDistributedCache (Redis, SQL Server).

Ответ 18+ 🔞

А, ну это же про кэш в памяти, да? Такая штука, когда ты, чтобы не ездить каждый раз в базу данных или не делать одни и те же ебанутые вычисления по сто раз, просто складываешь результат в оперативку. Дохуя быстрее, потому что память — это не диск, там всё мгновенно.

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

Где это обычно используют? Да везде, где можно сэкономить время. Справочники какие-нибудь, которые редко меняются. Или там отчёт, который строится полчаса — один раз посчитал, положил в кэш, и потом все юзеры им пользуются, пока он не устареет. Или состояние сессии пользователя на время его визита.

Смотри, как это в ASP.NET Core делается. Всё просто, как три копейки.

Сначала надо сервис этот самый зарегистрировать, одну строчку дописать:

builder.Services.AddMemoryCache();

А потом в любом своём сервисе ты можешь его запросить через конструктор и использовать. Вот смотри, пример сервиса, который работает с каталогом товаров.

public class CatalogService
{
    private readonly IMemoryCache _cache;
    private readonly IProductRepository _repository;
    private readonly TimeSpan _cacheDuration = TimeSpan.FromMinutes(5);

    public CatalogService(IMemoryCache cache, IProductRepository repository)
    {
        _cache = cache;
        _repository = repository;
    }

    public async Task<List<Product>> GetTopProductsAsync()
    {
        // Пробуем вытащить из кэша, не ходить же лишний раз в базу
        string cacheKey = "top_products";
        if (!_cache.TryGetValue(cacheKey, out List<Product> products))
        {
            // А, бля, в кэше пусто! Значит, надо идти в базу, делать тяжёлую работу.
            products = await _repository.GetTopSellingProductsAsync(10);

            // Теперь кладём в кэш, но не навечно, а, скажем, на пять минут
            var cacheOptions = new MemoryCacheEntryOptions()
                .SetAbsoluteExpiration(_cacheDuration) // Через 5 минут — пиздец данным, удалятся
                .SetPriority(CacheItemPriority.High); // А это чтобы при нехватке памяти удалялись в последнюю очередь

            _cache.Set(cacheKey, products, cacheOptions);
        }
        return products; // Возвращаем либо из кэша, либо свежедобытые
    }
}

Вот и вся магия. Главное — не забывать про сроки жизни данных. Можно сделать абсолютное истечение, как в примере — через пять минут нахуй, и всё. А можно sliding expiration — это когда срок сбрасывается при каждом обращении. То есть данные живут, пока к ним ходят. Перестали ходить — через заданный интервал удалились.

И ещё важный момент, чувак. Этот IMemoryCache хорош, когда у тебя приложение на одном сервере крутится. А если у тебя ферма из десяти серверов, то на каждом будет свой кэш, и они друг про друга нихуя не знают. Обновил данные на одном — на остальных девять старый хлам лежит. Для таких случаев есть IDistributedCache — это когда кэш общий на всех, типа Redis или SQL Server. Но это уже совсем другая история, с другим геморроем и скоростью.