C# 如何在MassTransit 3.0中使用分散/聚集模式实现传奇

C# 如何在MassTransit 3.0中使用分散/聚集模式实现传奇,c#,masstransit,saga,C#,Masstransit,Saga,Jimmy Boagard描述了一家麦当劳快餐连锁店,将其比作 从上述文章中窃取的工作流图像: 初步实施思路: 为所有食品站将获得的所有类型的FoodOrdered事件提供一个公共接口,然后每个食品站将能够消费/创建其各自的项目并发布一个公共完成事件。例:薯条和汉堡站收到一条关于薯条订单的消息,薯条站消费该订单,并宣布该传奇正在关注的ItemDoneEvent 最初的担忧: 因为这个故事并不关心食物的种类,只是所有的食物都完成了,这似乎是一个好的解决方案。然而,阅读关于共享队列的警告,并注意到框

Jimmy Boagard描述了一家麦当劳快餐连锁店,将其比作

从上述文章中窃取的工作流图像:

初步实施思路:

为所有食品站将获得的所有类型的FoodOrdered事件提供一个公共接口,然后每个食品站将能够消费/创建其各自的项目并发布一个公共完成事件。例:薯条和汉堡站收到一条关于薯条订单的消息,薯条站消费该订单,并宣布该传奇正在关注的ItemDoneEvent

最初的担忧:

因为这个故事并不关心食物的种类,只是所有的食物都完成了,这似乎是一个好的解决方案。然而,阅读关于共享队列的警告,并注意到框架似乎在说这种方法“会发生不好的事情(TM)”。但我不知道如果不为厨房中的每一种食物创建一个消息请求和响应并关联事件,你还能做什么。例:炸薯条、汉堡、炸薯条、汉堡。如果你必须对厨房里的每一件物品都这样做,这将是非常乏味的


考虑到上述问题-对于这种类型的工作流,一个好的saga示例应该是什么样的?

将已完成事件回退到saga的问题是,它会在共享资源(即saga状态)上产生争用

Jim在你提到的那篇文章之后还有一篇文章概述了问题和解决方案。当然,他特别提到了NServiceBus,但问题和概念是一样的

创建一个外部存储器。为每个工作项做一个记录。让每个员工都将自己的工作设置为已完成,而saga会使用延迟消息进行有效的轮询,以查看是否所有工作都已完成


然后,您仍在执行分散-聚集,但“聚合器”已被process manager模式取代以减少争用。

我遇到了类似的问题-需要发布几行命令(所有相同的接口,
IMyRequest
)然后等待

实际上,我的命令启动了其他saga,它在处理结束时发布
IMyRequestDone
,而不标记saga completed。(需要在以后的某个时间完成它们。)所以,我不必在父传奇中保存已完成的嵌套传奇的数量,而只需查询子传奇实例的状态

检查每个
MyRequestDone
消息:

Schedule(() => FailSagaOnRequestsTimeout, x => x.CheckToken, x =>
{
    // timeout for all requests
    x.Delay = TimeSpan.FromMinutes(10);
    x.Received = e => e.CorrelateById(context => context.Message.CorrelationId);
});


During(Active,
    When(Xxx)
        .ThenAsync(async context =>
        {
            await context.Publish(context => new MyRequestCommand(context.Instance, "foo"));
            await context.Publish(context => new MyRequestCommand(context.Instance, "bar"));

            context.Instance.WaitingMyResponsesTimeoutedAt = DateTime.UtcNow + FailSagaOnRequestsTimeout.Delay;
            context.Instance.WaitingMyResponsesCount = 2;
        })
        .TransitionTo(WaitingMyResponses)
        .Schedule(FailSagaOnRequestsTimeout, context => new FailSagaCommand(context.Instance))
    );

During(WaitingMyResponses,
    When(MyRequestDone)
        .Then(context =>
        {
            if (context.Instance.WaitingMyResponsesTimeoutedAt < DateTime.UtcNow)
                throw new TimeoutException();
        })
        .If(context =>
        {
            var db = serviceProvider.GetRequiredService<DbContext>();
            var requestsStates = db.MyRequestStates.Where(x => x.ParentSagaId == context.Instance.CorrelationId).Select(x => x.State).ToList();
            var allDone = requestsStates.Count == context.Instance.WaitingMyResponsesCount &&
                requestsStates.All(x => x != nameof(MyRequestStateMachine.Processing)); // assume 3 states of request - Processing, Done and Failed
            return allDone;
        }, x => x
            .Unschedule(FailSagaOnRequestsTimeout)
            .TransitionTo(Active))
        )
        .Catch<TimeoutException>(x => x.TransitionTo(Failed))
);

During(WaitingMyResponses,
    When(FailSagaOnRequestsTimeout.Received)
        .TransitionTo(Failed)
Schedule(()=>failsagaonrequestestimeout,x=>x.CheckToken,x=>
{
//所有请求的超时
x、 延迟=从分钟(10)开始的时间跨度;
x、 Received=e=>e.CorrelateById(context=>context.Message.CorrelationId);
});
在(活动)期间,,
何时(Xxx)
.ThenAsync(异步上下文=>
{
wait context.Publish(context=>newmyrequestCommand(context.Instance,“foo”);
wait context.Publish(context=>newmyrequestCommand(context.Instance,“bar”);
context.Instance.WaitingMyResponsesTimeoutedAt=DateTime.UtcNow+failsagaonrequestestestimeout.Delay;
context.Instance.waitingMyResponseCount=2;
})
.转换到(等待我的响应)
.Schedule(FailSagaOnRequestsTimeout,context=>newfailsagacommand(context.Instance))
);
在等待我的回答时,
何时(MyRequestDone)
。然后(上下文=>
{
if(context.Instance.WaitingMyResponsesTimeoutedAt
{
var db=serviceProvider.GetRequiredService();
var requestsStates=db.MyRequestStates.Where(x=>x.ParentSagaId==context.Instance.CorrelationId)。选择(x=>x.State.ToList();
var allDone=requestsStates.Count==context.Instance.waitingMyResponseCount&&
requestsStates.All(x=>x!=nameof(MyRequestStateMachine.Processing));//假设请求处理的三种状态:完成和失败
返回所有已完成;
},x=>x
.计划外(失败A不请求估计)
.转换为(主动)
)
.Catch(x=>x.transitiono(失败))
);
在等待我的回答时,
何时(收到失败请求的估计值)
.转换到(失败)
定期检查是否完成了所有请求(通过“减少NServiceBus Saga负载”):

Schedule(()=>checkAllRequestsOne,x=>x.CheckToken,x=>
{
//检查间隔
x、 延迟=从秒开始的时间跨度(15);
x、 Received=e=>e.CorrelateById(context=>context.Message.CorrelationId);
});
在(活动)期间,,
何时(Xxx)
.ThenAsync(异步上下文=>
{
wait context.Publish(context=>newmyrequestCommand(context.Instance,“foo”);
wait context.Publish(context=>newmyrequestCommand(context.Instance,“bar”);
context.Instance.WaitingMyResponsesTimeoutedAt=DateTime.UtcNow.AddMinutes(10);
context.Instance.waitingMyResponseCount=2;
})
.转换到(等待我的响应)
.Schedule(checkAllRequestsOne,context=>newcheckAllRequestsOne命令(context.Instance))
);
在等待我的回答时,
何时(选中AllRequestsOne.Received)
。然后(上下文=>
{
var db=serviceProvider.GetRequiredService();
var requestsStates=db.MyRequestStates.Where(x=>x.ParentSagaId==context.Instance.CorrelationId)。选择(x=>x.State.ToList();
var allDone=requestsStates.Count==context.Instance.waitingMyResponseCount&&
requestsStates.All(x=>x!=nameof(MyRequestStateMachine.Processing));
如果(!全部完成)
{
if(context.Instance.WaitingMyResponsesTimeoutedAtSchedule(() => CheckAllRequestsDone, x => x.CheckToken, x =>
{
    // check interval
    x.Delay = TimeSpan.FromSeconds(15);
    x.Received = e => e.CorrelateById(context => context.Message.CorrelationId);
});

During(Active,
    When(Xxx)
        .ThenAsync(async context =>
        {
            await context.Publish(context => new MyRequestCommand(context.Instance, "foo"));
            await context.Publish(context => new MyRequestCommand(context.Instance, "bar"));

            context.Instance.WaitingMyResponsesTimeoutedAt = DateTime.UtcNow.AddMinutes(10);
            context.Instance.WaitingMyResponsesCount = 2;
        })
        .TransitionTo(WaitingMyResponses)
        .Schedule(CheckAllRequestsDone, context => new CheckAllRequestsDoneCommand(context.Instance))
    );

During(WaitingMyResponses,
    When(CheckAllRequestsDone.Recieved)
        .Then(context =>
        {
            var db = serviceProvider.GetRequiredService<DbContext>();
            var requestsStates = db.MyRequestStates.Where(x => x.ParentSagaId == context.Instance.CorrelationId).Select(x => x.State).ToList();
            var allDone = requestsStates.Count == context.Instance.WaitingMyResponsesCount &&
                requestsStates.All(x => x != nameof(MyRequestStateMachine.Processing));
            if (!allDone)           
            {
                if (context.Instance.WaitingMyResponsesTimeoutedAt < DateTime.UtcNow + CheckAllRequestsDone.Delay)              
                    throw new TimeoutException();
                throw new NotAllDoneException();
            }
        })
        .TransitionTo(Active)
        .Catch<NotAllDoneException>(x => x.Schedule(CheckAllRequestsDone, context => new CheckAllRequestsDoneCommand(context.Instance)))
        .Catch<TimeoutException>(x => x.TransitionTo(Failed));