Введение: Почему кэширование — критически важный компонент C# приложений
В современной веб-разработке производительность приложения напрямую влияет на пользовательский опыт и бизнес-показатели. Каждая секунда задержки загрузки страницы может стоить вам 7% конверсий. Кэширование в C# (.NET) — это не просто "приятная опция", а обязательный инструмент любого серьёзного разработчика.
В этой статье мы разберём три основных подхода к кэшированию в экосистеме .NET: Redis, Memcached и встроенные решения (MemoryCache). Вы узнаете, когда какой инструмент использовать, как правильно настроить кэш и избежать типовых ошибок. Материал будет полезен как начинающим C# разработчикам, так и опытным инженерам, желающим систематизировать знания.
1. Встроенное кэширование: MemoryCache и IDistributedCache
Платформа .NET предоставляет разработчикам несколько встроенных механизмов для кэширования, которые не требуют установки сторонних сервисов.
1.1 MemoryCache — локальный кэш для одного сервера
IMemoryCache — это самый простой и быстрый способ кэширования данных в C#. Данные хранятся в оперативной памяти текущего процесса. Идеально подходит для однопоточных приложений или приложений, работающих на одном сервере.
using Microsoft.Extensions.Caching.Memory;
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 GetProductFromDatabaseAsync(id);
var cacheOptions = new MemoryCacheEntryOptions()
.SetAbsoluteExpiration(TimeSpan.FromMinutes(30))
.SetSlidingExpiration(TimeSpan.FromMinutes(5))
.SetSize(1);
_cache.Set(cacheKey, product, cacheOptions);
}
return product;
}
}
Ключевые особенности MemoryCache:
- Высокая скорость — данные читаются напрямую из RAM
- Отсутствие сериализации — хранит объекты как есть
- Ограничение — не работает в ферме серверов (каждый сервер имеет свой кэш)
- Риск — при перезапуске приложения кэш полностью очищается
1.2 IDistributedCache — абстракция для распределённого кэша
Интерфейс IDistributedCache предоставляет унифицированный API для работы с любым распределённым кэшем. Вы можете переключаться между Redis, SQL Server или NCache, не меняя код бизнес-логики.
using Microsoft.Extensions.Caching.Distributed;
using System.Text.Json;
public class WeatherService
{
private readonly IDistributedCache _cache;
public WeatherService(IDistributedCache cache)
{
_cache = cache;
}
public async Task<WeatherForecast> GetForecastAsync(string city)
{
string cacheKey = $"weather_{city}";
string cachedData = await _cache.GetStringAsync(cacheKey);
if (cachedData != null)
{
return JsonSerializer.Deserialize<WeatherForecast>(cachedData);
}
var forecast = await FetchFromApiAsync(city);
var serialized = JsonSerializer.Serialize(forecast);
await _cache.SetStringAsync(cacheKey, serialized, new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(15)
});
return forecast;
}
}
2. Redis — король распределённого кэша в C#
Redis (Remote Dictionary Server) — это самый популярный in-memory data store, который используется для кэширования в .NET приложениях. Он работает как отдельный сервис, к которому могут подключаться множество экземпляров вашего приложения.
2.1 Установка и настройка StackExchange.Redis
Для работы с Redis в C# используется библиотека StackExchange.Redis — де-факто стандарт в .NET сообществе.
// Установка через NuGet: Install-Package StackExchange.Redis
using StackExchange.Redis;
public class RedisCacheService
{
private readonly ConnectionMultiplexer _redis;
private readonly IDatabase _db;
public RedisCacheService(string connectionString)
{
_redis = ConnectionMultiplexer.Connect(connectionString);
_db = _redis.GetDatabase();
}
public async Task SetAsync<T>(string key, T value, TimeSpan expiry)
{
var json = JsonSerializer.Serialize(value);
await _db.StringSetAsync(key, json, expiry);
}
public async Task<T> GetAsync<T>(string key)
{
var value = await _db.StringGetAsync(key);
if (value.IsNullOrEmpty) return default;
return JsonSerializer.Deserialize<T>(value);
}
}
2.2 Продвинутые возможности Redis для C#
- Сложные структуры данных: списки, множества, хэши, сортированные множества
- Pub/Sub: для уведомлений об изменении кэша в реальном времени
- Locking: распределённые блокировки через RedLock
- Персистентность: данные могут сохраняться на диск (RDB/AOF)
// Пример использования хэшей в Redis для кэширования сложных объектов
public async Task<UserProfile> GetUserProfileAsync(int userId)
{
var hashEntries = await _db.HashGetAllAsync($"user:{userId}");
if (hashEntries.Length == 0)
{
var profile = await LoadFromDatabase(userId);
var entries = new HashEntry[]
{
new("name", profile.Name),
new("email", profile.Email),
new("age", profile.Age.ToString())
};
await _db.HashSetAsync($"user:{userId}", entries);
await _db.KeyExpireAsync($"user:{userId}", TimeSpan.FromHours(1));
return profile;
}
return new UserProfile
{
Name = hashEntries.FirstOrDefault(x => x.Name == "name").Value,
Email = hashEntries.FirstOrDefault(x => x.Name == "email").Value,
Age = int.Parse(hashEntries.FirstOrDefault(x => x.Name == "age").Value)
};
}
3. Memcached — лёгкий и быстрый распределённый кэш
Memcached — это высокопроизводительная распределённая система кэширования. В отличие от Redis, она не поддерживает сложные структуры данных и персистентность, но зато