Работали ли вы с кешированием данных?

«Работали ли вы с кешированием данных?» — вопрос из категории Архитектура, который задают на 25% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Да, активно использовал кеширование для повышения производительности и снижения нагрузки на источники данных (БД, внешние API).

Основные подходы в .NET:

  1. IMemoryCache (In-Memory Cache):

    • Для чего: Кеширование в памяти одного экземпляра приложения. Быстро, но не распределенное.
    • Пример:

      public class ProductService
      {
      private readonly IMemoryCache _cache;
      private const string CacheKey = "TopProducts";
      
      public ProductService(IMemoryCache cache) => _cache = cache;
      
      public async Task<List<Product>> GetTopProductsAsync()
      {
          if (!_cache.TryGetValue(CacheKey, out List<Product> products))
          {
              products = await _database.FetchTopProductsAsync(); // Дорогой запрос
              var options = new MemoryCacheEntryOptions()
                  .SetSlidingExpiration(TimeSpan.FromMinutes(5)) // "Сдвигается" при каждом обращении
                  .SetAbsoluteExpiration(TimeSpan.FromHours(1)); // Максимальное время жизни
      
              _cache.Set(CacheKey, products, options);
          }
          return products;
      }
      }
  2. IDistributedCache (Распределенный кеш):

    • Для чего: Когда несколько экземпляров приложения должны разделять общий кеш (веб-ферма, микросервисы).
    • Поставщики: Redis (наиболее популярен), SQL Server, NCache.
    • Пример (с Redis):
      
      // В Startup/Program.cs
      services.AddStackExchangeRedisCache(options =>
      {
      options.Configuration = "localhost:6379";
      });

    // В сервисе var cachedData = await _distributedCache.GetStringAsync("myKey"); if (cachedData == null) { cachedData = JsonSerializer.Serialize(freshData); await _distributedCache.SetStringAsync("myKey", cachedData, new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10) }); }

Критические аспекты при работе с кешем:

  • Инвалидация: Четкая стратегия обновления/удаления данных при их изменении.
  • Сериализация: Для IDistributedCache данные должны быть сериализуемы.
  • Просадки (Cache Stampede): Использование Lazy или семантики GetOrAdd для предотвращения одновременного обновления кеша множеством потоков.
  • Уровни кеширования: Комбинация in-memory (L1) и распределенного (L2) кеша.