如何设计处理到达队列的消息的服务

时间:2009-07-20 11:10:36

标签: multithreading msmq

我有一个多线程Windows服务的设计问题,它处理来自多个客户端的消息。 规则是

  • 每条消息都是为实体(具有唯一ID)处理某些内容,并且可以是不同的,即DoA,DoB,DoC等。实体ID在消息的有效负载中。
  • 处理可能需要一些时间(最多几秒)。
  • 必须按照每个实体(具有相同ID)到达的顺序处理邮件。
  • 然而,可以同时为另一个实体处理消息(即,只要它们不是同一个实体ID)。
  • 并发处理的数量是可配置的(通常为8)
  • 消息不会丢失。如果处理消息时出错,则必须存储该消息和同一实体的所有其他消息,以便将来手动处理。
  • 消息到达事务性MSMQ队列。

您将如何设计服务?我有一个有效的解决方案,但想知道其他人如何解决这个问题。

4 个答案:

答案 0 :(得分:1)

您要做的第一件事就是退后一步,想一想这个应用程序的性能有多重要。你真的需要同时处理消息吗?这是关键任务吗?或者你只是你需要它吗?您是否在服务上运行了一个分析器,以找到过程的真正瓶颈并对其进行优化?

我问的原因是因为你提到你想要8个并发 - 然而,如果你让这个应用程序单线程,它将极大降低复杂性&发展与发展测试时间......因为你只想要8,所以几乎看起来不值得......

其次,因为您只能在同一个实体上处理并发消息 - 您有多少次真正从客户端获取并发请求来获取同一个实体?是否值得为一个可能不会经常出现的用例添加如此多的复杂层?

我会吻。我通过WCF使用MSMQ,并将我的WCF服务保持为单例。现在您拥有MSMQ的强大,有序的可靠性,您现在可以满足您的实际要求。然后我用高负荷用实际数据测试它,并运行一个分析器来找到瓶颈如果我发现它太慢了。只有这样,我才能解决构建更复杂的应用程序以便仅针对特定用例管理并发性的所有额外麻烦......

要考虑的一个设计是创建一个中央“门卫”或“服务总线”服务,它接收来自客户端的所有消息,然后将这些消息传递给实际的工作服务。当他收到请求时,他然后发现他的另一个客户端是否已经为同一个实体处理了一条消息 - 如果是,他将其发送给他发送另一条消息的同一服务。通过这种方式,您可以同时为给定实体处理相同的消息,而不是更多...而且您可以轻松实现无缝可伸缩性...但是,如果我必须这样做,我只会这样做,并且通过分析和测试证明了这一点,而不是因为'我们认为我们需要它'(参见YAGNI校长:))

答案 1 :(得分:0)

虽然我的要求与您的要求不同,但我确实必须处理来自消息队列的并发处理。我的解决方案是提供一个服务,该服务将查看每个传入消息并将其交给代理进程使用。该服务具有一个设置,可以控制它可以运行的代理程序数。

答案 2 :(得分:0)

我会考虑从单个线程安全队列中读取每个线程的n个线程。然后我会对EntityId进行哈希处理,以确定在其上放置一条崩溃消息的女巫队列。

有时,某些线程无关,但如果你有更多的线程,那么这是一个问题吗?

(此外,可能希望按类型将entites分组到队列中,以减少数据库中锁定conflits的数量。)

答案 3 :(得分:0)

我的方法如下:

  1. 使用可配置的线程数创建一个线程池。
  2. 保留实体ID的映射,并将每个ID与消息队列相关联。
  3. 当您收到消息时,请将其放入相应实体ID的队列中。
  4. 每个线程只会查看专用于它的实体ID(例如,创建一个初始化为此类Service(EntityID id)的类)。
  5. 让线程仅处理来自其专用实体id的队列的消息。
  6. 为给定的实体ID处理完所有消息后,从地图中删除id并退出该线程的循环。
  7. 如果线程池中有空间,则添加一个新线程来处理下一个可用的实体ID。
  8. 您必须管理当时无法处理的消息,包括消息处理失败的情况。创建积压的消息等

    如果您可以访问并发映射(无锁/无等待映射),那么您可以将多个读取器和写入器映射到映射,而无需锁定或等待。如果您无法获得并发映射,那么所有意外事件都将在地图上:每当您向地图中的队列添加消息或添加新的实体ID时,您必须将其锁定。最好的办法是将地图包装在一个结构中,该结构提供了适当锁定的读写方法。

    我认为您不会看到锁定对性能产生任何重大影响,但如果您确实开始看到一个,我建议您创建自己的无锁哈希映射:http://www.azulsystems.com/events/javaone_2007/2007_LockFreeHash.pdf

    实施这个系统不是一项基本的任务,所以把我的评论作为一般指导......工程师应该实施适用的想法。