Что такое кешируемый метод?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Что такое кешируемый метод?
Кешируемый метод — это метод, результат работы которого сохраняется (кешируется) для последующего повторного использования, чтобы избежать повторных вычислений или дорогостоящих операций (таких как обращения к базе данных, вызовы внешних API или сложные вычисления) при идентичных входных параметрах.
Основная цель — оптимизация производительности приложения за счёт сокращения времени отклика и снижения нагрузки на ресурсы (ЦПУ, базу данных, сеть).
Как работает кеширование результата метода?
Принцип основан на идее мемоизации (memoization) — паттерне проектирования, при котором результат функции запоминается по ключу, формируемому из её аргументов.
Базовый алгоритм работы:
- Проверка кеша: При вызове метода система сначала проверяет, существует ли в кеше запись для данного набора входных параметров (ключа).
- Возврат из кеша: Если запись найдена и она ещё актуальна (не устарела), сохранённое значение возвращается немедленно, без выполнения тела метода.
- Выполнение и сохранение: Если записи нет или она устарела, выполняется оригинальная логика метода, его результат сохраняется в кеш под соответствующим ключом, а затем возвращается вызывающему коду.
Реализация в 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]). Успешное применение требует тщательного проектирования стратегий формирования ключей, устаревания и, что критически важно, инвалидации кеша для обеспечения консистентности данных.