Учебная лаборатория НГУ-Parallels - Написать нам Печать
LogoГлавнаяЛетняя практикаПроектыСеминарыСпецкурсыПубликацииДокументация
W Publikacii / 2010 / Object Systems / Chilim
ОРГАНИЗАЦИЯ РЕПЛИКАЦИИ ДАННЫХ ПРИЛОЖЕНИЯ С ORM-СЛОЕМ НА ОБЪЕКТНОМ УРОВНЕ
В. Д. Ипполитов, О.А. Кременная, Н.А. Тищенко, Н.А. Баженов, В.Н. Вельдяксов

Задача репликации данных состоит в поддержании нескольких хранилищ данных в согласованном состоянии. Репликация может применяться для обеспечения отказоустойчивости, повышения производительности и локализации трафика [1].

Рассмотрим бизнес-приложение, использующее слой объектно-реляционного отображения (ORM) [2,3] для хранения своих данных. Пусть на нескольких компьютерах (узлах) установлены копии этого приложения, каждая из которых работает с собственной базой данных. Если целостность всех данных одной копии приложения сводится к целостности отдельных объектов и прямых связей между ними, то возможна репликация данных между узлами на уровне объектов. Для этого необходимо отслеживать, какие объекты изменяет бизнес-приложение, и при репликации распространять эти изменения на другие узлы. Все изменения в базу данных приложение вносит через ORM-слой, что позволяет отследить их прозрачно для приложения, внедрившись в этот слой.

Целью проекта «Чилим» является разработка механизма репликации данных на объектном уровне и реализация программной системы, основанной на этом механизме. Наиболее важные требования к системе:
1. минимальность необходимых изменений кода бизнес-приложения,
2. устойчивость к разрывам сетевых соединений,
3. экономия сетевого трафика,
4. независимость от СУБД.
На данный момент реализован функциональный прототип системы репликации. В качестве бизнес-приложения выступает система управления контентом XWiki [4] – приложение на языке Java, использующее библиотеку Hibernate [5] для доступа к РСУБД на объектном уровне.

Прототип системы состоит из 4-х основных модулей:
1. модуль логики репликации,
2. протокол сетевого взаимодействия,
3. модуль работы с ORM-слоем,
4. интерфейс администрирования.

Код Hibernate был незначительно изменён, чтобы обеспечить подключение специального обработчика событий к каждой сессии работы приложения с базой данных. Модуль работы с ORM-слоем с помощью этого обработчика отслеживает три вида событий:
1. сохранение нового объекта (persist),
2. обновление содержимого ранее существовавшего объекта (update),
3. удаление объекта из базы данных (delete).

Сведения о модификациях бизнес-объектов, собранные репликатором, сохраняются в отдельной таблице Entities базы данных. Для каждого объекта хранятся:
1. глобальный идентификатор объекта (globalId),
2. первичный ключ объекта в базе данных текущего узла (hibernateKey),
3. дата последней модификации объекта приложением (modDate),
4. дата сохранения объекта в базе данных (saveDate).

Глобальный идентификатор присваивается каждому бизнес-объекту в момент первого сохранения приложением в базу данных и никогда не изменяется. С помощью globalId задаётся отношение эквивалентности на бизнес-объектах, иначе говоря, объекты с одинаковым globalId при репликации считаются версиями одного и того же бизнес-объекта. Глобальный идентификатор состоит из двух частей:
1. имя класса, к которому относится данный объект (entityName),
2. уникальный среди всех объектов этого класса в данной сети идентификатор (uniqueId).

Уникальный идентификатор вычисляется двумя способами, в зависимости от типа первичного ключа сущности [6]. Если первичный ключ присваивается базой данных автоматически (суррогатный ключ), то генерируется случайный GUID [7], который и становится uniqueId. Если же первичный ключ присваивается бизнес-приложением (естественный ключ), то в качестве uniqueId используется результат применения сжимающей взаимнооднозначной функции [8] к первичному ключу. Во втором случае считается, что два бизнес-объекта одного класса, которым на разных узлах приложение присвоило одинаковые ключи, являются по сути одним и тем же объектом, что наилучшим образом отражает идею естественного ключа. Сжатие позволяет уменьшить длину поля uniqueId во втором случае.

Дата сохранения объекта в базе данных (saveDate) позволяет отличать объекты, сохранённые в процессе репликации, от объектов, сохранённых локальной копией бизнес-приложения. В первом случае дата сохранения больше даты модификации, во втором они равны.

Кроме таблицы Entities, каждый узел сети имеет таблицу Nodes, в которой хранятся даты последней репликации с теми узлами, с которыми она когда-либо производилась. Вышеупомянутой информации достаточно, чтобы производить репликацию методом распространения слухов [9].

Прототип системы поддерживает сессии репликации с участием только двух узлов одновременно. Один из узлов является активным (Local), второй – пассивным (Foreign). Узел Local инициирует все действия в текущей сессии, в том числе открывает сетевые соединения. В начале сессии Local строит список идентификаторов объектов, изменённых локально с момента последней репликации с узлом Foreign, сравнивая даты последних изменений объектов своей базы данных с датой последней репликации с Foreign. Назовём этот список LocalChanges. Затем он запрашивает у узла Foreign «симметричный» список ForeignChanges. Списки изменений содержат globalId и modDate. Получив ForeignChanges, узел Local сравнивает списки изменений и составляет три новых списка идентификаторов объектов:
1. ToPull – изменённые на узле Foreign, но не изменённые на Local,
2. ToPush – изменённые на узле Local, но не изменённые на Foreign,
3. Conflicts – изменённые на обоих узлах.

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

После разрешения конфликтов происходит проталкивание объектов из списка ToPush на узел Foreign и вытягивание с него объектов из списка ToPull [10,11]. Передача объектов происходит пачками настраиваемого размера, что позволяет с одной стороны, уменьшить накладные расходы на установление сетевых соединений, а с другой, обеспечить достаточную устойчивость к их неожиданным обрывам. Ошибки при передаче одной пачки не останавливают процесс репликации в целом. На каждую полученную пачку объектов узел отвечает списком идентификаторов успешно сохранённых в своей базе данных объектов из этой пачки. Сохранение объекта может быть неудачным, например, в том случае, когда локальная модификация объекта произошла уже после составления списка изменений. Если объект не был успешно сохранён по той или иной причине, попытка будет повторена в следующем сеансе репликации.

Описанный механизм обеспечивает репликацию объектов, связи между которыми полностью отражены в их отображении на реляционную БД. Однако зачастую приложение построено так, что реализации бизнес-объектов не учитывают некоторые связи и ограничения, существующие в БД. И наоборот, существуют логические зависимости бизнес-объектов, влияющие на корректность работы приложения, но не отражённые в структуре базы данных. Такие ситуации были выявлены при анализе поведения XWiki. Например, два класса отображены на одну таблицу БД, причём у класса A есть обязательный атрибут, которого нет у класса B. Возникает неочевидная зависимость между этими классами: в БД необходимо сначала сохранять объект класса A, а потом B, если у них одинаковые ключи. В противном случае возникнет нарушение ограничения БД, поскольку у B отсутствует обязательный для этой таблицы атрибут.

Одно из решений заключается в исправлении структуры базы данных разработчиком приложения. Если же это невозможно, требуется задействовать дополнительные механизмы на этапе репликации. Разработчик бизнес-приложения должен предоставить файл описания зависимостей между объектами. В этом файле паре «класс объектов – загрузчик» сопоставляется модель отношения, определяющая порядок сохранения зависимого объекта. «Загрузчик» – это Java-класс, отыскивающий для зависимого объекта все объекты в базе данных, от которых он зависит. Загрузчики должен создать разработчик бизнес-приложения, поскольку именно он знает, каким образом объекты зависят друг от друга.

Файл описания зависимостей используется для поиска связей между объектами. В процессе построения списка изменений (*Changes) узлы отыскивают зависимости изменённых объектов друг от друга или от неизменённых объектов. Все найденные зависимости передаются вместе со списком изменений объектов. В процессе обмена изменёнными объектами (ToPull и ToPush) информация о связях используется при сохранении объекта в базу данных. В текущей версии прототипа системы данный механизм реализован лишь частично.

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

Для целей администрирования сети узлов имеется веб-интерфейс. Он позволяет подключиться к указанному узлу, изменить его настройки, изменить расписание сеансов репликации, инициировать немедленную репликацию с другим узлом.

В ходе работы были выявлены требования к бизнес-приложению, при выполнении которых система репликации способна корректно функционировать. Эти требования носят характер промежуточных ограничений. Некоторые из них можно ослабить или убрать, введя новые механизмы в процесс репликации. Основные из них:
1. Все операции обновления базы данных происходят через ORM-слой, причём без использования прямых HQL запросов [12] типа “UPDATE … SET …” и “DELETE FROM …”;
2. Любой бизнес-объект можно корректно загрузить штатными средствами Hibernate [13];
3. На одну таблицу БД отображено не более одного бизнес-класса;
4. Все бизнес-классы допускают сериализацию и десериализацию с помощью какого-либо механизма, например, XStream [14].

Приведение XWiki в соответствие с этими требованиями позволило создать работающий прототип системы репликации.

Литература.
1. Иртегов Д.В. Введение в сетевые технологии. — СПб.: БХВ-Петербург, 2004, стр.468
2. http://www.agiledata.org/essays/mappingObjects.html (Mapping Objects to Relational Databases: O/R Mapping In Detail)
3. http://www.hibernate.org/about/orm.html (What is Object/Relational Mapping?)
4. http://www.xwiki.org/ (XWiki, an open-source wiki and content-oriented application platform)
5. http://www.hibernate.org/ (Hibernate, relational persistence for Java &. NET)
6. http://docs.jboss.org/hibernate/core/3.3/reference/en/html/mapping.html, Chapter 5.1.4.1. Generator (Hibernate documentation)
7. http://java.sun.com/j2se/1.5.0/docs/api/java/util/UUID.html#randomUUID() (Java 2 SE 5.0 documentation)
8. http://java.sun.com/j2se/1.5.0/docs/api/java/util/zip/Deflater.html (Java 2 SE 5.0 documentation)
9. Э. Таненбаум, М. ван Стеен. Распределенные системы. Принципы и парадигмы. — СПб.: Питер, 2003, стр.376
10. http://www.ibm.com/developerworks/lotus/library/ls-Domino_Replication/index.html, PULL-PUSH (Notes from Support: Lotus Notes/Domino Replication)
11. Э. Таненбаум, М. ван Стеен. Распределенные системы. Принципы и парадигмы. — СПб.: Питер, 2003, стр.371
12. http://docs.jboss.org/hibernate/core/3.3/reference/en/html/queryhql.html (HQL: The Hibernate Query Language)
13. http://docs.jboss.org/hibernate/stable/core/api/org/hibernate/Session.html#load(java.lang.String,%20java.io.Serializable) (Hibernate Core 3.5.0 API)
14. http://xstream.codehaus.org/ (XStream, a simple library to serialize objects to XML and back)