Ответ
В микросервисной архитектуре классические ACID-транзакции, охватывающие несколько сервисов, не применяются из-за сильной связанности и проблем с производительностью. Вместо этого достигается конечная согласованность (eventual consistency) с помощью паттернов распределенных транзакций.
Основной паттерн — Saga.
Saga — это последовательность локальных транзакций. Каждый шаг саги выполняет транзакцию в одном сервисе и публикует событие, которое запускает следующий шаг в другом сервисе. Если шаг завершается неудачно, сага выполняет компенсирующие транзакции для отмены предыдущих шагов.
Существует два способа координации саги:
-
Хореография (Choreography): сервисы общаются напрямую через события, без центрального координатора. Это обеспечивает слабую связанность.
Пример создания заказа:
// OrderService func (s *Service) CreateOrder(orderData) error { // 1. Локальная транзакция: сохранить заказ в статусе "pending" if err := s.db.SaveOrderAsPending(orderData); err != nil { return err } // 2. Публикация события для следующего шага return s.eventBus.Publish("OrderCreated", orderData) } // PaymentService (подписчик на "OrderCreated") func (s *Service) OnOrderCreated(event) error { if err := s.db.ChargePayment(); err != nil { // Публикуем компенсирующее событие s.eventBus.Publish("PaymentFailed", event.OrderID) return err } // Публикуем событие об успешной оплате s.eventBus.Publish("PaymentSucceeded", event.OrderID) } -
Оркестрация (Orchestration): центральный сервис (оркестратор) управляет всем процессом, отправляя команды каждому участнику и ожидая ответа.
Связанные паттерны:
- Outbox Pattern: Гарантирует, что событие будет отправлено тогда и только тогда, когда локальная транзакция успешно завершена. Изменения и события записываются в одну транзакционную таблицу "outbox", а отдельный процесс считывает ее и надежно доставляет сообщения.
- CQRS (Command Query Responsibility Segregation): Часто используется вместе с сагами. Разделение моделей на запись (Commands) и чтение (Queries) упрощает обработку событий и построение согласованных представлений данных.
Ответ 18+ 🔞
А, микросервисы, блядь! Ну, классические-то ACID-транзакции, которые на несколько сервисов сразу — это, сука, как пытаться заставить пьяную орду мартышлюшек синхронно станцевать лезгинку. Не, ну можно, конечно, но потом все сервисы так связаны будут, что чихнёшь в одном — в другом сопли по колено, да и производительность накроется медным тазом, ёпта.
Поэтому тут главный принцип — конечная согласованность, блядь. То есть, в итоге-то всё сойдётся, но не сразу, а когда-нибудь потом, как зарплата у госслужащего. А основной инструмент для этого — Saga, сука.
Представь себе сагу — это как длинная, ебать, история, где каждый сервис делает своё маленькое дело (локальную транзакцию), а потом кричит в рупор (публикует событие): «Эй, следующий, твой выход!». Если следующий чувак споткнётся и упадёт мордой в асфальт, то придётся бегать назад и откатывать всё, что натворили предыдущие, с помощью компенсирующих транзакций. Весело, да?
И есть два способа эту кашу заварить:
-
Хореография (Choreography). Это когда все сервисы, как пьяные гости на свадьбе, сами между собой договариваются, кто за кем идёт. Центрального заводилы нет, слабая связанность, красота. Один сделал дело — крикнул, другой услышал — сделал своё.
Вот смотри, как заказ создаётся, на примере кода (его не трогаем, он святой):
// OrderService func (s *Service) CreateOrder(orderData) error { // 1. Локально в своей базе заказ сохранил, статус "pending" if err := s.db.SaveOrderAsPending(orderData); err != nil { return err } // 2. Орёшь на всю площадку: "Заказ создан, ёбта!" return s.eventBus.Publish("OrderCreated", orderData) } // PaymentService (сидит, ушами шевелит, слушает) func (s *Service) OnOrderCreated(event) error { // Пытается деньги списать if err := s.db.ChargePayment(); err != nil { // Не получилось! Орёт: "Ой, всё, платеж не прошёл, отменяйте!" s.eventBus.Publish("PaymentFailed", event.OrderID) return err } // Получилось! Орёт: "Деньги есть, работаем дальше!" s.eventBus.Publish("PaymentSucceeded", event.OrderID) } -
Оркестрация (Orchestration). А это когда есть главный по тарелочкам — оркестратор. Он, как злой режиссёр, тыкает пальцем в каждого сервиса: «Ты — делай! Ты — теперь ты! А ты — откатывай, потому что этот мудак всё просрал!». Все команды идут через него одного.
Паттерны-помощники, без которых нихуя не работает:
- Outbox Pattern. Это чтобы событие улетело в мир ровно тогда, когда локальная транзакция завершилась, а не «ой, я записал, но сообщение потерял, извините». Записываем и данные, и событие в одну транзакцию в специальную табличку-исходящую (outbox). Потом отдельный почтальон-процесс эту табличку читает и гарантированно расталкивает сообщения всем подписчикам. Без этого — доверия ебать ноль, всё рассыпется.
- CQRS (Command Query Responsibility Segregation). Часто с сагами в одной упряжке идёт. Смысл в том, чтобы разделить, сука, запись и чтение. Для команд (запись) — одна модель, своя база. Для запросов (чтение) — вообще другая, которая может обновляться асинхронно из событий, которые саги порождают. Так проще строить эти ваши «согласованные представления данных», которые в конечном счёте (eventual!) становятся верными. Хитрая жопа, но работает.
Вот так, блядь, и живём. Не как в старые добрые времена с одной жирной транзакцией на весь мир, а как партизаны в лесу — каждый своё, но в итоге мост всем миром взрываем.