代码之家  ›  专栏  ›  技术社区  ›  WebDude

如何处理针对WCF的msmq绑定中的消息失败

  •  17
  • WebDude  · 技术社区  · 16 年前

    我已经创建了一个WCF服务并正在使用netmsmqbinding绑定。

    这是一个简单的服务,它将一个DTO传递给我的服务方法,不需要响应。消息被放置在一个msmq中,一旦接收到,就插入到数据库中。

    确保没有数据丢失的最佳方法是什么?

    我尝试了以下两种方法:

    1. 引发异常

      这会将消息放入死信队列中,以便手动阅读。我可以在我的strvice启动时处理这个

    2. 在绑定上设置receiveretrycount=“3”

      在3次尝试之后——这种情况会立即发生,这似乎会将消息留在队列中,但会导致我的服务出错。重新启动我的服务会重复此过程。

    理想情况下,我希望执行以下操作:

    尝试处理消息

    • 如果失败,请等待5分钟,等待该消息,然后重试。
    • 如果该过程失败3次,请将消息移动到死信队列。
    • 重新启动服务将把死信队列中的所有消息推回到队列中,以便对其进行处理。

    我能做到吗?如果是这样怎么办? 你能给我指出任何一篇关于如何最好地利用WCF和msmq的文章吗?

    任何帮助都将不胜感激。谢谢!

    一些附加信息

    我正在Windows XP和Windows Server 2003上使用msmq 3.0。 不幸的是,我不能使用针对msmq 4.0和vista/2008的内置有害消息支持。

    4 回复  |  直到 16 年前
        1
  •  9
  •   tomasr    16 年前

    SDK中有一个示例可能对您的案例有用。基本上,它所做的就是将一个IErrorHandler实现附加到您的服务上,当WCF声明消息为“有害”时(即当所有配置的重试都已用尽时),该实现将捕获错误。示例所做的是将消息移动到另一个队列,然后重新启动与该消息关联的ServiceHost(因为当发现病毒消息时,它将出现故障)。

    这不是一个非常漂亮的样本,但它可能很有用。但是,有一些限制:

    1-如果您有多个与您的服务相关联的端点(即通过多个队列暴露),则无法知道中毒消息到达的队列。如果您只有一个队列,这不会是问题。我没有看到任何官方的解决方法,但我已经尝试了一种可能的替代方法,我在这里记录了这种方法: http://winterdom.com/weblog/2008/05/27/NetMSMQAndPoisonMessages.aspx

    2-一旦问题消息被移动到另一个队列,它就成为您的责任,所以在超时完成后,您就可以将它移回处理队列(或者在该队列上附加一个新服务来处理它)。

    老实说,在这两种情况下,你都在看一些“手工”工作,而WCF本身并没有涉及这些工作。

    我最近在一个不同的项目中工作,在这个项目中我需要明确地控制重试发生的频率,我当前的解决方案是创建一组重试队列,并根据一组计时器和一些启发式方法,仅使用原始系统在重试队列和主处理队列之间手动移动消息。用于处理msmq队列的内容。它看起来工作得很好,不过如果你走这条路的话,会有一些问题。

        2
  •  14
  •   John Sibly    15 年前

    我认为对于msmq(仅在Vista上可用),您可以这样做:

    <bindings>
        <netMsmqBinding>
            <binding name="PosionMessageHandling"
                 receiveRetryCount="3"
                 retryCycleDelay="00:05:00"
                 maxRetryCycles="3"
                 receiveErrorHandling="Move" />
        </netMsmqBinding>
    </bindings>
    

    在第一次调用失败后,wcf将立即重试receiveretrycount次。批处理失败后,将移动消息 到重试队列。在retrycycledelay分钟延迟后,消息从重试队列移到端点队列,然后重试批处理。这将重复 最大重试周期时间。如果所有失败,消息将根据可移动的接收错误处理进行处理。 (到中毒队列)、拒绝、丢弃或故障

    顺便说一句,关于wcf和msmq的好文章是juval lowy的progammig wcf书的第9章。

        3
  •  4
  •   community wiki 2 revs C. Lawrence Wenham    16 年前

    如果您使用的是SQL Server,那么应该使用分布式事务,因为msmq和SQL Server都支持它。所发生的是将数据库写入包装在TransactionScope块中,并仅在成功时调用scope.complete()。如果失败,那么当您的wcf方法返回时,消息将被放回队列中,以便再次尝试。下面是我使用的代码的修剪版本:

        [OperationBehavior(TransactionScopeRequired=true, TransactionAutoComplete=true)]
        public void InsertRecord(RecordType record)
        {
            try
            {
                using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
                {
                    SqlConnection InsertConnection = new SqlConnection(ConnectionString);
                    InsertConnection.Open();
    
                    // Insert statements go here
    
                    InsertConnection.Close();
    
                    // Vote to commit the transaction if there were no failures
                    scope.Complete();
                }
            }
            catch (Exception ex)
            {
                logger.WarnException(string.Format("Distributed transaction failure for {0}", 
                    Transaction.Current.TransactionInformation.DistributedIdentifier.ToString()),
                    ex);
            }
         }
    

    我通过将大量但已知数量的记录排队来测试这一点,让WCF启动许多线程来同时处理其中的许多记录(达到16个线程——一次从队列中发出16条消息),然后在操作过程中终止进程。当程序重新启动时,将从队列中读取消息,并再次进行处理,就像什么都没有发生一样。测试结束时,数据库是一致的,没有丢失的记录。

    分布式事务管理器具有环境存在性,当您创建TransactionScope的新实例时,它会自动搜索方法调用范围内的当前事务——当它从队列中弹出消息并调用您的方法时,WCF应该已经创建了该事务。

        4
  •  1
  •   WebDude    16 年前

    不幸的是,我在WindowsXP和WindowsServer2003上遇到了麻烦,所以这不是我的选择。-(我将重新澄清,在我的问题中,我发现这个解决方案后,张贴和意识到我不能使用它)

    我发现一个解决方案是设置一个自定义处理程序,将我的消息移动到另一个队列或中毒队列,然后重新启动我的服务。 我觉得这很疯狂。假设我的SQL Server关闭了,服务重新启动的频率是多少。

    所以我最后做的是允许线路出错并在队列中留下消息。 我还向我的系统日志记录服务记录了一条发生这种情况的致命消息。 一旦问题得到解决,我就重新启动服务,所有消息都会重新开始处理。

    我意识到重新处理此消息或任何其他消息都将失败,所以为什么需要将此消息和其他消息移动到另一个队列。我也可以停止我的服务,当一切按预期运行时重新启动。

    奥根,你对msmq 4.0有完美的答案,但不幸的是我没有。