Введение: Почему продвинутое кэширование меняет всё
В современной высоконагруженной архитектуре скорость ответа и снижение нагрузки на базу данных — ключевые задачи. Кэширование в C# (.NET) перестало быть просто запоминанием результата: это сложная система управления состоянием, временем жизни и согласованностью данных. На продвинутом уровне разработчик должен выбирать между Redis, Memcached и встроенными решениями в зависимости от сценария.
В этой статье мы разберём три основных подхода к кэшированию в экосистеме .NET, сравним их производительность, сложность внедрения и типичные ошибки. Вы узнаете, как настроить распределённый кэш с Redis, оптимизировать Memcached для высоких нагрузок и когда встроенный IMemoryCache — лучшее решение.
Встроенное кэширование в .NET: IMemoryCache и IDistributedCache
IMemoryCache — быстрый локальный кэш
Встроенный кэш IMemoryCache из пространства имён Microsoft.Extensions.Caching.Memory — идеальное решение для одного экземпляра приложения. Он хранит данные в оперативной памяти процесса, обеспечивая минимальную задержку.
// Регистрация в Startup.cs или Program.cs
builder.Services.AddMemoryCache();
// Использование в сервисе
public class ProductService
{
private readonly IMemoryCache _cache;
public ProductService(IMemoryCache cache)
{
_cache = cache;
}
public async Task<Product> GetProductAsync(int id)
{
string cacheKey = $"product_{id}";
if (!_cache.TryGetValue(cacheKey, out Product product))
{
product = await _repository.GetProductByIdAsync(id);
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetAbsoluteExpiration(TimeSpan.FromMinutes(5))
.SetSlidingExpiration(TimeSpan.FromMinutes(1))
.SetPriority(CacheItemPriority.High);
_cache.Set(cacheKey, product, cacheEntryOptions);
}
return product;
}
}
Плюсы: нулевая сетевая задержка, простота настройки, поддержка скользящего и абсолютного истечения.
Минусы: не работает при масштабировании на несколько серверов; ограничен объёмом RAM процесса.
IDistributedCache — абстракция для распределённых кэшей
Интерфейс IDistributedCache позволяет легко переключаться между Redis, SQL Server или NCache без изменения бизнес-логики. Реализация по умолчанию — MemoryDistributedCache (локальный), но для продакшена обычно выбирают Redis.
// Регистрация Redis-реализации
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = "localhost:6379";
options.InstanceName = "SampleInstance";
});
// Использование
public async Task SetCacheAsync(string key, byte[] data)
{
var cache = context.RequestServices.GetRequiredService<IDistributedCache>();
await cache.SetAsync(key, data, new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1)
});
}
Ключевое преимущество — унификация: вы пишете код один раз, а провайдер кэша меняется через конфигурацию.
Redis в C#: продвинутые техники и паттерны
Подключение и пул соединений
Для работы с Redis в .NET используется библиотека StackExchange.Redis. Вместо создания нового соединения каждый раз, используйте мультиплексирование — одно соединение на всё приложение.
// Глобальный синглтон
public static ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("localhost:6379,abortConnect=false");
// Получение базы данных
IDatabase db = redis.GetDatabase(0);
// Базовые операции
await db.StringSetAsync("user:1", "John Doe", TimeSpan.FromMinutes(30));
string name = await db.StringGetAsync("user:1");
Продвинутый совет: используйте каналы (pub/sub) для инвалидации кэша на всех узлах кластера:
// Подписка на канал инвалидации
var sub = redis.GetSubscriber();
sub.Subscribe("cache-invalidation", (channel, message) =>
{
string keyToInvalidate = message;
db.KeyDelete(keyToInvalidate);
Console.WriteLine($"Кэш {keyToInvalidate} сброшен");
});
// Публикация события инвалидации
await sub.PublishAsync("cache-invalidation", "product_123");
Стратегии истечения и вытеснения
Redis поддерживает несколько политик вытеснения: allkeys-lru, volatile-ttl, noeviction. Для продвинутых сценариев комбинируйте:
- Абсолютное истечение — для данных, которые устаревают в определённый момент (например, токены).
- Скользящее истечение — для сессий пользователей (обновляется при каждом запросе).
- Ленивая инвалидация — проверка версии данных через дополнительный ключ-счётчик.
// Пример ленивой инвалидации с версионированием
string versionKey = "product:version:123";
string dataKey = "product:data:123";
long currentVersion = await db.StringIncrementAsync(versionKey); // +1 при обновлении
await db.StringSetAsync(dataKey, serializedData, TimeSpan.FromMinutes(10));
// При чтении
long storedVersion = (long)await db.StringGetAsync(versionKey);
if (storedVersion != expectedVersion) { /* перезагружаем */ }
Memcached в .NET: когда скорость важнее структуры
Особенности и отличия от Redis
Memcached — это простейший распределённый кэш «ключ-значение» без персистентности и сложных типов данных. В .NET используется клиент EnyimMemcached или Memcached.ClientLibrary. Его главное преимущество — сверхнизкая задержка (до 1 мс) и линейная масштабируемость.
// Настройка клиента
var config = new MemcachedClientConfiguration();
config.AddServer("localhost", 11211);
var client = new MemcachedClient(config);
// Сохранение и чтение
await client.StoreAsync(StoreMode.Set, "user:42", userObject, TimeSpan.FromMinutes(10));
var cachedUser = await client.GetAsync<User>("user:42");
Когда выбирать Memcached вместо Redis:
- Вам нужен только простой кэш без