Rebus:2个进程中的2个处理程序。不一致和交替点击

时间:2016-11-18 15:08:46

标签: c# handler bus rebus

我有两个使用Rebus的控制台应用程序。它们都引用了定义消息(命令和事件)的程序集。 控制台应用" A"发送命令并侦听事件以进行日志记录(例如:发送CreateTCommand并侦听TCreatedEvent)。控制台应用" B" (实际上是一个ASP.NET核心应用程序)监听命令并处理它们(例如:一个传奇由CreateTCommand启动,聚合被创建并引发一个TCreatedEvent)。在app" B"的过程中的另一个DLL中有一个TCreatedEvent的处理程序。

所以我有app" A"发送的创建命令以及创建活动的两个处理程序,一个在app" A"和应用程序中的一个" B"。

问题:当我从app" A"发送命令时第一次,app" B"引发创建的事件,该事件在同一进程中触发处理程序。处理程序 在app" A"没有触发。 来自app" A"的进一步命令总是由应用程序中的传奇处理" B"但是创建的事件在该过程中永远不会再次触及处理程序,但是由app" A" !!!处理 有时(我无法理解如何重现)来自app" A"不是应用程序中的传奇处理" B" (我在MSMQ中的错误队列中找到了带有异常&#34的命令;带有Id的消息无法分派给任何处理程序")。 有时(很少)两个处理程序都被击中。但我无法一致地重现这种行为......

我对此的感觉(对Rebus一无所知,这对我来说很新):

  • 可能是并发问题吗?我的意思是:Rebus被配置为在进程外部存储订阅(使用SQL或Mongo,问题不会消失),所以我认为第一个处理程序可能太快并且在第二个处理程序之前将事件标记为已处理处理程序被调用
  • 检查订阅SQL表,我找到5行(我在代码中订阅的每种类型的一个事件(使用app启动时使用bus.Subscribe())具有相同的地址(队列名称)链接到我的本地计算机名称。。只有一个地址有2个进程尝试使用它,这是一个问题吗?

Rebus的配置代码在2个应用程序中是相同的,如下所示:

        const string inputQueueAddress = "myappqueue";
        var mongoClient = new MongoClient("mongodb://localhost:27017");
        var mongoDatabase = mongoClient.GetDatabase("MyAppRebusPersistence");
        var config = Rebus.Config.Configure.With(new NetCoreServiceCollectionContainerAdapter(services))
            .Logging(l => l.Trace())
            .Routing(r => r.TypeBased()
                .MapAssemblyOf<AlertsCommandStackAssemblyMarker>(inputQueueAddress)
                .MapAssemblyOf<AlertsQueryStackAssemblyMarker>(inputQueueAddress)
            )
            .Subscriptions(s => s.StoreInMongoDb(mongoDatabase, "subscriptions"))
            .Sagas(s => s.StoreInMongoDb(mongoDatabase))
            .Timeouts(t => t.StoreInMongoDb(mongoDatabase, "timeouts"))
            .Transport(t => t.UseMsmq(inputQueueAddress));

        var bus = config.Start();
        bus.Subscribe<AlertDefinitionCreatedEvent>();
        bus.Subscribe<AlertStateAddedEvent>();
        bus.Subscribe<AlertConfigurationForEhrDefinitionAddedEvent>();
        services.AddSingleton(bus);

        services.AutoRegisterHandlersFromThisAssembly();

我希望有人可以提供帮助,这让我疯狂......

p.s。:当传递isCentralized时,问题也存在:true到subscription.StoreInMongoDb()。

编辑1:我添加了控制台日志记录,您可以看到这种奇怪的行为: https://postimg.org/image/czz5lchp9/

第一个命令发送成功。它由saga处理,事件触发了控制台app#34; A&#34;。 Rebus说第二个命令没有发送给任何处理程序,但它实际上是由saga处理的(我在调试中遵循了代码)并且事件由app中的处理程序处理&#34; B&#34;而不是&#34; A&#34; ...为什么? ;(

编辑2:我正在调试Rebus的源代码。我注意到在ThreadPoolWorker.cs中,方法是TryAsyncReceive

    async void TryAsyncReceive(CancellationToken token, IDisposable parallelOperation)
    {
        try
        {
            using (parallelOperation)
            using (var context = new DefaultTransactionContext())
            {
                var transportMessage = await ReceiveTransportMessage(token, context);

                if (transportMessage == null)
                {
                    context.Dispose();

                    // no need for another thread to rush in and discover that there is no message
                    //parallelOperation.Dispose();

                    _backoffStrategy.WaitNoMessage();
                    return;
                }

                _backoffStrategy.Reset();

                await ProcessMessage(context, transportMessage);
            }
        }

在应用程序&#34; B&#34;发布TCreatedEvent之后,在应用程序&#34; A&#34;代码到达     等待ProcessMessage(context,transportMessage) transportMessage是实际事件。在应用程序&#34; B&#34;的过程中未达到此行代码。好像消息的第一个接收者将其从MSMQ的队列中删除。正如我所说,我对Rebus和总线一般都很陌生,但如果这种行为符合设计,我会感到非常困惑......多个进程中的多个总线如何监听同一个队列? / p>

1 个答案:

答案 0 :(得分:2)

您永远不应该有两个总线实例接收来自同一队列的消息,除非它们是同一端点的多个实例

当两个进程使用相同的输入队列时,它们将相互接收消息。如果您正在实现competing consumers pattern,使用它在工作进程集群之间均匀分配工作,但是您无法在多个不同的端点之间共享输入队列,那么这绝对可以。

我的猜测是,如果让每个总线实例使用自己的输入队列,一切看起来都会更加可预测;)

  

现在你告诉我这些处理程序需要住在主应用程序中

不,我不是:)我告诉你,如果让两个不同的应用程序抢夺对方的消息,你将得到不可预测的结果。

虽然所有处理程序都在同一个总线实例中(因此可以通过来自同一队列的消息调用),但最常见的情况是,您将以符合您希望的方式拆分应用程序进化系统。

通过这种方式,您可以一次更新一个应用程序,避免大“停止世界” - 更新。

您可以通过启动多个端点来执行此操作,每个端点使用自己的队列 - 然后在端点之间使用ROUTE消息进行通信。

考虑要将命令发送到命令处理器的场景。命令处理器是一个Rebus端点,它从HH:mm队列中获取消息。

在发件人的最后,您将配置一个“端点映射”(您可以在the routing section on the Rebus wiki中阅读更多相关信息,如下所示:

command_processor

这将使发件人能够简单地进入

Configure.With(...)
    .Transport(t => t.UseMsmq("sender"))
    .Routing(r => {
        r.TypeBased()
            .Map<TheCommand>("command_processor");
    })
    .Start();

然后总线将知道将命令消息发送到哪个队列。

我希望这更清楚:)

请注意,这是point-to-point messaging的一个非常简单的情况,其中一个端点发送一条消息,该消息应由一个其他端点使用。 Rebus可以为您提供其他几种模式,例如: request/replypublish/subscribe