(495) 925-0049, ITShop интернет-магазин 229-0436, Учебный Центр 925-0049
  Главная страница Карта сайта Контакты
Поиск
Вход
Регистрация
Рассылки сайта
 
 
 
 
 

Расширяем и улучшаем Cache в ASP.NET

Источник: realcoding

Про ASP.NET-объект Cache наверняка знает каждый web-разработчик на платформе .NET. Совсем не странно, ведь это единственное решение для кэширования данных web-приложения в ASP.NET, доступное прямо из коробки.
Достаточно функциональный и легкий, снабженный механизмами приоритета, вытеснения, зависимостей и обратных вызовов, Cache хорошо подходит для небольших приложений, работая внутри AppDomain. Кажется, Microsoft предусмотрела все, что необходимо… Но я, тем не менее, хочу сделать его еще немного лучше. Чем же именно?

Синхронизация обновлений

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

расскажет

нам, как это сделать, и мы напишем так:

List<Product> products;
products = (List<Product>)Cache["Products"];
if (products == null)
{
  products = db.Products.ToList();
  Cache.Insert("Products", products);
}

* This source code was highlighted with Source Code Highlighter.

Все выглядит правильно, но ровно до тех пор, пока мы не осознаем, что код может выполняться одновременно в нескольких потоках. И в этих нескольких строчках мы только что организовали классическое состояние гонки (race condition). Ничего страшного, конечно, не произойдет, просто элемент кэша будет обновлен несколько раз, и каждый раз для этого мы обратимся к базе данных. Но это лишняя работа, и ее можно избежать, применив обычную double-check блокировку. Вот так:

private static object _lock = new object();

...

object value;
if ((value = Cache["Products"]) == null)
{
  lock (_lock)
  {
   if ((value = Cache["Products"]) == null)
   {
      value = db.Products.ToList();
      Cache.Insert("Products", value);
   }
  }
}
var products = (List<Product>)value;

* This source code was highlighted with Source Code Highlighter.

Таким образом, мы гарантируем, что только один поток отправится в базу данных за списком товаров, а остальные подождут его возвращения. Можно писать такой код всякий раз, когда мы работаем с кэшем, но лучше реализовать расширение объекта Cache при помощи extension-метода.

Итак,

public static T Get<T>(this Cache cache, string key, object @lock, Func<T> selector,
  DateTime absoluteExpiration)
{
   object value;
   if ((value = cache.Get(key)) == null)
   {
     lock (@lock)
     {
      if ((value = cache.Get(key)) == null)
      {
        value = selector();
        cache.Insert(key, value, null,
         absoluteExpiration, Cache.NoSlidingExpiration,
         CacheItemPriority.Normal, null);
      }
   }
  }
  return (T)value;
}
* This source code was highlighted with Source Code Highlighter.

Если в кэше нашелся элемент с заданным ключом, метод просто возвратит его, а в противном случае установит блокировку и выполнит загрузку. Конструкции

value = Cache.Get(key)

нужны для того, чтобы не получить такую же гонку при удалении элемента кэша в другом потоке. Теперь для получения нашего списка товаров мы можем написать только одну строку, а все остальное наше расширение возьмет на себя. Перегрузки можно добавить по вкусу :)

private static object myLock = new object();
...
var products = Cache.Get<List<Product>>("Products", myLock,
  () => db.Products.ToList(), DateTime.Now.AddMinutes(10));

* This source code was highlighted with Source Code Highlighter.

Итак, с одной задачей мы расправились, но есть еще кое-что интересное. К примеру, ситуация, когда необходимо объявить невалидными сразу несколько связанных элементов кэша. ASP.NET Cache предоставляет нам возможность создания зависимости от одного или нескольких элементов. Примерно так:

string[] dependencies = { "parent" };
Cache.Insert("child", someData,
  new CacheDependency(null, dependencies));

* This source code was highlighted with Source Code Highlighter.

И при обновлении элемента

parent

элемент

child

будет удален. Пока ничего не напоминает? Что ж, еще немного кода, и у нас появится полноценная…

Поддержка тегов и групповой инвалидации

Подсистема тегирования будет работать вполне прозрачно, используя уже описанный механизм зависимостей от ключа в кэше. Такими ключами и будут - угадайте что? - теги. При добавлении в кэш элемента с некоторой коллекцией тегов мы создадим соответствующее количество ключей в кэше и зависимость от них.

public static CacheDependency CreateTagDependency(
  this Cache cache, params string[] tags)
{
  if (tags == null // tags.Length < 1)
   return null;

  long version = DateTime.UtcNow.Ticks;
  for (int i = 0; i < tags.Length; ++i)
  {
   cache.Add("_tag:" + tags[i], version, null,
     DateTime.MaxValue, Cache.NoSlidingExpiration,
     CacheItemPriority.NotRemovable, null);
  }
  return new CacheDependency(null, tags.Select(s =>
   "_tag:" + s).ToArray());
}

* This source code was highlighted with Source Code Highlighter.

Здесь в качестве значения версии тега я использую текущее время, как рекомендовалось в

статье о Memcached

, но в нашем случае сравнивать ничего не придется, этим займется ASP.NET. Теперь при добавлении элемента в кэш мы можем легко и просто создать зависимость от указанных нами тегов.

Сache.Insert("key", value, Сache.CreateTagDependency("tag1", "tag2"));

* This source code was highlighted with Source Code Highlighter.

Осталось совсем немного - обеспечить сброс такой группы элементов в кэше. Для этого нужно всего лишь обновить элементы кэша, представляющие интересующие нас теги. Все остальное произойдет само собой.

public static void Invalidate(this Cache cache, params string[] tags)
{
  long version = DateTime.UtcNow.Ticks;
  for (int i = 0; i < tags.Length; ++i)
  {
   cache.Insert("_tag:" + tags[i], version, null,
     DateTime.MaxValue, Cache.NoSlidingExpiration,
     CacheItemPriority.NotRemovable, null);
  }
}

* This source code was highlighted with Source Code Highlighter.

Обратите внимание на то, что в методе, создающем зависимость от тегов, использовался метод

cache.Add

, а здесь -

cache.Insert

. Существенная разница между этими в остальном очень похожими методами в том, что первый записывает информацию в кэш только в том случае, если указанный ключ не был создан ранее, а второй - записывает в любом случае, затирая старые данные. Это различие очень важно в нашем случае, потому что при простом добавлении элемента в кэш нам не нужно обновлять уже существующие теги.

На этом, кажется, и все…

Я требую продолжения банкета!

А продолжать тут еще есть куда. Например, можно доработать приведенный здесь метод Get так, чтобы вместо немедленного удаления данные кэша временно "переезжали" в другую ячейку, и вместо блокировки возвращать запрошенную информацию из нее, пока новые данные загружаются в кэш.

Можно вместо extension-методов сделать некую абстракцию провайдера кэширования и работать с любым хранилищем без изменения кода приложения или полностью отключать кэш при отладке, использовать IoC… да мало ли что!

И я надеюсь, что подходы, описанные в моей статье, окажутся полезными для вас ;)

UPDATE

: Посмотрев незамыленным глазом на собственный код, я узрел в нем одну нехорошую вещь - блокировка при синхронизации выставлялась на весь кэш целиком. Поэтому я изменил extension-метод

Get

с тем, чтобы он принимал пользовательский объект для блокировки.

Ссылки по теме


 Распечатать »
 Правила публикации »
  Написать редактору 
 Рекомендовать » Дата публикации: 15.12.2009 
 

Магазин программного обеспечения   WWW.ITSHOP.RU
Microsoft Office 365 Персональный 32-bit/x64. 1 ПК/MAC + 1 Планшет + 1 Телефон. Все языки. Подписка на 1 год.
Microsoft 365 Business Standard (corporate)
Microsoft Windows Professional 10, Электронный ключ
Microsoft Office 365 для Дома 32-bit/x64. 5 ПК/Mac + 5 Планшетов + 5 Телефонов. Подписка на 1 год.
Microsoft Office для дома и учебы 2019 (лицензия ESD)
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
Безопасность компьютерных сетей и защита информации
Новости ITShop.ru - ПО, книги, документация, курсы обучения
Программирование на Microsoft Access
CASE-технологии
Утиль - лучший бесплатный софт для Windows
Новые программы для Windows
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
 



    
rambler's top100 Rambler's Top100