Возникли проблемы с веб-приложением. Net (C#). Я использую библиотеку LazyCache для кэширования частых JSON ответов (некоторые в пределах и около 80+ КБ) для пользователей, принадлежащих к одной и той же компании в течение сеансов пользователей.
Одна из вещей, которые нам нужно сделать, это отслеживать ключи кеша для конкретной компании, поэтому, когда любой пользователь в компании вносит изменения в кешируемые элементы, нам нужно очистить кеш для этих элементов, чтобы пользователи этой конкретной компании принудительно заполняют кэш сразу после получения следующего запроса.
Мы выбираем библиотеку LazyCache , поскольку мы хотели сделать это в памяти без необходимости использования внешнего кэша. источник, такой как Redis et c, поскольку у нас нет интенсивного использования.
Одна из проблем, с которыми мы сталкиваемся при использовании этого подхода, заключается в том, что мы должны отслеживать все ключи кеша, принадлежащие конкретному клиенту, каждый раз, когда мы кешируем. Поэтому, когда пользователь компании вносит какие-либо изменения в соответствующий ресурс, нам нужно удалить все ключи кеша, принадлежащие этой компании.
Для этого у нас есть глобальный кэш, к которому имеют доступ все веб-контроллеры.
private readonly IAppCache _cache = new CachingService();
protected IAppCache GetCache()
{
return _cache;
}
Упрощенный пример (простите за опечатки!) Наших контроллеров, использующих этот кэш, будет выглядеть примерно так:
[HttpGet]
[Route("{customerId}/accounts/users")]
public async Task<Users> GetUsers([Required]string customerId)
{
var usersBusinessLogic = await _provider.GetUsersBusinessLogic(customerId)
var newCacheKey= "GetUsers." + customerId;
CacheUtil.StoreCacheKey(customerId,newCacheKey)
return await GetCache().GetOrAddAsync(newCacheKey, () => usersBusinessLogic.GetUsers(), DateTimeOffset.Now.AddMinutes(10));
}
Мы используем класс util с методами stati c и stati c параллельный словарь для хранения ключей кэша - каждая компания (GUID) может иметь много ключей кэша.
private static readonly ConcurrentDictionary<Guid, ConcurrentHashSet<string>> cacheKeys = new ConcurrentDictionary<Guid, ConcurrentHashSet<string>>();
public static void StoreCacheKey(Guid customerId, string newCacheKey)
{
cacheKeys.AddOrUpdate(customerId, new ConcurrentHashSet<string>() { newCacheKey }, (key, existingCacheKeys) =>
{
existingCacheKeys.Add(newCacheKey);
return existingCacheKeys;
});
}
В том же самом классе утилит, когда нам нужно удалить все ключи кеша для конкретной компании, у нас есть метод, аналогичный приведенному ниже (который вызывается при внесении мутантных изменений в другие контроллеры)
public static void ClearCustomerCache(IAppCache cache, Guid customerId)
{
var customerCacheKeys = new ConcurrentHashSet<string>();
if (!cacheKeys.TryGetValue(customerId,out customerCacheKeys))
{
return new ConcurrentHashSet<string>();
}
foreach (var cacheKey in customerCacheKeys)
{
cache.Remove(cacheKey);
}
cacheKeys.TryRemove(customerId, out _);
}
В последнее время у нас возникают проблемы с производительностью, из-за которых время отклика наших веб-запросов значительно замедляется - мы не видим значительных изменений в количестве запросов в секунду.
Глядя на метрики сбора мусора, мы, кажется, замечаем большой размер кучи Gen 2 и большой размер объекта, который, кажется, идет вверх - мы не видим, что память была восстановлена.
Мы все еще находимся в процессе отладки, но мне интересно, может ли использование описанного выше подхода привести к проблемам, которые мы видим. Мы хотим обеспечить безопасность потоков, но может возникнуть проблема с использованием имеющегося выше словаря параллелизма, который даже после удаления элементов не освобождает память, что приводит к чрезмерному сбору Gen 2.
Также мы используем режим сбора мусора на рабочей станции, представьте, что переключение в режим сервера G C поможет нам (наш IIS-сервер имеет 8 процессоров + 16 ГБ ОЗУ), но не уверен, что переключение решит все проблемы.