Компонентный подход в программировании

       

Транзакции


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

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

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

Транзакции представляют собой группы действий, обладающие следующим набором свойств:

  • Атомарность (atomicity). Для окружения транзакция неделима — она либо выполняется целиком, либо ни одно из ее действий транзакции не выполняется. Другие процессы не имеют доступа к промежуточным результатам транзакции.
  • Непротиворечивость (consistency). Транзакция не нарушает инвариантов и ограничений целостности данных системы.
  • Изолированность (isolation). Одновременно происходящие транзакции не влияют друг на друга. Это означает, что несколько транзакций, выполнявшихся параллельно, производят такой суммарный эффект, как будто они выполнялись в некоторой последовательности. Сама эта последовательность определяется внутренними механизмами реализации транзакций. Это свойство также называют сериализуемостью транзакций, поскольку любой сценарий их выполнения эквивалентен некоторой их последовательности или серии.
  • Долговечность (durability). После завершения транзакции сделанные ею изменения становятся постоянными и доступными для выполняемых в дальнейшем операций. Если транзакция завершилась, никакие сбои не могут отменить результаты ее работы.

По первым буквам английских терминов для этих свойств, их часто называют ACID.

Свойствами ACID во всей полноте обладают так называемые плоские транзакции (flat transactions), самый распространенный вариант транзакций. Иногда требуется гораздо более сложное поведение, в рамках которого нужно уметь выполнять или отменять только часть операций в составе транзакции; бывают случаи, когда процессам, не участвующим в транзакции, нужно уметь получить ее промежуточные результаты. Сокрытие промежуточных результатов часто накладывает слишком сильные ограничения на работу системы, если транзакция продолжается заметное время (а иногда их выполнение требует нескольких месяцев!). Для решения таких задач применяются механизмы, допускающие вложенность транзакций друг в друга, длинные транзакции, позволяющие получать доступ к своим промежуточным результатам, и пр.

Одним из широко распространенных видов программного обеспечения промежуточного уровня являются мониторы транзакций (transaction monitors), обеспечивающие выполнение удаленных вызовов процедур с поддержкой транзакций. Такие транзакции часто называют распределенными, поскольку участвующие в них процессы могут работать на разных машинах.


увеличить изображение
Рис. 12.7.  Схема реализации поддержки распределенных транзакций

Для организации таких транзакций необходим координатор, который получает информацию обо всех участвующих в транзакции действиях и обеспечивает ее атомарность и изолированность от других процессов. Обычно транзакции реализуются при помощи примитивов, позволяющих начать транзакцию, завершить ее успешно (commit), с сохранением всех сделанных изменений, и откатить транзакцию (rollback), отменив все выполненные в ее рамках действия.

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

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

Если вызывается примитив "завершить транзакцию", координатор выполняет некоторый протокол подтверждения, чтобы убедиться, что все участники выполнили свои действия успешно и можно открыть результаты транзакции для внешнего мира. Наиболее широко используется протокол двухфазного подтверждения (Two-phase Commit Protocol, 2PC) [3,4], который состоит в следующем:

  1. Координатор посылает каждому компоненту-участнику транзакции запрос о подтверждении успешности его действий.
  2. Если данный компонент выполнил свою часть операций успешно, он возвращает координатору подтверждение.

    Иначе — он посылает сообщение об ошибке.

  3. Координатор собирает подтверждения всех участников и, если все зарегистрированные участники транзакции присылают подтверждения успешности, рассылает им сообщение о подтверждении транзакции в целом. Если хотя бы один участник прислал сообщение об ошибке или не ответил в рамках заданного времени, координатор рассылает сообщение о необходимости отменить транзакцию.
  4. Каждый участник, получив сообщение о подтверждении транзакции в целом, сохраняет локальные изменения, сделанные в рамках транзакции.

Если же он получить сообщение об отмене транзакции, он отменяет локальные изменения.

Аналог протокола двухфазного подтверждения используется, например, в компонентной модели JavaBeans для уведомления об изменениях свойств компонента, которые некоторые из оповещаемых о них компонентов-подписчиков могут отменить [5,6]. При этом до внесения изменений о них надо оповестить с помощью метода vetoableChange() интерфейса java.beans.VetoableChangeListener. Если хотя бы один из подписчиков требует отменить изменение с помощью создания исключения типа java.beans.PropertyVetoException, его надо откатить, сообщив об этом остальным подписчикам. Если же все согласны, то после внесения изменений о них, как об уже сделанных, оповещают с помощью метода propertyChange() интерфейса java.beans.PropertyChangeListener.


Содержание раздела