В микросервисной архитектуре оформление заказа затрагивает сервисы заказов, склада и платежей. Как обеспечить согласованность без распределённых транзакций?
Короткий ответ
- 2PC в микросервисах не используют: блокировки и хрупкость
- Сага: цепочка локальных транзакций с компенсациями при сбое
- Оркестрация — явный координатор; хореография — реакция на события
- Outbox гарантирует согласованную публикацию событий с записью в БД
- Резервирование товара вместо списания — компенсируемое действие
- Итог — согласованность в конечном счёте и идемпотентность шагов
Вместо распределённой транзакции — сага из локальных шагов с компенсациями, outbox для событий и идемпотентность везде.
Как сказать вслух
пример ответаКлассическую распределённую транзакцию с двухфазным коммитом в микросервисах не применяют — она блокирует ресурсы и хрупка к сбоям координатора. Вместо неё сага: последовательность локальных транзакций — создать заказ, зарезервировать товар, списать оплату — где у каждого шага есть компенсация. Если оплата не прошла, запускаем компенсации: снимаем резерв и отменяем заказ. События между сервисами публикую через outbox-паттерн, чтобы запись в базу и событие не разъехались. И все обработчики делаю идемпотентными, потому что доставка at-least-once.
Подробный ответ
Основной ответ
Проблема: инвариант «заказ оплачен и товар зарезервирован» размазан по трём сервисам с собственными базами. Сага разбивает процесс на локальные транзакции с компенсирующими действиями (отмена резерва, возврат платежа, отмена заказа). Два стиля: хореография — сервисы подписаны на события друг друга (OrderCreated → StockReserved → PaymentCompleted), просто для коротких цепочек, но поток логики нигде не виден целиком; оркестрация — отдельный координатор (state machine, например Temporal или самописный) ведёт шаги и компенсации явно — предпочтительна для сложных процессов, упрощает понимание и обработку таймаутов. Техническая база: transactional outbox в каждом сервисе (событие пишется в одной транзакции с данными, публикатор/CDC доставляет в брокер), идемпотентные обработчики (дедуп по event_id), таймауты шагов с переводом в компенсацию, статусная модель заказа видимая пользователю (pending → confirmed/failed). Важно проектировать компенсируемость: резервирование вместо немедленного списания склада, авторизация платежа с последующим capture.
Ключевые моменты
- Почему не 2PC. Двухфазный коммит держит блокировки на время координации и останавливается при падении координатора — в сервисной среде это неприемлемо.
- Компенсация — не откат. Это новое бизнес-действие (возврат денег), а не rollback: его видно в истории, оно само может сбоить и требует ретраев.
- Оркестрация vs хореография. Хореография распределяет знание о процессе по подпискам; оркестратор держит его в одном месте — для заказа с 4+ шагами обычно выбирают его.
- Eventual consistency наружу. Пользователь видит промежуточный статус «обрабатывается» — UX и статусная модель являются частью дизайна, а не деталью.
Практический контекст
Это центральный сеньорский вопрос про микросервисы: он связывает архитектуру, очереди и транзакционность в один сценарий. Интервьюер гоняет по сбоям: «платёжка ответила таймаутом — списали или нет?» (ответ: идемпотентный ключ платежа и запрос статуса), «компенсация упала — что дальше?» (ретраи, DLQ, ручной разбор). Сильный кандидат сам вводит outbox, идемпотентность и говорит о наблюдаемости процесса — трейсинг саги по correlation_id.
Частые ошибки
- Предлагают двухфазный коммит или «общую транзакцию», не зная её цены в распределённой среде
- Забывают про сбои самих компенсаций и таймауты шагов саги
- Публикуют события после коммита без outbox — событие теряется при падении сервиса