Ответ
Распределенная транзакция — это операция, которая охватывает несколько независимых ресурсов (разные базы данных, очереди сообщений, микросервисы) и должна быть выполнена как единое атомарное действие (ACID на уровне распределенной системы). Это означает «все или ничего»: либо все участники фиксируют изменения, либо все откатываются.
Классический подход: Двухфазный коммит (2PC)
Протокол, где координатор управляет процессом.
Фаза 1: Подготовка (Prepare)
- Координатор отправляет всем участникам команду
prepare. - Каждый участник выполняет локальные операции (включая запись в лог), но не фиксирует их, блокируя ресурсы.
- Участник отвечает
YES(готов к фиксации) илиNO(не может выполнить).
Фаза 2: Фиксация или Откат (Commit/Rollback)
- Если все ответили
YES, координатор рассылает командуcommit. Участники фиксируют изменения. - Если хотя бы один ответил
NO, координатор рассылает командуrollback. Все участники откатывают изменения.
Пример на Java с JTA (Java Transaction API):
import javax.transaction.UserTransaction;
import javax.annotation.Resource;
import javax.persistence.*;
@Stateless
public class DistributedTransferService {
@Resource
private UserTransaction userTransaction; // Координатор транзакций
@PersistenceContext(unitName = "AccountDB")
private EntityManager emAccount; // Ресурс 1: База счетов
@PersistenceContext(unitName = "AuditDB")
private EntityManager emAudit; // Ресурс 2: База аудита
public void transferMoney(Long fromId, Long toId, BigDecimal amount) throws Exception {
userTransaction.begin(); // Начало распределенной транзакции
try {
// 1. Списание со счета отправителя (Resource 1)
Account from = emAccount.find(Account.class, fromId);
from.withdraw(amount);
// 2. Запись в лог аудита (Resource 2)
AuditLog logEntry = new AuditLog("Transfer", fromId, toId, amount);
emAudit.persist(logEntry);
// 3. Зачисление на счет получателя (Resource 1)
Account to = emAccount.find(Account.class, toId);
to.deposit(amount);
userTransaction.commit(); // Фаза 2: Commit, если все успешно
} catch (Exception e) {
userTransaction.rollback(); // Фаза 2: Rollback при любой ошибке
throw e;
}
}
}
Проблемы 2PC и современные альтернативы
- Блокировки ресурсов: Участники блокируют данные на время всей транзакции (проблема производительности).
- Координатор — единая точка отказа.
- Сложность восстановления после сбоя на фазе подготовки.
Современные паттерны для микросервисов:
- Saga: Длинная транзакция, состоящая из цепочки локальных транзакций. Каждая следующая транзакция запускается после успеха предыдущей. В случае ошибки выполняются компенсирующие транзакции (Compensating Transactions) для отката уже выполненных шагов.
- Eventual Consistency (Конечная согласованность): Отказ от немедленной атомарности. Система допускает временную несогласованность, но гарантирует, что в конечном счете все данные будут согласованы (через обмен событиями).
Когда использовать? 2PC подходит для монолитных приложений с несколькими БД. Для микросервисной архитектуры предпочтительны Saga или Eventual Consistency.
Ответ 18+ 🔞
А, распределённые транзакции! Ну это же классика, блядь, вечная головная боль. Представь, тебе надо денег перевести, но так, чтобы и со счёта списалось, и в лог записалось, и получателю зачислилось, и всё это в разных, ёпта, местах — одна база тут, другая там, а третья вообще на другом континенте лежит. И надо, чтобы либо всё разом сработало, либо, если где-то пиздец случился, чтобы всё как было осталось. Вот это и есть «всё или ничего», атомарность, только уже на уровне всей системы, а не одной базульки.
Старый добрый способ: Двухфазный коммит (2PC)
Это как свадьба, блядь, с координатором-тамадой. Протокол прямолинейный, как лом.
Фаза 1: Подготовка (Prepare)
- Координатор (этот самый тамада) орет всем участникам: «Мужики, готовьтесь! Запись в лог, всё посчитали, но хуй вам, не фиксируйте пока!».
- Участники делают свою работу локально, но держат всё на замке, ресурсы блокируют. Потом кричат в ответ: «ДА, готов!» или «НЕТ, нихуя не выйдет».
- Если хоть один гаркнул «НЕТ» — всё, пиздец, отмена.
Фаза 2: Фиксация или Откат (Commit/Rollback)
- Если все ответили «ДА», координатор командует: «Ну всё, женитесь!». Все фиксируют изменения.
- Если хотя бы один — «НЕТ», то кричит: «Расходимся, блядь!». Все откатывают свою работу.
Пример на Java с JTA (Java Transaction API):
Смотри, как это выглядит в коде. Всё через UserTransaction, который и есть наш главный по таблеткам.
import javax.transaction.UserTransaction;
import javax.annotation.Resource;
import javax.persistence.*;
@Stateless
public class DistributedTransferService {
@Resource
private UserTransaction userTransaction; // Вот он, наш координатор-тамада
@PersistenceContext(unitName = "AccountDB")
private EntityManager emAccount; // Ресурс 1: База со счетами
@PersistenceContext(unitName = "AuditDB")
private EntityManager emAudit; // Ресурс 2: База для аудита
public void transferMoney(Long fromId, Long toId, BigDecimal amount) throws Exception {
userTransaction.begin(); // Начали! Все на старт!
try {
// 1. Списание (Resource 1)
Account from = emAccount.find(Account.class, fromId);
from.withdraw(amount);
// 2. Логирование (Resource 2)
AuditLog logEntry = new AuditLog("Transfer", fromId, toId, amount);
emAudit.persist(logEntry);
// 3. Зачисление (Resource 1)
Account to = emAccount.find(Account.class, toId);
to.deposit(amount);
userTransaction.commit(); // Фаза 2: ВСЁ ОТЛИЧНО, КОММИТИМ!
} catch (Exception e) {
userTransaction.rollback(); // Фаза 2: ЧТО-ТО ПОШЛО НЕ ТАК, ОТКАТ ВСЕГО, БЛЯДЬ!
throw e;
}
}
}
Да какие же тут проблемы, спросишь ты? А проблемы — овердохуища!
- Блокировки: Пока все готовятся и ждут команды, ресурсы висят на замке. Это как очередь в один сортир на десять человек — производительность летит в пизду.
- Координатор — единая точка отказа. Упал тамада — вся свадьба в ахуе. Кто коммитить-то будет?
- Восстановление после сбоя — это отдельный пиздец. Если координатор сдох на фазе подготовки, когда все уже сказали «ДА», но команды «жениться» ещё не было, то участники так и будут висеть в подвешенном состоянии, пока кто-то не разберётся, что делать.
Поэтому сейчас, особенно в этих ваших микросервисах, 2PC часто посылают нахуй. Вместо него:
- Saga (Сага): Длинная-предлинная транзакция из кусочков. Сделал один шаг — запустил следующий. Если на каком-то шаге обосрался — не беда, запускаешь компенсирующую транзакцию (обратный шаг) для каждого уже выполненного куска. Не атомарно сразу, но в итоге система к согласованности приходит.
- Eventual Consistency (Конечная согласованность): Вообще забиваем хуй на немедленную атомарность. Допускаем, что какое-то время данные в разных местах будут немного ебанатами. Но через обмен событиями гарантируем, что в конце концов всё устаканится. Как в жизни, блядь.
Короче, резюме: 2PC — это для монолитов, где несколько баз, но всё ещё более-менее под контролем. Для микросервисной вакханалии, где каждый творит что хочет, лучше смотреть в сторону Saga или смириться с конечной согласованностью. А то так и до инфаркта недалеко.