如何使用存储引擎持久保存Saga实例并避免竞争情况

时间:2019-08-29 12:06:15

标签: state-machine masstransit saga automatonymous

我尝试使用RedisSagaRepository保留Saga实例;我想在负载平衡设置中运行Saga,因此无法使用InMemorySagaRepository。 但是,切换后,我发现Saga并未处理Consumers发布的某些事件。我检查了队列,没有看到任何消息。

我注意到的是,当使用者花很少甚至没有时间来处理命令和发布事件时,很可能会发生这种情况。 如果我使用InMemorySagaRepository或在Task.Delay()中添加Consumer.Consume(),则不会发生此问题

我使用不正确吗?

此外,如果我想在负载平衡设置中运行Saga,并且Saga是否需要使用字典发送相同类型的多个命令来跟踪完整性(与Handling transition to state for multiple events中的逻辑类似)。当多个消费者同时发布事件时,如果两个Sagas同时处理两个不同的事件,我是否会处于竞争状态?在这种情况下,是否可以正确设置State Dictionary in State对象?

该代码可用here

SagaService.ConfigureSagaEndPoint()是我在InMemorySagaRepositoryRedisSagaRepository之间切换的地方

private void ConfigureSagaEndPoint(IRabbitMqReceiveEndpointConfigurator endpointConfigurator)
{
    var stateMachine = new MySagaStateMachine();

    try

    {
        var redisConnectionString = "192.168.99.100:6379";
        var redis = ConnectionMultiplexer.Connect(redisConnectionString);

        ///If we switch to RedisSagaRepository and Consumer publish its response too quick,
        ///It seems like the consumer published event reached Saga instance before the state is updated
        ///When it happened, Saga will not process the response event because it is not in the "Processing" state
        //var repository = new RedisSagaRepository<SagaState>(() => redis.GetDatabase());
        var repository = new InMemorySagaRepository<SagaState>();

        endpointConfigurator.StateMachineSaga(stateMachine, repository);
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.ToString());
    }
}

LeafConsumer.Consume是我们添加Task.Delay()

的地方
public class LeafConsumer : IConsumer<IConsumerRequest>
{
    public async Task Consume(ConsumeContext<IConsumerRequest> context)
    {
        ///If MySaga project is using RedisSagaRepository, uncomment await Task.Delay() below
        ///Otherwise, it seems that the Publish message from Consumer will not be processed
        ///If using InMemorySagaRepository, code will work without needing Task.Delay
        ///Maybe I am doing something wrong here with these projects
        ///Or in real life, we probably have code in Consumer that will take a few milliseconds to complete
        ///However, we cannot predict latency between Saga and Redis
        //await Task.Delay(1000);

        Console.WriteLine($"Consuming CorrelationId = {context.Message.CorrelationId}");
        await context.Publish<IConsumerProcessed>(new
        {
            context.Message.CorrelationId,
        });
    }
}

1 个答案:

答案 0 :(得分:1)

当您以这种方式发布事件,并且使用具有非事务性传奇存储库(例如Redis)的多个服务实例时,您需要设计传奇,以便Redis使用并强制使用唯一标识符。这样可以防止创建同一传奇的多个实例。

您还需要接受超出“预期”状态的事件。例如,在仅接收处理中的另一个事件之前,期望接收到一个开始,将传奇置于处理状态,这很可能会失败。建议允许通过任何事件序列来启动传奇(最初是自动命名),以避免出现乱序的消息传递问题。只要事件全部将拨盘从左向右移动,就会达到最终状态。如果在较晚的事件之后收到较早的事件,则不应将状态向后移动(在此示例中为向左移动),而应仅向saga实例添加信息并将其保留在较晚的状态。

如果在单独的服务实例上处理了两个事件,它们都将尝试将saga实例插入Redis,这将作为重复实例失败。然后,该消息应重试(将UseMessageRetry()添加到您的接收端点),然后该消息将拾取现在存在的传奇实例并应用事件。