В общем, причина кеширования в том, что вы чувствуете, что можете извлечь данные из памяти (без их устаревания) быстрее, чем вы можете извлечь их из базы данных. Ситуация, когда вы можете извлечь нужные данные из кэша, является попаданием в кэш. Если ваша схема имеет низкий коэффициент попадания в кэш, то, вероятно, кэш наносит больший вред, чем помогает. Если ваши данные быстро изменяются, у вас будет низкий коэффициент попадания в кэш, и он будет медленнее, чем простой запрос данных.
Хитрость в том, чтобы разделить ваши данные между редко меняющимися и часто меняющимися элементами. Кэшируйте редко меняющиеся элементы и не кешируйте часто меняющиеся элементы. Это можно сделать даже на уровне базы данных для одной сущности, используя отношение 1: 1, где одна из таблиц содержит редко меняющиеся данные и другую часто меняющуюся информацию. Вы сказали, что ваши исходные данные будут содержать 10 столбцов, которые почти никогда не будут изменить и 90, которые часто меняются. Постройте свои объекты вокруг этого понятия, чтобы вы могли кэшировать 10, которые редко меняются, и запрашивать 90, которые часто меняются.
Я храню каждую строку в классе и
класс хранится в кэше сервера
через огромный список
Из вашего исходного поста звучит так, будто вы храните не каждый экземпляр в кэше, а вместо этого список экземпляров в кэше в виде одной записи. Проблема в том, что вы можете получить многопоточность в этом проекте. Когда несколько потоков извлекают один список для правила их всех, все они обращаются к одному и тому же экземпляру в памяти (при условии, что они находятся на одном сервере). Кроме того, как вы обнаружили, CacheDependency
не будет работать в этом дизайне, потому что у него истечет весь список, а не один элемент.
Одним из очевидных, но весьма проблемных решений было бы изменить ваш дизайн для хранения каждого экземпляра в памяти с помощью некоторого логического ключа кэша и добавить CacheDependency
для каждого экземпляра. Проблема заключается в том, что, если количество экземпляров велико, это создаст много накладных расходов в системе, проверяя валюту каждого из экземпляров и истекая при необходимости. Если элементы кэша опрашивают базу данных, это также создаст много трафика.
Подход, который я использовал для решения проблемы наличия большого числа зависимых от базы данных CacheDependencies, заключается в создании настраиваемого ICacheItemExpiration в CachingBlock из Enterprise Library. Это также означало, что я использовал CachingBlock для кэширования своих объектов, а не кеш ASP.NET напрямую. В этом варианте я создал класс под названием DatabaseExpirationManager
, который отслеживал, какие элементы истекают из кэша. Я бы по-прежнему добавлял каждый элемент в кеш индивидуально, но с этой измененной зависимостью, которая просто регистрировала элемент с DatabaseExpirationManager
. DatabaseExpirationManager
будет уведомлен о ключах, срок действия которых должен истечь, и истечет срок действия элементов из кэша. С самого начала я скажу, что это решение, вероятно, не будет работать с быстро меняющимися данными. DatabaseExpirationManager
будет работать постоянно, удерживая блокировку в списке элементов, срок действия которой истекает, и предотвращая добавление новых элементов. Вам нужно будет провести серьезный многопоточный анализ, чтобы убедиться, что вы сократили конкуренцию, не включив условие гонки.
ДОПОЛНЕНИЕ
Ok. Во-первых, справедливое предупреждение о том, что это будет длинный пост. Во-вторых, это даже не вся библиотека, так как это было бы слишком долго.
На обратном пути я написал этот код в начале и конце 2005 / начале 2006 года, когда вышел .NET 2.0, и я не исследовал, могут ли более современные библиотеки справиться с этим лучше (почти наверняка они ). Я использовал библиотеки января 2005 года / мая 2005 года / января 2006 года. Вы все еще можете получить библиотеку 2006 года от CodePlex.
Я пришел к этому решению, посмотрев на источник системы кэширования в Enterprise Library. Короче, все кормили через класс CacheManager
. Этот класс имеет три основных компонента (все три находятся в пространстве имен Microsoft.Practices.EnterpriseLibrary.Caching
):
Cache
BackgroundScheduler
ExpirationPollTimer
Класс Cache
является реализацией кеша EntLib. BackgroundScheduler
использовался для очистки кэша в отдельном потоке. ExpirationPollTimer
был оберткой вокруг Timer
класса.
Итак, во-первых, следует отметить, что Cache
очищает себя на основе таймера. Точно так же мое решение будет опрашивать базу данных по таймеру. Кеш EntLib и кеш ASP.NET работают с отдельными элементами, имеющими делегата для проверки истечения срока действия элемента. Мое решение работало при условии, что внешняя сущность проверит, когда истекает срок действия товаров. Второе, на что следует обратить внимание, это то, что когда вы начинаете играть с центральным кешем, вы должны быть внимательны к проблемам многопоточности.
Сначала я заменил BackgroundScheduler
на два класса: DatabaseExpirationWorker
и DatabaseExpirationManager
. DatabaseExpirationManager
содержит важный метод, который запрашивает базу данных на предмет изменений и передает список изменений событию:
private object _syncRoot = new object();
private List<Guid> _objectChanges = new List<Guid>();
public event EventHandler<DatabaseExpirationEventArgs> ExpirationFired;
...
public void UpdateExpirations()
{
lock ( _syncRoot )
{
DataTable dt = GetExpirationsFromDb();
List<Guid> keys = new List<Guid>();
foreach ( DataRow dr in dt.Rows )
{
Guid key = (Guid)dr[0];
keys.Add(key);
_objectChanges.Add(key);
}
if ( ExpirationFired != null )
ExpirationFired(this, new DatabaseExpirationEventArgs(keys));
}
}
Класс DatabaseExpirationEventArgs
выглядел так:
public class DatabaseExpirationEventArgs : System.EventArgs
{
public DatabaseExpirationEventArgs( List<Guid> expiredKeys )
{
_expiredKeys = expiredKeys;
}
private List<Guid> _expiredKeys;
public List<Guid> ExpiredKeys
{
get { return _expiredKeys; }
}
}
В этой базе данных все первичные ключи были Guids. Это значительно упрощает отслеживание изменений. Каждый из методов сохранения на среднем уровне записывает в таблицу свои PK и текущее время. Каждый раз, когда система опрашивала базу данных, она сохраняла дату и время (из базы данных, а не из промежуточного уровня), когда она инициировала опрос, и GetExpirationsFromDb
вернет все элементы, которые изменились с того времени. Другой метод будет периодически удалять строки, которые были опрошены давно. Эта таблица изменений была очень узкой: guid и datetime (с PK в обоих столбцах и кластеризованным индексом в datetime IIRC). Таким образом, это может быть запрошено очень быстро. Также обратите внимание, что я использовал Guid в качестве ключа в Cache.
Класс DatabaseExpirationWorker
был почти идентичен классу BackgroundScheduler
, за исключением того, что его DoExpirationTimeoutExpired
будет вызывать метод DatabaseExpirationManager
UpdateExpirations
. Поскольку ни один из методов в BackgroundScheduler
не был virtual
, я не мог просто извлечь из BackgroundScheduler
и переопределить его методы.
Последнее, что я сделал, это написал свою собственную версию CacheManager EntLib, которая использовала мой DatabaseExpirationWorker
вместо BackgroundScheduler
, и его индексатор проверял бы список истечения срока действия объекта:
private List<Guid> _objectExpirations;
private void OnExpirationFired( object sender, DatabaseExpirationEventArgs e )
{
_objectExpirations = e.ExpiredKeys;
lock(_objectExpirations)
{
foreach( Guid key in _objectExpirations)
this.RealCache.Remove(key);
}
}
private Microsoft.Practices.EnterpriseLibrary.Caching.CacheManager _realCache;
private Microsoft.Practices.EnterpriseLibrary.Caching.CacheManager RealCache
{
get
{
lock(_syncRoot)
{
if ( _realCache == null )
_realCache = Microsoft.Practices.EnterpriseLibrary.Caching.CacheManager.CacheFactory.GetCacheManager();
return _realCache;
}
}
}
public object this[string key]
{
get
{
lock(_objectExpirations)
{
if (_objectExpirations.Contains(key))
return null;
return this.RealCache.GetData(key);
}
}
}
Опять же, с тех пор, как я просмотрел этот код, было много лун, но это дает вам представление о нем. Даже просматривая мой старый код, я вижу много мест, которые можно очистить и очистить. Я также не смотрел на блок Caching в самой последней версии EntLib, но думаю, что он изменился и улучшился. Имейте в виду, что в системе, в которой я это построил, в секунду происходили десятки изменений, а не сотни. Итак, если данные устарели в течение минуты или двух, это было приемлемо. Если в вашем решении тысячи изменений в секунду, то это решение может оказаться неосуществимым.