Ответ
Да, активно использовал кеширование для повышения производительности и снижения нагрузки на источники данных (БД, внешние API).
Основные подходы в .NET:
-
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; } }
-
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) кеша.
Ответ 18+ 🔞
Ну, смотри, кеширование — это как если бы ты каждый раз за пивом не в магазин бегал, а из своего холодильника брал. Работает, пока запасы есть, и не надо каждый раз ноги бить.
В .NET, если по-простому, есть два основных подхода, и они, блядь, как небо и земля.
Первый — IMemoryCache (кеш в памяти процесса).
Это твой личный холодильник в квартире. Быстро, под рукой, но если сосед захочет — ему не дашь. Для одного экземпляра приложения — самое то.
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;
}
}
Второй — IDistributedCache (распределённый кеш).
Это уже общий холодильник в офисе на десять человек. Redis, SQL Server или что-то подобное. Подходит, когда у тебя несколько инстансов приложения и им надо делиться данными, а не драться за них.
// Регаем Redis, как самого надёжного пацана для этой работы
services.AddStackExchangeRedisCache(options =>
{
options.Configuration = "localhost:6379";
});
// А в сервисе уже работаем
var cachedData = await _distributedCache.GetStringAsync("myKey");
if (cachedData == null)
{
// Сериализуем, потому что Redis — не падла, он бинарщину любит
cachedData = JsonSerializer.Serialize(freshData);
await _distributedCache.SetStringAsync("myKey", cachedData, new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10) // Через десять минут — в утиль
});
}
А теперь, блядь, самое важное, где все обычно обжигаются:
- Инвалидация кеша. Ты изменил данные в базе, а в кеше лежит старая хуйня. Надо чётко придумать, когда и как кеш сносить, иначе пользователи будут видеть древние данные, и доверия к тебе — ноль ебать.
- Сериализация. Для распределённого кеша всё надо превращать в строку или байты. Выбирай сериализатор с умом, а то JSON может раздуться, как пивной живот.
- Просадки (Cache Stampede). Представь: кеш протух, и сто потоков одновременно полезли в базу, чтобы его обновить. База ложится, как муха от дихлофоса. Используй
Lazy<T>или асинхронные блокировки, чтобы только один поток пошёл за данными, а остальные ждали, как в очереди за колбасой. - Многоуровневое кеширование. Можно комбинировать: быстрый in-memory кеш (L1) как первая линия обороны, а за ним распределённый (L2) как тяжёлая артиллерия. Но тут уже сложность растёт, как грибы после дождя.
Короче, кеширование — это мощно, но если накосячить, то отладка превращается в ад, где ты ищешь иголку в стоге сена, который ещё и сам постоянно меняется. Держи ухо востро.