如何处理WCF的MSMQ绑定中的消息失败

时间:2008-09-17 11:15:21

标签: .net wcf msmq

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

这是一个简单的服务,它将Dto传递给我的服务方法,并且不期望响应。该消息放在MSMQ中,一旦被选中就插入数据库。

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

我尝试了以下两种方法:

  1. 抛出异常

    这会将消息放入死信队列中供手动阅读。我可以在我的服务开始时处理这个

  2. 在绑定

    上设置receiveRetryCount =“3”

    经过3次尝试 - 即时发生,这似乎将消息留在队列中,但是我的服务出错了。重新启动我的服务会重复此过程。

  3. 理想情况下,我想这样做:

    尝试处理消息

    • 如果此操作失败,请等待5分钟,然后重试。
    • 如果该过程失败3次,请将消息移至死信队列。
    • 重新启动服务会将死信队列中的所有邮件推送回队列,以便进行处理。

    我能实现吗?如果是这样的话? 你能否指出一些关于如何最好地利用WCF和MSMQ来获得我的特定场景的好文章。

    非常感谢任何帮助。谢谢!

    一些其他信息

    我在Windows XP和Windows Server 2003上使用MSMQ 3.0。 不幸的是,我不能使用针对MSMQ 4.0和Vista / 2008的内置毒药消息支持。

4 个答案:

答案 0 :(得分:14)

我认为使用MSMQ(仅适用于Vista),您可以这样做:

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

第一次呼叫失败后,WCF将立即重试ReceiveRetryCount次数。批处理失败后,将移动该邮件 到重试队列。在RetryCycleDelay分钟延迟之后,消息从重试队列移动到端点队列,并重试批处理。这将重复 MaxRetryCycle时间。如果全部失败,则根据可以移动的receiveErrorHandling处理该消息 (毒害队列),拒绝,丢弃或错误

顺便说一句关于WCF和MSMQ的好文章是来自Juval Lowy的Progammig WCF书的第9章

答案 1 :(得分:9)

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

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

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

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

老实说,在任何一种情况下,你都在看这里的一些“手动”工作,而WCF根本就没有覆盖它。

我最近一直致力于一个不同的项目,我需要明确控制重试的频率,我当前的解决方案是创建一组重试队列并在重试队列和主处理之间手动移动消息基于一组定时器和一些启发式的队列,只需使用原始的System.Messaging来处理MSMQ队列。它看起来工作得非常好,但如果你这样做会有一些问题。

答案 2 :(得分:4)

如果您正在使用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弹出消息时,它应该已由WCF创建离开队列并调用你的方法。

答案 3 :(得分:1)

不幸的是我被困在Windows XP和Windows Server 2003上,所以这对我来说不是一个选择。 - (我会在发布之后发现这个解决方案,在我的问题中重新澄清,并意识到我无法使用它)

我发现一个解决方案是设置一个自定义处理程序,它将我的消息移动到另一个队列或毒性队列并重新启动我的服务。 这对我来说似乎很疯狂。想象一下,我的Sql Server已关闭服务重启的频率。

所以我最终做的是允许Line出错并在队列中留下消息。 我还将一条致命的消息记录到我的系统日志记录服务中。 解决问题后,我重新启动服务,所有消息再次开始处理。

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

aogan,你对MSMQ 4.0有完美的答案,但不幸的是不适合我