> Использовали ли транзакции при отправке сообщений в брокер (Go)

Уровень: senior · Роль: backend · Категория: Технические вопросы

Компании: BetBoom

Стек: Go

> Пример ответа

Да, транзакции при отправке сообщений в брокер (например, Kafka, RabbitMQ) использовались, но с оговорками. В Go это часто реализуется через паттерн Transactional Outbox.

Почему это необходимо:
Если приложение сохраняет данные в БД и одновременно отправляет сообщение в брокер, без транзакции возможна ситуация, когда данные записаны, а сообщение не отправлено (или наоборот). Это нарушает консистентность.

Как реализовано:

  1. В рамках одной транзакции БД записываем событие в таблицу outbox (например, с полями id, topic, payload, status).

  2. Отдельный воркер (или паттерн Polling Publisher) читает из outbox неподтверждённые записи и отправляет их в брокер.

  3. После успешной отправки статус записи меняется на sent (или запись удаляется).

Пример на Go (упрощённо):

GO
type OutboxMessage struct {
ID string `gorm:"primaryKey"`
Topic string
Payload []byte
Status string // "pending", "sent"
}
func CreateOrderAndPublishEvent(ctx context.Context, db *gorm.DB, producer *kafka.Producer, order Order) error {
tx := db.Begin()
defer tx.Rollback()
// Сохраняем заказ
if err := tx.Create(&order).Error; err != nil {
return err
}
// Пишем в outbox
msg := OutboxMessage{
ID: uuid.New().String(),
Topic: "order.created",
Payload: orderJSON,
Status: "pending",
}
if err := tx.Create(&msg).Error; err != nil {
return err
}
// Коммитим транзакцию - только теперь данные фиксируются
if err := tx.Commit().Error; err != nil {
return err
}
// После коммита отправляем в брокер (но это уже вне транзакции)
// Лучше делегировать воркеру, но для простоты - синхронно
if err := producer.SendMessage(ctx, msg.Topic, msg.Payload); err != nil {
// Логируем ошибку, но не откатываем БД - outbox позволяет повторить
log.Printf("failed to send message: %v", err)
return err
}
// Обновляем статус (можно асинхронно)
db.Model(&msg).Update("status", "sent")
return nil
}

Важные нюансы:

  • Транзакция БД и отправка в брокер не могут быть атомарны в распределённой системе. Outbox решает эту проблему через идемпотентность и повторные попытки.

  • В Kafka можно использовать транзакции продюсера (Idempotence + TransactionalId), но это не отменяет Outbox - он защищает от потери сообщений при сбое до коммита.

  • В RabbitMQ транзакции не рекомендуются из-за падения производительности; Outbox предпочтительнее.

Вывод: Транзакции использовались не напрямую с брокером, а через паттерн Outbox, гарантирующий доставку сообщений при сохранении консистентности данных.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью