Протокол 2PC

Представь, что у тебя микросервисная архитектура с независимыми базами данных. Тебе нужно перевести 100$ от Пользователя А (в User DB) на счет Магазина Б (в Payment DB). Если транзакция пройдет только в одной базе, деньги исчезнут в никуда. Нам нужно гарантировать атомарность: либо изменения применяются везде, либо нигде.

Здесь на помощь приходит Two-Phase Commit (Двухфазный коммит). В нём всегда участвуют две роли:

  1. Координатор (Coordinator Node): Узел, который дирижирует процессом.
  2. Участники (Participant Nodes): Базы данных, в которых непосредственно меняются данные.
Как это работает: Две фазы
  1. Фаза 1: Prepare (Голосование)
    1. Координатор рассылает всем узлам запрос: «Готовы ли вы провести транзакцию?».
    2. Каждый участник проверяет свои ресурсы, блокирует нужные строки в БД и отвечает: «Да (Ready)» или «Нет (Abort)».
  2. Фаза 2: Commit (Фиксация)
    1. Если ВСЕ участники ответили «Да», координатор шлет команду Commit. Участники применяют изменения и снимают блокировки.
    2. Если хотя бы один ответил «Нет» или отвалился по таймауту, координатор шлет команду Rollback (Откат) всем участникам.
Главный недостаток 2PC блокировка

Протокол 2PC имеет уязвимость. Представь ситуацию: Участник проголосовал «Да» в первой фазе и ждет команду Commit. Но в этот момент Координатор «умирает» (сгорает сервер).

Что делать Участнику? Он не может откатить транзакцию (ведь координатор мог послать Commit другим узлам перед смертью). Он не может закоммитить (вдруг кто-то другой проголосовал «Нет»). Участник обязан бесконечно висеть и держать заблокированными строки в базе данных, пока Координатор не воскреснет. Из-за этого вся система может встать намертво.

Забегая вперед: Из-за риска таких фатальных блокировок (особенно если баз данных много), в современном мире разработки стараются избегать классического 2PC. Вместо этого используют асинхронные паттерны вроде SAGA. Мы подробно разберем его при изучении событийной архитектуры, а пока посмотрим, как можно починить 2PC.