← Назад к вопросам

Что такое кешируемый метод?

1.7 Middle🔥 111 комментариев
#Другое#Кэширование и Redis

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI7 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Что такое кешируемый метод?

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

Основная цель — оптимизация производительности приложения за счёт сокращения времени отклика и снижения нагрузки на ресурсы (ЦПУ, базу данных, сеть).

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

Принцип основан на идее мемоизации (memoization) — паттерне проектирования, при котором результат функции запоминается по ключу, формируемому из её аргументов.

Базовый алгоритм работы:

  1. Проверка кеша: При вызове метода система сначала проверяет, существует ли в кеше запись для данного набора входных параметров (ключа).
  2. Возврат из кеша: Если запись найдена и она ещё актуальна (не устарела), сохранённое значение возвращается немедленно, без выполнения тела метода.
  3. Выполнение и сохранение: Если записи нет или она устарела, выполняется оригинальная логика метода, его результат сохраняется в кеш под соответствующим ключом, а затем возвращается вызывающему коду.

Реализация в C#: от простого примера до атрибута [OutputCache]

1. Простейшая ручная реализация (шаблон "Мемоизация")

using System.Collections.Concurrent;

public class DataService
{
    // Потокобезопасный словарь для хранения кеша в памяти
    private readonly ConcurrentDictionary<string, ExpensiveData> _cache = new();

    // Кешируемый метод
    public ExpensiveData GetExpensiveData(string key)
    {
        // Попытка получить значение из кеша
        if (_cache.TryGetValue(key, out var cachedData))
        {
            Console.WriteLine($"Возвращаем данные из кеша для ключа: {key}");
            return cachedData;
        }

        // Имитация дорогостоящей операции (запрос к БД, вычисления и т.д.)
        Console.WriteLine($"Выполняем дорогостоящую операцию для ключа: {key}");
        var result = ComputeExpensiveData(key);

        // Сохранение результата в кеш
        _cache[key] = result;
        return result;
    }

    private ExpensiveData ComputeExpensiveData(string key)
    {
        // Длительная операция...
        return new ExpensiveData { Key = key, Value = Guid.NewGuid().ToString() };
    }
}

2. Использование IMemoryCache (ASP.NET Core)

Платформа предоставляет встроенные абстракции для более удобного и гибкого кеширования.

using Microsoft.Extensions.Caching.Memory;

public class CachedWeatherService
{
    private readonly IMemoryCache _memoryCache;
    private readonly IWeatherRepository _repository;

    public CachedWeatherService(IMemoryCache memoryCache, IWeatherRepository repository)
    {
        _memoryCache = memoryCache;
        _repository = repository;
    }

    public async Task<WeatherForecast> GetForecastAsync(string city, DateTime date)
    {
        // Формируем уникальный ключ для комбинации параметров
        var cacheKey = $"forecast_{city}_{date:yyyy-MM-dd}";

        // Попытка получить значение из кеша. Метод GetOrCreateAsync атомарен.
        return await _memoryCache.GetOrCreateAsync(
            cacheKey,
            async cacheEntry =>
            {
                // Настройка политики кеширования для этой записи
                cacheEntry.SlidingExpiration = TimeSpan.FromMinutes(10); // Сбрасывает таймер при каждом обращении
                cacheEntry.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1); // Максимальное время жизни
                cacheEntry.SetPriority(CacheItemPriority.High);

                Console.WriteLine($"Запрос к БД для {city} на {date}");
                // Выполнение основного дорогостоящего запроса
                return await _repository.GetWeatherAsync(city, date);
            });
    }
}

3. Атрибут [OutputCache] в ASP.NET Core 7+ (для веб|API)

Для кеширования целых ответов действий контроллеров (результатов HTTP|запросов) можно использовать декларативный подход.

[ApiController]
[Route("[controller]")]
public class ProductsController : ControllerBase
{
    private readonly IProductService _productService;

    public ProductsController(IProductService productService)
    {
        _productService = productService;
    }

    // Кеширует весь HTTP|ответ (статус, заголовки, тело) на 30 секунд.
    // Ключ формируется автоматически на основе пути и параметров запроса.
    [OutputCache(Duration = 30)]
    [HttpGet("{id}")]
    public async Task<ActionResult<Product>> GetProductById(int id)
    {
        var product = await _productService.GetByIdAsync(id);
        if (product == null)
        {
            return NotFound();
        }
        return Ok(product);
    }

    // Более сложная настройка: кеш зависит от заголовка "Accept|Language"
    [OutputCache(Duration = 60, VaryByHeader = "Accept-Language")]
    [HttpGet]
    public async Task<ActionResult<List<Product>>> GetProducts([FromQuery] string category)
    {
        var products = await _productService.GetByCategoryAsync(category);
        return Ok(products);
    }
}

Ключевые аспекты и лучшие практики

  • Формирование ключа: Ключ должен однозначно идентифицировать комбинацию входных параметров. Часто используется конкатенация или хеширование.
  • Политика устаревания (Expiration Policy):
    *   **Абсолютное время жизни (Absolute Expiration)**: Запись удаляется в заданный момент времени.
    *   **Скользящее время жизни (Sliding Expiration)**: Срок жизни записи обновляется при каждом обращении. Если к записи не обращаются в течение заданного интервала, она удаляется.
  • Инвалидация кеша (Cache Invalidation): Это одна из самых сложных задач. Кеш должен очищаться или обновляться при изменении исходных данных. Стратегии:
    *   **По времени (Time-based)**: Просто, но данные могут быть устаревшими.
    *   **По событию (Event-based)**: Подписка на события изменения данных (например, через шину сообщений).
    *   **Явная инвалидация**: Удаление записей по известному ключу при обновлении.
  • Распределённое кеширование: Для приложений, работающих в кластере (несколько инстансов), используется внешнее хранилище кеша (например, Redis или NCache), чтобы кеш был общим для всех узлов. В ASP.NET Core для этого используется интерфейс IDistributedCache.
  • Где применять: Идеальные кандидаты — методы с тяжёлыми вычислениями, частыми запросами к БД или к внешним сервисам, возвращающие данные, которые редко меняются.

Вывод

Кешируемый метод — это мощный инструмент оптимизации, который переводит задачу с дорогостоящих операций на быструю операцию чтения из кеша. Его реализация в экосистеме .NET варьируется от простых словарей до использования высокоуровневых абстракций (IMemoryCache, IDistributedCache) и декларативных атрибутов ([OutputCache]). Успешное применение требует тщательного проектирования стратегий формирования ключей, устаревания и, что критически важно, инвалидации кеша для обеспечения консистентности данных.