Поиск на сайте: Расширенный поиск


Новые программы oszone.net Читать ленту новостей RSS
CheckBootSpeed - это диагностический пакет на основе скриптов PowerShell, создающий отчет о скорости загрузки Windows 7 ...
Вы когда-нибудь хотели создать установочный диск Windows, который бы автоматически установил систему, не задавая вопросо...
Если после установки Windows XP у вас перестала загружаться Windows Vista или Windows 7, вам необходимо восстановить заг...
Программа подготовки документов и ведения учетных и отчетных данных по командировкам. Используются формы, утвержденные п...
Red Button – это мощная утилита для оптимизации и очистки всех актуальных клиентских версий операционной системы Windows...
OSzone.net Microsoft Разработка приложений .NET Framework Как обрабатывать реляционные данные в распределенном кеше RSS

Как обрабатывать реляционные данные в распределенном кеше

Текущий рейтинг: 5 (проголосовало 1)
 Посетителей: 836 | Просмотров: 2125 (сегодня 0)  Шрифт: - +

Microsoft .NET Framework стала популярной в разработке приложений с интенсивными транзакциями для таких платформ и архитектур, как Web, SOA (Service-Oriented Architecture), высокопроизводительные, распределенные и облачные вычисления. Все эти архитектуры являются масштабируемыми. Но главное узкое место — это хранилище данных, обычно реляционная база данных, которое не способно к масштабированию и обработке транзакционной нагрузки в той мере, в какой это позволяет прикладной уровень.

В связи с этим довольно популярным стал распределенный кеш, так как он дает возможность кешировать данные в памяти, обеспечивающей гораздо более быстрый доступ, чем любая база данных. Кроме того, распределенное кеширование обеспечивает линейное масштабирование. Благодаря этому при необходимости обработки более интенсивной транзакционной нагрузки можно добавлять в кластер распределенного кеша новые серверы кеша. Инкрементальный прирост пропускной способности по транзакциям не уменьшается по мере добавления все новых и новых серверов (он представляет собой прямую линию на графике, где по оси X откладывается число серверов кеша, а по оси Y — пропускная способность по транзакциям). На рис. 1 показано, как распределенное кеширование соотносится с типичным приложением ASP.NET или Windows Communication Foundation (WCF) и как оно обеспечивает линейное масштабирование.

*
Рис. 1. Распределенный кеш, используемый в приложениях ASP.NET или Windows Communication Foundation

Load BalancerБалансировка нагрузки
ASP.NET Apps/WCF ServicesПриложения ASP.NET/WCF-сервисы
Enterprise Distributed CacheКорпоративный распределенный кеш
App DataДанные приложений
Database ServersСерверы базы данных
Memory PooledПул памяти
Windows Server 2008 (64-bit)Windows Server 2008 (64-разрядная)
Scale SeamlesslyБесшовное масштабирование

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

Хотя распределенное кеширование — отличная штука, одна из трудных задач заключается в том, как кешировать реляционные данные, имеющие взаимосвязи между элементами данных. Проблема вызвана тем, что распределенный кеш предоставляет простой интерфейс в стиле хеш-таблицы (ключ, значение), где ключ уникально идентифицирует кешированный элемент, а значение — это ваши данные или объект. Однако ваше приложение наверняка имеет сложную объектную модель предметной области с наследованием, агрегацией и другими типами взаимосвязей.

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

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

Объектно-реляционное сопоставление

Первым делом преобразуйте свои реляционные данные в объектную модель предметной области. Без этого этапа вас ждут трудные времена в обработке всех взаимосвязей в распределенном кеше.

В любом .NET-приложении вы скорее всего извлекаете данные из базы данных с помощью DataReader (SqlDataReader, OracleDataReader или OleDbDataReader) либо DataTable, что нормально. Но многие разработчики потом напрямую обращаются к данным этих объектов (особенно в DataTable). Некоторые делают это из-за лени, потому что не хотят создавать собственные объекты предметной области. Другие — потому, что считают, что так они используют средства фильтрации в DataTable.

Я настоятельно рекомендую преобразовывать DataReader или Data Table в объектную модель предметной области. Это значительно упростит архитектуру приложения и позволит эффективно задействовать распределенный кеш. А хороший распределенный кеш обычно предоставляет возможность запросов в стиле SQL или LINQ, поэтому вы не лишитесь средств фильтрации в DataTable.

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

Вы можете либо вручную преобразовать DataReader и DataTable в объекты предметной области, либо использовать один из популярных механизмов Object Relation Mapping (ORM). Таким механизмом, в частности, является инфраструктура Microsoft Entity Framework. Другой популярный механизм — NHibernate (это проект с открытым исходным кодом). ORM значительно упрощает вашу задачу преобразования реляционных данных в объектную модель предметной области.

CacheDependency помогает управлять взаимосвязями

В случае объектной модели предметной области сразу же возникает вопрос: как обрабатывать все взаимосвязи в кеше. Ответ — с помощью CacheDependency, который является частью ASP.NET Cache и который теперь можно найти и в некоторых коммерческих решениях в области распределенных кешей.

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

Допустим, элемент данных A зависит от B и, если B обновляется или удаляется из кеша, автоматически удаляется и элемент A. CacheDependency также поддерживает каскадные связи, поэтому, если A зависит от B, а B — от C и C обновляется или удаляется, это приводит к автоматическому удалению B из распределенного кеша. Как только это происходит, A тоже автоматически удаляется. Это называют каскадным CacheDependency.

Позднее в статье я воспользуюсь CacheDependency для демонстрации того, как обрабатывать взаимосвязи в распределенном кеше.

Три типа отношений

Прежде всего в целях последующего обсуждения задействуем пример модели данных, приведенный на рис. 2.

*
Рис. 2. Пример модели данных для обсуждения отношений

Как видите, в этой модели данных Customer имеет отношение «один ко многим» с Order, Product — отношение «один ко многим» с Order, а Customer и Product — отношение «многие ко многим» друг с другом через таблицу Order. Для нашего примера модели данных на рис. 3 показана эквивалентная объектная модель, представляющая те же отношения.

*
Рис. 3. Пример объектной модели, основанной на модели данных

Прежде чем углубиться в детали различных типов отношений, я хочу пояснить одну вещь. В отличие от базы данных объектная модель предметной области приложения всегда имеет основной объект (primary object), который приложение извлекает из базы данных и который является начальной точкой для приложения при выполнении любой транзакции. Все остальные объекты видны как связанные с этим основным объектом. Эта концепция действует для всех типов отношений и влияет на то, как вы видите отношения в объектной модели предметной области. Ну а теперь продолжим.

Отношения «один к одному» и «многие к одному»

Отношения «один к одному» и «многие к одному» аналогичны. При отношении «один к одному» между таблицами A и B, одна запись в таблице A связана только с одной записью в таблице B. Вы храните внешний ключ либо в таблице A, либо в таблице B, и он является основный ключом другой таблицы.

В случае отношения «многие к одному» между таблицами A и B вы должны хранить внешний ключ в таблице A. Это внешний ключ является основным ключом таблицы B. В обоих случаях внешний ключ имеет уникальное ограничение, чтобы гарантировать отсутствие дубликатов.

То же отношение можно легко трансформировать в объектную модель предметной области. Основной объект (поясню позже) хранит ссылку на связанный объект. На рис. 4 показан пример основного объекта, хранящего информацию о взаимосвязях. Заметьте, что класс Order содержит ссылку на класс Customer, указывая отношение «многие к одному». То же самое было бы даже в случае отношения «один к одному».

Figure 4. Caching a Related Object Separately

public void CacheOrder(Order order)
{
  Cache cache = HttpRuntime.Cache;
  DateTime absolutionExpiration = Cache.NoAbsoluteExpiration;
  TimeSpan slidingExpiration = Cache.NoSlidingExpiration;
  CacheItemPriority priority = CacheItemPriority.Default;
  if (order != null) {
    // Это предотвращает кеширование Customer с Order
    Customer cust = order.OrderingCustomer;
    // Присваиваем null объектам order, чтобы они не кешировались с Customer
    cust.Orders = null;
    string custKey = "Customer:CustomerId:" + cust.CustomerId;
    cache.Add(custKey, cust, null,
              absolutionExpiration,
              slidingExpiration,
              priority, null);
    // Зависимость гарантирует удаление order при обновлении/удалении Cust
    string[] keys = new string[1];
    keys[0] = custKey;
    CacheDependency dep = new CacheDependency(null, keys);
    string orderKey = "Order:CustomerId:" + order.CustomerId
      + ":ProductId:" + order.ProductId;
    // Это приведет к кешированию только объекта Order
    cache.Add(orderKey, order, dep,
              absolutionExpiration,
              slidingExpiration,
              priority, null);
  }
}

Отношения «один ко многим» (обратны отношениям «многие к одному»)

В случае отношения «один ко многим» между таблицами A и B в базе данных таблица B (сторона «многие») хранит внешний ключ, который на самом деле является основным ключом таблицы A, но без уникального ограничения внешнего ключа.

В объектной модели предметной области с отношениями «один ко многим» основной объект — Customer, а связанный объект — Order. Поэтому объект Customer содержит набор объектов Order. На рис. 4 также показан пример этого типа отношений между объектами Customer и Order.

Отношения «многие ко многим»

В случае отношения «многие ко многим» между таблицами A и B всегда имеется промежуточная таблица AB. В нашем примере Order является промежуточной таблицей и имеет два внешних ключа: один — для таблицы Customer, чтобы представить отношение «многие к одному», и второй — для таблицы Order, чтобы опять же представить отношение «многие к одному».

В этой ситуации объектная модель обычно рассматривается как «один ко многим» с точки зрения основного объекта, которым является либо Customer, либо Product. Объектная модель также содержит отношение «многие к одному» между промежуточным объектом (в нашем примере — Order) и другим объектом (здесь Product). Пример отношения «многие ко многим» тоже приведен на рис. 4; оно имеется между объектами Customer и Product, а Order выступает в роли промежуточного объекта.

Как видите, основным объектом в модели является Customer, и приложение получает его из базы данных. Все остальные объекты считаются связанными с основным. Поэтому объект Customer будет содержать набор объектов Order, а каждый Order — ссылку на объект Product. Объект Product, по-видимому, не будет содержать набор всех объектов Order, принадлежащих объекту Product, так как здесь необходимости в этом нет. Если бы основным был объект Product, он содержал бы набор объектов Order, но тогда в объекте Customer не было бы набора объектов Order.

Стратегии кеширования для разных типов отношений

До сих пор мы говорили о том, как извлекать данные из базы данных, преобразовывать их в объектную модель предметной области и сохранять те же отношения в этой модели, что и в базе данных, — пусть и с точки зрения основного объекта. Но если в вашем приложении нужно кешировать данные в распределенном кеше, вы должны понимать, как обрабатывать все эти взаимосвязи в таком кеше. Рассмотрим каждый случай.

Однако во всех случаях вы должны гарантировать, что информация о взаимосвязях не будет утеряна в кеше, — впоследствии это повлияло бы либо на целостность данных, либо на возможность извлечения связанных объектов из кеша.

Кеширование отношений «один к одному» и «многие к одному»

В этом случае у вас есть два варианта.

  1. Кеширование связанного объекта вместе с основным Этот вариант предполагает, что связанный объект не будет модифицироваться другим пользователем, пока этот объект находится в кеше, а значит, его можно безопасно кешировать вместе с основным объектом как единый элемент. Если вы не уверены в том, что модификации не будет, не используйте этот вариант.
  2. Кеширование связанного объекта отдельно от основного Здесь предполагается, что связанный объект может быть обновлен другим пользователем, пока этот объект находится в кеше, поэтому лучше всего кешировать основной и связанный объекты как отдельные элементы. Для каждого из этих двух объектов вы должны указывать в кеше уникальный ключ. Кроме того, вы можете использовать поддержку тегов в распределенном кеше, чтобы пометить связанный объект как имеющий отношение к основному объекту. Тогда впоследствии вы смогли бы извлекать его по тегу.

Кеширование отношений «один ко многим»

При отношениях «один ко многим» ваш основной объект всегда находится на «одной стороне» (в нашем примере это объект Customer). Основной объект содержит набор объектов Order. Каждый набор связанных объектов представляет отношение «один ко многим». Здесь у вас есть три варианта.

  1. Кеширование наборов связанных объектов вместе с основным объектом Здесь, конечно, предполагается, что связанные объекты не будут обновляться или независимо извлекаться другим пользователем, пока эти объекты находятся в кеше, поэтому вы можете безопасно кешировать их как часть основного объекта. Такой вариант повышает производительность, так как вы можете получить все объекты одним вызовом кеша. Однако, если набор велик (подразумевается, что он состоит из десятков тысяч объектов и содержит мегабайты данных), вы не добьетесь выигрыша в производительности.
  2. Раздельное кеширование наборов связанных объектов В этой ситуации вы считаете, что другие пользователи могут извлекать из кеша те же наборы; следовательно, имеет смысл кешировать наборы связанных объектов отдельно. Вы должны структурировать свой ключ кеша так, чтобы у вас была возможность поиска этого набора на основе какой-либо информации о вашем основном объекте. Вопрос кеширования наборов мы подробнее обсудим чуть позже.
  3. Раздельное кеширование всех индивидуальных связанных объектов из наборов В этом случае вы считаете, что каждый индивидуальный объект в связанном наборе может обновляться другими пользователями; значит, хранить объект в наборе нельзя и его нужно кешировать отдельно. Вы можете использовать поддержку тегов в распределенном кеше, чтобы идентифицировать все объекты как связанные с вашим основным объектом; это позволит впоследствии быстро извлекать их.

Кеширование отношений «многие ко многим»

Отношения «многие ко многим» в объектной модели предметной области на самом деле не существуют. Вместо этого они представляются отношениями «один ко многим» с тем исключением, что промежуточный объект (Order — в нашем случае) содержит ссылку на объект другой стороны (здесь Product). В чистом варианте «один ко многим» этой ссылки не было бы.

Обработка наборов

Обработка наборов — тема интересная, так как наборы объектов часто извлекают из базы данных и хотят эффективно кешировать их.

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

Для кеширования наборов применяются разные стратегии кеширования, которые я и поясню в следующих разделах.

Кеширование всего набора как одного элемента

В этом случае вы знаете, что не будете добавлять новых клиентов из Нью-Йорка и что другие пользователи не будут обращаться и модифицировать любые данные по клиентам из Нью-Йорка в течение периода кеширования этих данных. Следовательно, весь набор клиентов из Нью-Йорка можно кешировать как один элемент. Здесь вы можете сделать критерии поиска или SQL-запрос частью ключа кеша. Всякий раз, когда вам нужно извлечь информацию о клиентах из Нью-Йорка, вы просто сообщаете кешу: «дай мне набор, содержащий клиентов из Нью-Йорка».

На рис. 5 показано, как кешируется весь набор связанных объектов Order.

Рис. 5. Раздельное кеширование связанного набора

public void CacheCustomer(Customer cust)
{
  Cache cache = HttpRuntime.Cache;
  DateTime absolutionExpiration = Cache.NoAbsoluteExpiration;
  TimeSpan slidingExpiration = Cache.NoSlidingExpiration;
  CacheItemPriority priority = CacheItemPriority.Default;
  if (cust != null)
  {
    string key = "Customer:CustomerId:" + cust.CustomerId;
    // Указываем раздельное кеширование...
    IList<Order> orderList = cust.Orders;
    // ...чтобы этот набор не кешировался как часть Customer
    cust.Orders = null;
    // Это приведет к кешированию только объекта Customer
    cache.Add(key, cust, null,
              absolutionExpiration,
              slidingExpiration,
              priority, null);
    // Видим, что этот ключ также основан на Customer
    key = "Customer:CustomerId:" + cust.CustomerId + ":Orders";
    cache.Add(key, orderList, null,
              absolutionExpiration,
              slidingExpiration,
              priority, null);
  }
}

Раздельное кеширование каждого элемента набора

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

Поэтому в нашем примере вам не следует хранить набор всех клиентов из Нью-Йорка как один кешированный элемент. Вы разбиваете набор и сохраняете каждый объект Customer в кеше раздельно. Вам нужно группировать все эти объекты Customer так, чтобы позднее вы могли просто извлечь их обратно либо как набор, либо как IDictionary. Преимущество этого подхода — возможность получать и модифицировать индивидуальные объекты Customer. На рис. 6 показан пример того, как раздельно кешировать каждый из связанных объектов в наборе.

Рис. 6. Раздельное кеширование каждого элемента набора

public void CacheOrdersListItems(IList<Order> ordersList)
{
  Cache cache = HttpRuntime.Cache;
  DateTime absolutionExpiration = Cache.NoAbsoluteExpiration;
  TimeSpan slidingExpiration = Cache.NoSlidingExpiration;
  CacheItemPriority priority = CacheItemPriority.Default;
  foreach (Order order in ordersList)
  {
    string key = "Order:CustomerId:" + order.CustomerId
      + ":ProductId" + order.ProductId;
    string[] keys = new string[1];
    keys[0] = key;
    // Зависимость гарантирует удаление Order, если обновляется или удаляется Cust
    CacheDependency dep = new CacheDependency(null, keys);
    Tag [] tagList = new Tag [1];
    tagList[0] = new Tag ("customerId" + order.CustomerId);
    // Тег позволяет находить order по одному customerId
    cache.Add(key, order, dep,
              absolutionExpiration,
              slidingExpiration,
              priority, null, tagList);
  }
}

Однако учтите, что эта стратегия предполагает, что в период кеширования в базу данных не добавляются новые клиенты из Нью-Йорка. Иначе, когда вы извлечете из кеша все объекты клиентов, проживающих в Нью-Йорке, вы получите лишь частичный список. Аналогично, если какой-то клиент удаляется из базы данных, но не из кеша, вы получите устаревший список клиентов.

Обработка наборов, в которых добавляются или удаляются объекты

Третий вариант обработки наборов — случай, где могут быть добавлены новые клиенты из Нью-Йорка или где могут быть удалены какие-то существующие клиенты. Тогда, что бы вы ни кешировали, это только частичные или устаревшие данные. Возможно, в наборе было всего 100 клиентов, а сегодня вы добавили еще двух. Эти двое не будут частью кеша. Однако, когда вы извлекаете всех нью-йоркских клиентов, вам нужны корректные данные: 102 результата, а не 100.

Чтобы этого не случилось, вызывайте базу данных. Получите все идентификаторы для клиентов и сравнивайте их, чтобы увидеть, какие из клиентов в кеше, а какие — нет. Индивидуально извлекайте из базы данных тех клиентов, которые отсутствуют в кеше, и добавляйте их в кеш. Этот процесс, очевидно, не быстрый; вы вынуждены многократно вызывать и базу данных, и кеш. В случае распределенного кеша с базовой функциональностью, если у вас имеется набор из 1000 клиентов и добавлено 50 новых, вам придется 51 раз вызывать базу данных и 101 раз распределенный кеш. Тогда будет быстрее извлечь весь набор из базы данных одним вызовом.

Но если распределенный кеш поддерживает пакетные операции, вам будет достаточно сделать один вызов базы данных для получения идентификаторов, один вызов распределенного кеша, чтобы узнать, какие идентификаторы существуют в нем, один вызов для добавления всех новых клиентов в кеш и один вызов для получения всего набора клиентов из кеша. Итого: один вызов базы данных и три вызова распределенного кеша — это совсем неплохо. А если новые клиенты не добавлялись (что будет наблюдаться в 90% случаев), один вызов распределенного кеша будет исключен.

Производительность и масштабируемость

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

Распределенное кеширование — отличный способ повышения производительности и масштабируемости приложения. А поскольку приложения в основном имеют дело с реляционными данными, я надеюсь, что эта статья даст вам хорошее представление о том, как обрабатывать реляционные данные в распределенном кеше.

Автор: Икбал Хан  •  Иcточник: MSDN Magazine  •  Опубликована: 16.11.2012
Нашли ошибку в тексте? Сообщите о ней автору: выделите мышкой и нажмите CTRL + ENTER
Теги:  


Оценить статью:
Вверх
Комментарии посетителей
Комментарии отключены. С вопросами по статьям обращайтесь в форум.