MassTransit In-Memory Outbox в саге - PullRequest
       11

MassTransit In-Memory Outbox в саге

1 голос
/ 23 февраля 2020

Мы провели несколько нагрузочных тестов на Saga с исходящим почтовым ящиком в памяти. Во время этих тестов мы моделировали различные типы сбоев: перезапуски приложений, перезапуск инфраструктуры, перезапуск брокера сообщений и т. Д. c.

Мы заметили, что некоторые экземпляры саг не заканчиваются sh, и у нас было множество ошибок. : Automatonymous.NotAcceptedStateMachineException: ... {SomeEvent}: не принято в состоянии {SomeState}

После некоторой отладки мы изолировали проблему. Я попытаюсь описать это, используя этот пример кода:

public class OrderStateMachine : MassTransitStateMachine<Order>
{
    public OrderStateMachine()
    {
        InstanceState(x => x.CurrentState);

        During(Initial, 
            When(Create).TransitionTo(New));

        During(New,
            When(AddItem)
                .Then(x => x.Instance.Items.Add(x.Data.Name)),

            When(Submit)
                .ThenAsync(async x =>
                {
                    // do something
                    await x.Publish(new SendEmail {Text = $"Order submitted. {x.Instance.Summary}"});
                })
                .TransitionTo(Submitted));

        During(Submitted,
            When(Accept)
                .ThenAsync(async x =>
                {
                    // do something
                    await x.Publish(new SendEmail {Text = $"Order accepted. {x.Instance.Summary}"});
                })
                .Finalize());

        SetCompletedWhenFinalized();
    }

    public State New { get; private set; }
    public State Submitted { get; private set; }

    public Event<Create> Create { get; private set; }
    public Event<AddItem> AddItem { get; private set; }
    public Event<Submit> Submit { get; private set; }
    public Event<Accept> Accept { get; private set; }
}

public class Order : SagaStateMachineInstance
{
    public Guid CorrelationId { get; set; }
    public string CurrentState { get; set; }
    public IList<string> Items { get; set; } = new List<string>();

    public string Summary => $"Items: {string.Join(", ", Items)}";
}

public class Create : CorrelatedBy<Guid>
{
    public Guid CorrelationId { get; set; }
}

public class AddItem : CorrelatedBy<Guid>
{
    public Guid CorrelationId { get; set; }
    public string Name { get; set; }
}

public class Submit : CorrelatedBy<Guid>
{
    public Guid CorrelationId { get; set; }
}

public class Accept : CorrelatedBy<Guid>
{
    public Guid CorrelationId { get; set; }
}

public class SendEmail
{
    public string Text { get; set; }
}

Вот что происходит:

  1. Во время Заказ в состоянии Новый , мы обрабатываем событие Submit , вносим некоторые изменения в порядок, публикуем sh SendEmail событие и переходим в Отправлено состояние.
  2. Состояние заказа успешно сохраняется в БД
  3. Сбои приложения (т. Е. Принудительный перезапуск) перед отправкой сообщения в исходящие сообщения.
  4. Приложение перезапускается, Подтверждение доставляется во второй раз, Заказ находится в состоянии Отправлено , и мы получаем исключение Automatonymous.NotAcceptedStateMachineException: ... Отправить : Не принято в состоянии Отправлено

Что если это произойдет в состоянии Отправлено во время Принять Обработка событий? Мое предположение:

  1. Во время Заказ в состоянии Отправлено , мы обрабатываем Принять событие, внести некоторые изменения в порядок, опубликовать sh Событие SendEmail и завершение Заказ
  2. Заказ Состояние удаляется из БД, поскольку оно завершено и настроено на CompletedWhenFinalized
  3. Приложение аварийно завершает работу (то есть принудительно перезапускает), прежде чем отправляет сообщение в исходящие.
  4. Приложение перезапускается, Принять доставляется во второй раз, Заказ больше не находится в БД, мы потеряли всю информацию об этом ... что происходит сейчас?

Какое лучшее решение для таких ситуаций? Я прочитал замечательную статью Криса о In-Memory Outbox, но не понимаю, как можно обрабатывать сообщение во время повторной доставки, когда Saga находится в состоянии, когда оно больше не обрабатывает это сообщение. Конечно, мы можем обработать доставленное событие в следующем состоянии с некоторыми хитрыми логами c, но это кажется довольно громоздким. Наша Saga намного сложнее, чем предоставленный образец.

Может быть, решением будет транзакция, которая фиксируется после отправки всех сообщений из исходящих сообщений? Можно ли как-то настроить Transaction Outbox на Saga?

1 Ответ

1 голос
/ 23 февраля 2020

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

Вторая часть - это другой ответ, и на самом деле это довольно просто. Вы не завершаете заказ в Accept. Вы создаете дополнительное состояние Принято , в которое заказ переходит после принятия. И вы удаляете экземпляры заказа через определенный промежуток времени (неделю, месяц и т. Д.). Таким образом, когда сообщение Accept доставляется экземпляру Accepted, вы можете регенерировать опубликованные события.

Теперь вы можете использовать Quartz, чтобы запланировать будущее сообщение для завершения саги, чего не происходит. любая бизнес-логика c, но только удаляет экземпляр саги. И вы могли бы установить обработчик Initially (When (RemoveOrder) .Ignore ()), который бы отбрасывал сообщение о порядке удаления, если сага не существует. И это делает его автоматизированным c. Но в прошлых системах мы только что заархивировали раздел диапазона дат в файловой группе (на SQL сервере) или удалили более старые записи через 30 или 90 дней или что-то еще.

...