Ответ
Outbox Pattern — это архитектурный шаблон, используемый в распределенных системах для надежной публикации событий или сообщений, гарантируя атомарность между изменением бизнес-данных и отправкой соответствующего сообщения.
Проблема, которую решает Outbox:
В распределенных системах часто возникает необходимость изменить данные в базе данных и одновременно отправить сообщение (например, в брокер сообщений, такой как Kafka или RabbitMQ). Если эти две операции выполняются отдельно, существует риск несогласованности: либо данные изменятся, но сообщение не отправится (например, из-за сбоя сети или брокера), либо сообщение отправится, но транзакция с данными откатится.
Суть паттерна:
- Атомарная запись: Вместо прямой отправки сообщения, оно записывается в специальную таблицу
outbox
в той же транзакции базы данных, что и изменение бизнес-данных. Это гарантирует, что либо оба действия (изменение данных и запись вoutbox
) успешно завершатся, либо ни одно из них. - Фоновая отправка: Отдельный фоновый процесс (например, поллер или механизм Change Data Capture - CDC) периодически считывает сообщения из таблицы
outbox
, отправляет их в брокер сообщений и помечает как отправленные (или удаляет).
Пример на Go (псевдокод):
func CreateOrder(order Order) error {
tx := db.Begin() // Начинаем транзакцию
if tx.Error != nil {
return tx.Error
}
// 1. Сохраняем бизнес-данные
err := tx.Create(&order).Error
if err != nil {
tx.Rollback()
return err
}
// 2. Создаем сообщение для Outbox в той же транзакции
outboxMsg := OutboxMessage{
Topic: "order.created",
Payload: order.ToJSON(), // Сериализованные данные заказа
Status: "pending",
}
err = tx.Create(&outboxMsg).Error
if err != nil {
tx.Rollback()
return err
}
// 3. Коммитим транзакцию. Если здесь произойдет сбой, ни данные, ни сообщение не будут сохранены.
return tx.Commit()
}
// Фоновый процесс (пример логики)
func ProcessOutbox() {
// Периодически читаем неотправленные сообщения
messages := db.Where("status = ?", "pending").Find(&[]OutboxMessage{}).Error
for _, msg := range messages {
err := messageBroker.Publish(msg.Topic, msg.Payload)
if err == nil {
// Если успешно отправлено, помечаем как отправленное
db.Model(&msg).Update("status", "sent")
} else {
// Логируем ошибку, сообщение останется в статусе 'pending' для повторной попытки
}
}
}
Плюсы Outbox Pattern:
- Атомарность: Гарантирует, что изменение данных и публикация сообщения происходят как единая атомарная операция.
- Надежность: Предотвращает потерю сообщений даже при сбоях системы или брокера сообщений.
- At-Least-Once Delivery: Обеспечивает, что сообщение будет отправлено как минимум один раз. Важно: Потребители сообщений должны быть идемпотентными, чтобы корректно обрабатывать возможные дубликаты, так как фоновый процесс может повторно отправить сообщение в случае временных сбоев.
- Простота отладки: Все связанные данные (бизнес-данные и сообщения) находятся в одной базе данных, что упрощает отладку и мониторинг.
Минусы Outbox Pattern:
- Дополнительная таблица в БД: Требует создания и управления дополнительной таблицей
outbox
. - Фоновый обработчик: Необходимость реализации и поддержки отдельного фонового процесса для отправки сообщений.
- Задержка: Сообщения отправляются не мгновенно, а с небольшой задержкой, зависящей от частоты работы фонового обработчика.