佐贺市的MassTransit内存发件箱

时间:2020-02-23 17:27:35

标签: masstransit

我们在带有In-Memory发件箱的Saga上进行了一些负载测试。在这些测试中,我们模拟了不同类型的故障:应用程序重启,基础架构重启,消息代理重启等。

我们注意到,某些传奇实例未完成,并且出现了很多错误: 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. 在状态订单期间,我们处理提交事件, 更改顺序,发布 SendEmail 事件并过渡到 已提交状态。
  2. 订单状态已成功保存到数据库
  3. 应用程序在发送消息之前崩溃(即强制重新启动) 发件箱。
  4. 应用程序重新启动,提交第二次交付,订单处于已提交状态,我们得到了一个异常Automatonymous.NotAcceptedStateMachineException:。 .. 提交:在状态已提交
  5. 中不被接受

如果在接受事件处理过程中状态为已提交,该怎么办?我的假设:

  1. 已提交状态下的订单期间,我们处理接受事件,请执行一些操作 更改订单,发布 SendEmail 事件并最终确定订单
  2. 订单状态已从数据库中删除,因为它已完成并配置为CompletedWhenFinalized
  3. 应用程序在发送消息之前崩溃(即强制重新启动) 发件箱。
  4. 应用程序重新启动,接受第二次交付,订单不再存在于数据库中,我们丢失了有关它的所有信息...现在发生了什么?

处理这种情况的最佳解决方案是什么?我已经阅读了克里斯关于内存发件箱的出色article,但不了解当Saga处于不再处理该消息的状态时,如何在重新交付期间处理该消息。当然,我们可以使用一些棘手的逻辑在下一个状态中处理重新传递的事件,但这似乎很麻烦。我们的Saga比提供的示例要复杂得多。

也许发送了所有发件箱中的消息之后提交的事务,是否可以解决? Transaction Outbox可以以某种方式配置Saga吗?

1 个答案:

答案 0 :(得分:1)

由于您已经阅读了有关使用发件箱的文章,并且意识到需要将unsafe的处理程序添加到Submit状态,所以这确实是答案。但是,与更新了传奇状态并保留的原始处理程序不同,您只需要重新生成发送/发布的事件即可。解决了问题的第一部分,已提交

第二部分是一个不同的答案,实际上很简单。您不会在“接受”中最终确定订单。您创建一个附加状态 Accepted ,该状态在接受后即转换为。然后,您将在一段时间(一周,一个月等)之后删除订单实例。这样,当“接受”消息传递到“接受”实例时,您可以重新生成已发布的事件。

现在,您可以使用Quartz安排将来的消息来完成传奇,这不会执行任何业务逻辑,而只会删除传奇实例。您可以设置一个Initially(When(RemoveOrder).Ignore())处理程序,如果该传奇不存在,该处理程序将丢弃移除订单消息。这使其成为自动的。但是在过去的系统中,我们只是归档了文件组的日期范围分区(在SQL Server中),或者在30天或90天或任何其他天后删除了较旧的记录。