Лучший способ гарантировать, что событие в конечном итоге будет опубликовано в системе очередей сообщений

Пожалуйста, представьте, что у вас есть метод, подобный следующему:

public void PlaceOrder(Order order)
{
     this.SaveOrderToDataBase(order);
     this.bus.Publish(new OrderPlaced(Order));    
}

После сохранения заказа в базе данных событие публикуется в системе очередей сообщений, поэтому его могут обрабатывать другие подсистемы на том же или другом компьютере.

Но что произойдет, если this.bus.Publish(new OrderPlaced(Order)) звонок не получается? Или машина вылетает сразу после сохранения заказа в базе данных? Событие не публикуется, и другие подсистемы не могут его обработать. Это неприемлемо. Если это произойдет, мне нужно убедиться, что событие в конечном итоге будет опубликовано.

Какие приемлемые стратегии я могу использовать? Какой из них лучший?

ПРИМЕЧАНИЕ. Я не хочу использовать распределенные транзакции.

РЕДАКТИРОВАТЬ:

Пол Сасик очень близок, и я думаю, что могу достичь 100%. Это то, что я думал:

Сначала создайте таблицу Events в базе данных следующим образом:

CREATE TABLE Events (EventId int PRIMARY KEY)

Вы можете хотеть использовать guids вместо int, или вы можете использовать последовательности или тождества.

Затем выполните следующий псевдокод:

open transaction
save order and event via A SINGLE transaction
in case of failure, report error and return
place order in message queue
in case of failure, report error, roll back transaction and return
commit transaction

Все события должны включать EventId. Когда подписчики события получают событие, они сначала проверяют существование EventId в базе данных.

Таким образом, вы получите 100% надежность, а не только 99,999%

2 ответа

Решение

Правильный способ гарантировать, что событие в конечном итоге будет опубликовано в системе очередей сообщений, объясняется в этом видео и в этом сообщении в блоге.

По сути, вам нужно сохранить сообщение для отправки в базу данных в той же транзакции, в которой вы выполняете логическую операцию бизнес-процесса, затем асинхронно отправить сообщение на шину и удалить сообщение из базы данных в другой транзакции:

public void PlaceOrder(Order order)
{
     BeginTransaction();
     Try 
     {
         SaveOrderToDataBase(order);
         ev = new OrderPlaced(Order);
         SaveEventToDataBase(ev);
         CommitTransaction();
     }
     Catch 
     {
          RollbackTransaction();
          return;
     }

     PublishEventAsync(ev);    
}

async Task PublishEventAsync(BussinesEvent ev) 
{
    BegintTransaction();
    try 
    {
         await DeleteEventAsync(ev);
         await bus.PublishAsync(ev);
         CommitTransaction();
    }
    catch 
    {
         RollbackTransaction();
    }

}

Поскольку PublishEventAsync может потерпеть неудачу, вы должны повторить попытку позже, поэтому вам нужен фоновый процесс для повторной отправки неудачных отправлений, что-то вроде этого:

foreach (ev in eventsThatNeedsToBeSent) {
    await PublishEventAsync(ev);
}

Вы можете сделать this.bus.Publish вызвать часть транзакции базы данных this.SaveOrderToDataBase, Это означает, что this.SaveOrderToDataBase выполняется в области транзакции, и если вызов db завершается неудачно, вы никогда не вызываете mq, а если вызов mq не выполняется, вы откатываете транзакцию db, оставляя обе системы в согласованном состоянии. Если оба вызова успешны, вы совершаете транзакцию БД.

псевдокод:

open transaction
save order via transaction
in case of failure, report error and return
place order in message queue
in case of failure, report error, roll back transaction and return
commit transaction

Вы не упомянули какую-либо конкретную технологию БД, поэтому вот ссылка на вики-статью о транзакциях. Даже если вы новичок в сделках, это хорошее место для начала. И немного хороших новостей: их не сложно реализовать.

Другие вопросы по тегам