NServiceBus - 如何为每个接收者订阅的消息类型获取单独的队列?

时间:2012-02-23 10:56:04

标签: c# nservicebus

我有以下情况:

enter image description here

因此,接收者订阅了两种事件:eventA和eventB。 NServiceBus为接收方(Receiver)创建队列,并将eventA和eventB类型的消息放入同一队列。问题是,如果我可以配置NServiceBus为接收器的每种类型的事件使用单独的队列(ReceiverEventA和ReceiverEventB)?或者我可以在单个进程中有两个接收器(并且每个接收器单独的队列)。 事实上,EventA需要比EventB花费更长的时间,并且它们是独立的 - 所以如果它们在不同的队列中,它们可以同时处理。

更新:如果我采用这样的天真方法,接收器无法以空引用异常开始:

 private static IBus GetBus<THandler, TEvent>()
    {                   
        var bus = Configure.With(new List<Type>
                                     {
                                         typeof(THandler), 
                                         typeof(TEvent),
                                         typeof(CompletionMessage)
                                     })
            .Log4Net()
            .DefaultBuilder()
            .XmlSerializer()
            .MsmqTransport()
            .IsTransactional(true)
            .PurgeOnStartup(false)
            .UnicastBus()
            .LoadMessageHandlers()
            .ImpersonateSender(false);

        bus.Configurer.ConfigureProperty<MsmqTransport>(x => x.InputQueue, "Queue" + typeof(THandler).Name);

        return bus.CreateBus().Start();
    }

    [STAThread]
    static void Main()
    {
        Busses = new List<IBus>
                     {
                         GetBus<ItemEventHandlerA, ItemEventA>(),
                         GetBus<ItemEventHandlerB, ItemEventB>()
                     };          

        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new TestForm());
    }

异常堆栈跟踪是:

  

在NServiceBusTest2.WinFormsReceiver.Program.GetBusTHandler,C:\ Users \ User \ Documents \ Visual Studio 2010 \ Projects \ NServiceBusTest2 \ NServiceBusTest2.WinFormsReceiver \ Program.cs中的TEvent:第57行      位于C:\ Users \ User \ Documents \ Visual Studio 2010 \ Projects \ NServiceBusTest2 \ NServiceBusTest2.WinFormsReceiver \ Program.cs中的NServiceBusTest2.WinFormsReceiver.Program.Main():第26行      在System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly,String [] args)
     在System.AppDomain.ExecuteAssembly(String assemblyFile,Evidence assemblySecurity,String [] args)      在Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
     在System.Threading.ThreadHelper.ThreadStart_Context(对象状态)
     在System.Threading.ExecutionContext.Run(ExecutionContext executionContext,ContextCallback回调,对象状态,布尔ignoreSyncCtx)
     在System.Threading.ExecutionContext.Run(ExecutionContext executionContext,ContextCallback回调,对象状态)
     在System.Threading.ThreadHelper.ThreadStart()

2 个答案:

答案 0 :(得分:5)

我开始写一个更为抽象的解释,说明为什么你不应该有两个独立的过程,以支持我对@stephenl发布的答案的评论。 NServiceBus基本上为每个进程强制执行一个输入队列。

通常情况下,你会有两个独立的过程。 EventAService将从QForEventA读取EventA,与EventBService在一个单独的进程中读取,该进程将从QForEventB读取EventB。

然后我更仔细地查看了您的示例代码,并意识到您使用的是Windows窗体应用程序。咄!现在我觉得有点傻。当然,你只能有一个过程。想象一下,如果在启动Outlook之后你还必须启动MailService.exe来实际获取邮件!

所以问题实际上是在Windows窗体应用程序中,EventA和EventB的处理时间差异很大。我无从得知这项工作是什么,但对于客户端应用程序来说这有点奇怪。

大多数情况下,这是一项需要进行大量处理的服务,而客户端收到的任何消息都相当轻量级 - 这与“实体X已经改变”有关,因此下次您需要直接从中加载它数据库“和处理它只涉及从缓存中删除一些东西 - 当然不是一个长期运行的过程。

但是听起来无论出于什么原因,你的客户端中的处理需要更长的时间,因为担心UI线程编组,阻塞UI线程等等,这在WinForms应用程序中是关注的。

我建议你不要在WinForms应用程序中的NServiceBus处理程序中进行所有处理,而是在其他地方编组。把它作为工作项或类似的东西扔给ThreadPool。或者将长时间运行的项目放入队列中,然后以自己的速度对后台线程进行紧缩。这样所有NServiceBus消息处理程序都是“是的,得到了​​消息。非常感谢。”如果NServiceBus消息一次处理一个,那真的不重要。

<强>更新

在评论中,OP询问如果在NServiceBus finsihes收到它之后将工作抛给ThreadPool会发生什么。当然,这就是这种方法的另一面 - 在NServiceBus完成后,你自己 - 如果它在ThreadPool中失败,那么由你来创建你自己的重试逻辑,或者只是捕获异常,提醒WinForms应用程序的用户,让它死掉。

显然这是最优的,但它引出了一个问题 - 你究竟想在WinForms应用程序中完成什么样的工作?如果NServiceBus提供的健壮性(有害消息的自动重试和错误队列)是这个难题的关键部分,那么为什么它首先在WinForms应用程序中进行?这个逻辑可能需要卸载到WinForms应用程序外部的服务,其中每个消息类型(通过部署单独的服务)具有单独的队列变得容易,然后只有影响UI的部分才会被发送回WinForms客户端。当到UI的消息只影响UI时,处理它们几乎总是微不足道的,并且你不需要卸载到ThreadPool来跟上它。

直接谈到您在GitHub Issue中描述的情况,这听起来确实类似于每种消息类型的单独进程完全是指定解决方案的情况。我听说部署和管理那么多流程听起来很难,但我想你会发现它并不像听起来那么糟糕。甚至还有优势 - 例如,如果您必须使用Amazon.com重新部署连接器,则只需重新部署THAT端点,不会因任何其他端点而停机,或者担心可能已经将错误引入其他端点。

为了简化部署,希望您使用的是持续集成服务器,然后检查DropkicK等帮助部署脚本的工具。就个人而言,我最喜欢的部署工具是老式的Robocopy。甚至像1)NET STOP ServiceName这样简单,2)ROBOCOPY,3)NET START ServiceName非常有效。

答案 1 :(得分:2)

您不需要,您只需要为接收器中的每个事件创建一个处理程序。例如

public class EventAHandler : IHandleMessages<EventA>{}

public class EventBHandler : IHandleMessages<EventB>{}

如果要分隔队列,则需要将每个处理程序放入单独的端点。这有意义吗?

此外,我认为您的图表需要一些工作。它是一个标签的东西,我敢肯定,但我看到的方式是Host是实际的发布者,而不是客户端端点(你已经命名为Publisher A和Publisher B)。

更新:这是一个简单的例子,但说明了我的意思 - https://github.com/sliedig/sof9411638

我已经扩展了NServiceBus附带的Full Duplex示例,其中包括三个额外的端点,其中两个端点分别订阅服务器发布的事件,另一个端点处理两者。 HTH。