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

我有以下情况:

在此处输入图像描述

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

更新:如果我采用这样的天真方法,接收器无法启动null引用exception:

private static IBus GetBus() { var bus = Configure.With(new List { typeof(THandler), typeof(TEvent), typeof(CompletionMessage) }) .Log4Net() .DefaultBuilder() .XmlSerializer() .MsmqTransport() .IsTransactional(true) .PurgeOnStartup(false) .UnicastBus() .LoadMessageHandlers() .ImpersonateSender(false); bus.Configurer.ConfigureProperty(x => x.InputQueue, "Queue" + typeof(THandler).Name); return bus.CreateBus().Start(); } [STAThread] static void Main() { Busses = new List { GetBus(), GetBus() }; Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new TestForm()); } 

exception堆栈跟踪是:

在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程序集,String [] args)
at System.AppDomain.ExecuteAssembly(String assemblyFile,Evidence assemblySecurity,String [] args)at 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()

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

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

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

所以问题实际上是你的Windows窗体应用程序中的EventA和EventB的处理时间截然不同。 我没有办法知道那项工作是什么,但对于客户端应用程序来说这有点奇怪。

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

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

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

更新:

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

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

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

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

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

 public class EventAHandler : IHandleMessages{} public class EventBHandler : IHandleMessages{} 

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

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

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

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