Двухфазная фиксация в распределенных транзакциях
При разработке бэкенда высоконагруженных сервисов часто применяют распределенные транзакции. О проблемах, с которыми можно столкнуться при совершении данных транзакций в микросервисах, мы уже говорили. Пришло время поговорить о способах их решения. Один из способов -- двухфазная фиксация.
Двухфазный метод транзакций предполагает наличие 2-х этапов: фазы подготовки и фазы фиксации. Немаловажную роль играет координатор транзакций, который, по сути, организует жизненный цикл транзакции. Рассмотрим, как это работает на практике.
Уже на подготовительном этапе микросервисы, которые участвуют в работе, подготавливаются к фиксации, а также уведомляют координатора о готовности завершить транзакцию. На следующем этапе фиксация либо происходит, либо координатор транзакции выдает команду микросервисам выполнить откат.
В качестве примера можно привести работу интернет-магазина. Ниже -- как раз таки пример успешной 2-фазной фиксации в микросервисной системе:
Мы видим, что когда пользователь направляет запрос на заказ, TransactionCoordinator, обладающий полной информацией о контексте, сначала начинает глобальную транзакцию. Он отправляет команду подготовиться микросервису OrderMicroservice, что необходимо для создания заказа. Потом он отправляет команду подготовиться к InventoryMicroservice, что необходимо для резервирования товаров. Когда оба эти сервиса готовы внести изменения, происходит блокировка объектов от последующих изменений, о чем уведомляется TransactionCoordinator. И в тот момент, когда TransactionCoordinator подтвердит, что микросервисы готовы применить изменения, поступит команда микросервисам эти изменения сохранить путем запроса фиксации транзакции. В тот самый момент все объекты будут разблокированы.
Для полноты картины можно рассмотреть и пример неудавшейся 2-фазной фиксации:
В сценарии отказа, который показан на рисунке выше, все работает следующим образом: если в любую секунду отдельно взятый микросервис приготовиться не успеет, то TransactionCoordinator отменит транзакцию, запустив процесс отката. К примеру, на нашей схеме OrderMicroservice по каким-то причинам заказ создать не смог, однако InventoryMicroservice при этом откликнулся, что создать заказ он готов. В результате координатор TransactionCoordinator запросит отмену на InventoryMicroservice, ну а далее сервис откатит все выполненные изменения и разблокирует объекты БД.
Плюсы и минусы
Подход имеет как преимущества, так и недостатки.
Плюсы:
- гарантируется атомарность транзакции. Транзакция завершится либо при успешной сработке обоих микросервисов, либо в том случае, если микросервисы не внесут вообще никаких изменений;
- изолируется чтение от записи. Изменения не видны в объектах до тех пор, пока координатор транзакций эти изменения не зафиксирует;
- подход является синхронным вызовом, при котором клиент уведомляется или об успехе, или о неудаче.
Минусы:
- 2-фазные фиксации протекают относительно медленно, если сравнивать их с операциями над одним микросервисом. Также они существенно зависят от координатора транзакций, что иногда замедляет работу системы, особенно в период повышенной загруженности;
- блокируются строки базы данных. Такая блокировка может становиться узким местом, где затрудняется производительность, мало того, не исключена взаимная блокировка, когда 2 транзакции намертво застопорят друг друга.
По материалам "Handling Distributed Transactions in the Microservice world".