Кэширование в C#: Redis, Memcached и встроенные решения .NET

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Кэширование в C#: Redis, Memcached и встроенные решения для .NET

Кэширование — один из ключевых инструментов повышения производительности приложений на C#. Оно позволяет сократить время отклика, снизить нагрузку на базы данных и внешние API. В экосистеме .NET доступно несколько подходов: от встроенного MemoryCache до распределённых решений на базе Redis и Memcached. В этой статье мы разберём каждый вариант, покажем примеры кода и дадим рекомендации по выбору.

1. Встроенное кэширование: MemoryCache и IDistributedCache

Платформа .NET предоставляет два основных абстрактных механизма для работы с кэшем: IMemoryCache (in-process) и IDistributedCache (распределённый).

1.1 IMemoryCache

Хранит данные в памяти процесса. Идеально для однопоточных или монолитных приложений, где не требуется синхронизация между несколькими серверами. Прост в настройке и не требует внешних зависимостей.

// Добавление в DI (Program.cs)
builder.Services.AddMemoryCache();

// Использование в сервисе
public class WeatherService
{
    private readonly IMemoryCache _cache;

    public WeatherService(IMemoryCache cache)
    {
        _cache = cache;
    }

    public async Task<string> GetWeatherAsync(string city)
    {
        string cacheKey = $"weather_{city}";
        if (_cache.TryGetValue(cacheKey, out string cachedData))
        {
            return cachedData;
        }

        // Эмуляция долгого запроса
        await Task.Delay(2000);
        string result = $"Погода в {city}: +25°C";

        var options = new MemoryCacheEntryOptions()
            .SetAbsoluteExpiration(TimeSpan.FromMinutes(5))
            .SetSlidingExpiration(TimeSpan.FromMinutes(2));

        _cache.Set(cacheKey, result, options);
        return result;
    }
}

1.2 IDistributedCache

Абстракция для распределённого кэша. Позволяет легко переключаться между провайдерами (Redis, SQL Server, NCache) без изменения бизнес-логики. По умолчанию хранит данные в виде byte[].

// Регистрация (Program.cs) - используем Redis
builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = "localhost:6379";
});

// Использование
public class ProductService
{
    private readonly IDistributedCache _cache;

    public ProductService(IDistributedCache cache)
    {
        _cache = cache;
    }

    public async Task<Product> GetProductAsync(int id)
    {
        string cacheKey = $"product_{id}";
        var cachedBytes = await _cache.GetAsync(cacheKey);

        if (cachedBytes != null)
        {
            return JsonSerializer.Deserialize<Product>(cachedBytes);
        }

        // Запрос к БД
        var product = await GetProductFromDb(id);

        var options = new DistributedCacheEntryOptions()
            .SetAbsoluteExpiration(TimeSpan.FromHours(1));

        var serialized = JsonSerializer.SerializeToUtf8Bytes(product);
        await _cache.SetAsync(cacheKey, serialized, options);

        return product;
    }
}

2. Redis: распределённый кэш с продвинутыми возможностями

Redis — самое популярное решение для кэширования в .NET. Работает как высокопроизводительное in-memory хранилище с поддержкой сложных структур данных (списки, хэши, множества).

2.1 Установка и настройка

Для подключения Redis к .NET используйте пакет Microsoft.Extensions.Caching.StackExchangeRedis.

dotnet add package Microsoft.Extensions.Caching.StackExchangeRedis

2.2 Пример с Redis и IDistributedCache

Помимо базового кэширования, Redis позволяет реализовать:

  • Сессии пользователей — хранение данных сессии между запросами.
  • Блокировки (Distributed Lock) — синхронизация доступа к ресурсам.
  • Pub/Sub — обмен сообщениями между сервисами.
// Пример использования Redis для блокировки
public class OrderService
{
    private readonly ConnectionMultiplexer _redis;

    public OrderService(ConnectionMultiplexer redis)
    {
        _redis = redis;
    }

    public async Task<bool> TryProcessOrder(int orderId)
    {
        var db = _redis.GetDatabase();
        string lockKey = $"lock_order_{orderId}";
        string lockValue = Guid.NewGuid().ToString();

        bool acquired = await db.LockTakeAsync(lockKey, lockValue, TimeSpan.FromSeconds(10));
        if (!acquired) return false;

        try
        {
            // Обработка заказа
            await Task.Delay(100);
            return true;
        }
        finally
        {
            await db.LockReleaseAsync(lockKey, lockValue);
        }
    }
}

3. Memcached: простота и скорость

Memcached — распределённая система кэширования, ориентированная на простые пары «ключ-значение». В отличие от Redis, не поддерживает сложные структуры данных и персистентность, но выигрывает в скорости на простых операциях.

3.1 Подключение Memcached к .NET

Используйте библиотеку EnyimMemcachedCore (для ASP.NET Core) или Memcached(Sharp).

dotnet add package EnyimMemcachedCore

// Program.cs
builder.Services.AddEnyimMemcached(options =>
{
    options.AddServer("localhost", 11211);
});

// Использование через IDistributedCache
public class AnalyticsService
{
    private readonly IDistributedCache _cache;

    public AnalyticsService(IDistributedCache cache)
    {
        _cache = cache;
    }

    public async Task<int> GetPageViewsAsync(string pageUrl)
    {
        string cacheKey = $"views_{pageUrl}";
        var cached = await _cache.GetStringAsync(cacheKey);

        if (cached != null && int.TryParse(cached, out int views))
        {
            return views;
        }

        // Получаем из БД
        views = await CalculateViewsFromDb(pageUrl);
        await _cache.SetStringAsync(cacheKey, views.ToString(), 
            new DistributedCacheEntryOptions 
            { 
                AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30) 
            });

        return views;
    }
}

3.2 Когда выбирать Memcached?

  • Требуется максимальная скорость на простых операциях Get/Set.
  • Нет необходимости в персистентности или репликации.
  • Объём данных невелик (до 1 МБ на ключ).

4. Сравнение и рекомендации

Выбор решения зависит от архитектуры и требований к проекту:

Блоги

Рекомендаций по книгам