понедельник, 24 августа 2009 г.

Транзакции и обеспечение правильного порядка асинхронного взаимодействия


Пару слов об истории проблемы. Я - разработчик бизнес-процессов в компании Naumen. Как я уже писал, основная активность бизнес-процесса (BPEL-процесса) - вызов неких сервисов (чаще всего - веб-сервисов). Фактически задача BPEL-процесса сводится к тому, чтобы обеспечить необходимый порядок вызова необходимых сервисов. Впрочем, BPEL взят лишь для примера, мысли, изложенные далее, характерны для взаимодействия любых систем. Так вот, при взаимодействии приложения и бизнес-процесса, а так же бизнес-процесса и приложения, иногда возникают интересные коллизии, вызванные неправильной организацией взаимодействия. Именно об этом я и хочу сегодня поговорить.


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



При асинхронном взаимодействии картина сложнее. Мы не знаем через какие временные интервалы наши сервисы будут возвращать результат, мы не знаем когда мы будем вызывать сервисы. Главное на чем строится взаимодействие - механизм подтверждений. Т.е. мы посылаем запрос сервису и ждем, когда он нам ответит. Мы создаем задачи и подсчитываем подтверждения о завершении каждой. И т.д. С асинхронным взаимодействием мы сталкиваемся всегда, когда не знаем (часто и не можем знать) временных параметров компонентов системы.



Теперь вопрос, а причем здесь транзакции в БД? Как известо - транзакции спасают нас от грязного чтения - т.е. если в транзакции меняются данные, извне они не доступны, пока транзакция не будет завершена. А теперь рассмотрим такой момент: мы из процесса вызываем некий сервис, который пишет данные в БД (в транзакции А) и посылает уведомление процессу (реализуется асинхронное взаимодействие). Процесс, получив уведомление, вызывает другой метод этого же сервиса, который пытается прочитать недавно записаные данные из БД. И вот здесь возможны два варианта:

1. Транзакция А успела завершиться до вызова второго метода - будут прочитаны корректные данные



2. Транзакция А не успела завершиться до вызова второго метода - будут прочитаны некорректные данные, т.к. транзакция защищает нас от грязного чтения.



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

Сигнал уведомления должен отправляться BPEL-процессу после завершения транзакции. Казалось бы - очевидно, однако не совсем. Дело в том, что при разработке на Java во многих фреймворках программист не может сам вносить изменение в механизм транзакций. Например, транзакции реализуют с помощью Servlet-фильтров, т.е. для обработки HTTP-запроса создается одна транзакция и внутри нее мы уже вольны работать с БД. Другим примером может являться декларативное управление транзакциями с помощью SpringAOP, когда границы транзакции совпадают с границами метода (а границы метода - с границами вызываемого сервиса).



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

Что может здесь помочь? В частности - тот же SpringAOP, если вы разрабатываете приложение на Spring. С помощью AOP можно добиться вызова некоторого кода непосредственно после завершения нужных методов, а значит и транзакций.

В Naumen Kernel используется механизм событий. При завершении исполнения бизнес-действия (что совпадает с завершением транзакции) - генерируется соответствующее событие. Существует механизм обработки таких событий. Все действия по уведомлению внешних систем выносится в эти обработчики.

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

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

Вопросы, возражения, пожелания?

Понравилось сообщение - подпишитесь на блог или читайте меня в twitter

6 комментариев:

Никита Кокшаров комментирует...

Помним-помним мы этот Naumen Kernel... :)

Jungehexe комментирует...

ага, спасибо за практическую схему реализации.

Unknown комментирует...

Очень недавно наткнулся на блог, оч. интересные статьи.

Как мне кажется, данный пример не совсем верен. Если система, где вертится процесс, а также транспорт, поддерживают distributed transactions, то транзакция A должна распространяться на процесс при возврате результата (это по сути синхронный callback-вызов с remote transaction). Это гарантирует, что пока сервис не сделает полный commit транзакции A, процесс дальше не сдвинется, пусть даже он и на другой системе.

Unknown комментирует...

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

Unknown комментирует...

Собственно, вся статья - попытка объяснить такой use case в случае, если система не поддерживает распределенных транзакций.

Unknown комментирует...

Спасибо за такой подробный и развернутый комментарий.

Мое описание грязного чтение естественно и не претендовало на полноту, но для целей поста его было действительно достаточно. Потому что при настройках СУБД по умолчанию (по крайней мере в PostgreSQL) чтение именно атомарно.

Про механизм тикетов не знал. Действительно интересно, надо будет разобраться.

Отправить комментарий

Любой Ваш комментарий важен для меня, однако, помните, что действует предмодерация. Давайте уважать друг друга!