病毒消息处理

有害消息是一类已超出向应用程序传递的最大尝试次数的消息。 当基于队列的应用程序因错误而无法处理消息时,可能会出现这种情况。 为符合可靠性要求,排队的应用程序是在事务中接收消息的。 中止已接收某个排队消息的事务时,该消息仍会保留在队列中,这样当开始一个新事务时,将对该消息重试操作。 如果导致事务中止的问题未得到更正,则接收应用程序可能会陷入循环接收和中止同一消息,直到超过最大传递尝试次数,并导致病毒消息结果。

由于多种原因,消息可能会成为病毒消息。 最常见的原因与应用程序有关。 例如,如果应用程序从队列中读取消息并执行某些数据库处理,则应用程序可能无法锁定数据库,导致它中止事务。 由于数据库事务已中止,因此消息保留在队列中,这会导致应用程序再次重新读取消息,并再次尝试获取数据库上的锁。 如果消息包含无效信息,消息也可能变得有害。 例如,采购订单可能包含无效的客户编号。 在这些情况下,应用程序可能会自愿中止事务并强制消息成为有害消息。

在极少数情况下,消息可能无法调度到应用程序。 Windows Communication Foundation(WCF)层可能会发现消息存在问题,例如消息有错误的帧、附加到该消息的无效消息凭据或无效的操作标头。 在这些情况下,应用程序永远不会收到消息;但是,消息仍可能成为病毒消息,并手动处理。

处理有害消息

在 WCF 中,有毒消息处理提供了一种机制,使接收应用程序能够处理无法调度到应用程序的消息,或调度到应用程序但因应用程序特定原因无法被处理的消息。 在每个可用的队列绑定中使用以下属性配置有害消息处理:

  • ReceiveRetryCount。 一个整数值,指示从应用程序队列到应用程序重试传递消息的最大次数。 默认值为 5。 在立即重试修复问题(例如数据库上的临时死锁)的情况下,这已经足够了。

  • MaxRetryCycles。 一个整数值,指示最大重试周期数。 重试周期包括将消息从应用程序队列传输到重试子队列,并在可配置延迟后从重试子队列传回应用程序队列以重新尝试传递。 默认值为 2。 在 Windows Vista 上,消息最多尝试次数 (ReceiveRetryCount +1) * (MaxRetryCycles + 1) 次。 MaxRetryCycles 在 Windows Server 2003 和 Windows XP 上被忽略。

  • RetryCycleDelay。 重试周期之间的时间延迟。 默认值为 30 分钟。 MaxRetryCyclesRetryCycleDelay 一起提供了一种机制,旨在通过在固定延迟后重试来解决问题。 例如,这种机制可以处理 SQL Server 挂起的事务提交中锁定的行集。

  • ReceiveErrorHandling。 一个枚举,指示对在已尝试过最大重试次数后仍无法传递的消息所采取的操作。 这些值可以是 Fault、Drop、Reject 和 Move。 默认选项为“错误”。

  • 故障。 此选项会将导致 ServiceHost 出现故障的错误发送给侦听器。 必须先通过一些外部机制从应用程序队列中删除消息,然后应用程序才能继续处理队列中的消息。

  • 落。 此选项会删除病毒消息,并且消息永远不会传递到应用程序。 如果消息 TimeToLive 的属性此时已过期,则消息可能显示在发件人的死信队列中。 否则,消息不会显示在任何位置。 此选项指示用户尚未指定在消息丢失时要执行的操作。

  • 拒绝。 此选项仅在 Windows Vista 上可用。 选择此选项会指示消息队列 (MSMQ) 将否定确认发送回发送队列管理器,以说明应用程序无法接收该消息。 该消息会放入发送队列管理器的死信队列中。

  • 移动。 此选项仅在 Windows Vista 上可用。 这会将病毒消息移动到病毒消息队列,供以后由病毒消息处理应用程序进行处理。 病毒消息队列是应用程序队列的子队列。 病毒消息处理应用程序可以是从病毒队列中读取消息的 WCF 服务。 病毒队列是应用程序队列的子队列,其地址为 net.msmq://<machine-name>/applicationQueue,其中计算机名称是该队列所驻留的计算机的名称,应用程序队列是应用程序特定队列的名称。

以下是对邮件进行的最大传递尝试次数:

  • 对于 Windows Vista,请用 ((ReceiveRetryCount+1) * (MaxRetryCycles + 1)) 计算。

  • 对于 Windows Server 2003 和 Windows XP,请用 (ReceiveRetryCount + 1) 计算。

注释

对于成功传递的消息,不会再重试传递。

为了跟踪尝试读取消息的次数,Windows Vista 维护可计算中止次数的持久消息属性,以及计算消息在应用程序队列和子队列之间移动的次数的移动计数属性。 WCF 通道使用这些来计算接收重试计数和重试周期计数。 在 Windows Server 2003 和 Windows XP 上,中止计数由 WCF 通道保留在内存中,如果应用程序失败,则会重置。 此外,WCF 通道可以随时在内存中保留最多 256 条消息的中止计数。 如果已读取第 257 条消息,则会重置最早一条消息的中止计数。

中止计数和移动计数属性均可用于通过操作上下文进行的服务操作。 下面的代码示例演示如何访问它们。

MsmqMessageProperty mqProp = OperationContext.Current.IncomingMessageProperties[MsmqMessageProperty.Name] as MsmqMessageProperty;
Console.WriteLine($"Abort count: {mqProp.AbortCount} ");
Console.WriteLine($"Move count: {mqProp.MoveCount} ");
// code to submit purchase order ...

WCF 提供两种标准的队列绑定:

  • NetMsmqBinding。 适用于与其他 WCF 终结点执行基于队列的通信的 .NET Framework 绑定。

  • MsmqIntegrationBinding。 这种绑定适用于与现有消息队列应用程序之间的通信。

注释

可以根据 WCF 服务的要求更改这些绑定中的属性。 整个病毒消息处理机制是接收应用程序的本地机制。 除非接收应用程序最终停止并将负确认发送回发送方,否则该进程对发送应用程序不可见。 这种情况下,该消息会移动到发送方的死信队列中。

最佳方案:处理 MsmqPoisonMessageException

当服务确定某个消息是病毒消息时,排队传输会引发一个 MsmqPoisonMessageException,其中包含病毒消息的 LookupId

接收应用程序可以实现 IErrorHandler 接口来处理应用程序所需的任何错误。 有关详细信息,请参阅 扩展对错误处理和报告的控制

应用程序可能需要某种自动化处理机制,将有害消息移动到有害消息队列,以便服务能够访问队列中的其他消息。 使用错误处理程序机制侦听病毒消息异常的唯一方案是将 ReceiveErrorHandling 设置设置为 Fault。 消息队列 3.0 的病毒消息示例演示了此行为。 下面概述了处理有害消息的步骤,包括最佳做法:

  1. 确保您的病毒消息设置可以反映您的应用程序需求。 使用设置时,请确保了解 Windows Vista、Windows Server 2003 和 Windows XP 上的消息队列功能之间的差异。

  2. 如有必要,请实现 IErrorHandler 以处理病毒消息错误。 由于将ReceiveErrorHandling设置为Fault需要手动机制来将病毒消息移出队列或纠正外部依赖问题,因此通常的用法是在将IErrorHandler设置为ReceiveErrorHandling时实现Fault,如下面的代码所示。

    class PoisonErrorHandler : IErrorHandler
    {
        public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
        {
            // No-op -We are not interested in this. This is only useful if you want to send back a fault on the wire…not applicable for queues [one-way].
        }
    
        public bool HandleError(Exception error)
        {
            if (error != null && error.GetType() == typeof(MsmqPoisonMessageException))
            {
                Console.WriteLine($" Poisoned message -message look up id = {((MsmqPoisonMessageException)error).MessageLookupId}");
                return true;
            }
    
            return false;
        }
    }
    
  3. 创建服务行为可以使用的 PoisonBehaviorAttribute。 此行为将 IErrorHandler 安装到调度程序上。 请参阅以下代码示例:

    public class PoisonErrorBehaviorAttribute : Attribute, IServiceBehavior
    {
        Type errorHandlerType;
    
        public PoisonErrorBehaviorAttribute(Type errorHandlerType)
        {
            this.errorHandlerType = errorHandlerType;
        }
    
        void IServiceBehavior.Validate(ServiceDescription description, ServiceHostBase serviceHostBase)
        {
        }
    
        void IServiceBehavior.AddBindingParameters(ServiceDescription description, ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, BindingParameterCollection parameters)
        {
        }
    
        void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription description, ServiceHostBase serviceHostBase)
        {
            IErrorHandler errorHandler;
    
            try
            {
                errorHandler = (IErrorHandler)Activator.CreateInstance(errorHandlerType);
            }
            catch (MissingMethodException e)
            {
                throw new ArgumentException("The errorHandlerType specified in the PoisonErrorBehaviorAttribute constructor must have a public empty constructor", e);
            }
            catch (InvalidCastException e)
            {
                throw new ArgumentException("The errorHandlerType specified in the PoisonErrorBehaviorAttribute constructor must implement System.ServiceModel.Dispatcher.IErrorHandler", e);
            }
    
            foreach (ChannelDispatcherBase channelDispatcherBase in serviceHostBase.ChannelDispatchers)
            {
                ChannelDispatcher channelDispatcher = channelDispatcherBase as ChannelDispatcher;
                channelDispatcher.ErrorHandlers.Add(errorHandler);
            }
        }
    }
    
  4. 确保你的服务已使用病毒行为属性批注过。

此外,如果 ReceiveErrorHandling 设置为 Fault,那么 ServiceHost 在遇到毒化消息时会出错。 可以连接到故障事件并关闭服务、采取纠正措施并重启。 例如,传播到LookupIdMsmqPoisonMessageExceptionIErrorHandler可以被记录,当服务主机出错时,可以使用System.Messaging API 从队列中接收消息,并用LookupId将消息从队列中移除,然后将其存储在某个外部存储或另一个队列中。 然后可以重启 ServiceHost 以恢复正常处理。 MSMQ 4.0 中的有毒消息处理演示了此行为。

事务超时和病毒消息

排队传输通道和用户代码之间可能发生一类错误。 可以通过中间层(例如消息安全层或服务调度逻辑)来检测这些错误。 例如,无论是在 SOAP 安全层中检测到缺少 X.509 证书,还是缺少操作,都会导致不将消息调度到应用程序。 发生这种情况时,服务模型会删除消息。 由于消息是在事务中读取的,并且无法提供该事务的结果,因此事务最终会超时并中止,然后消息被放回队列中。 换句话说,对于某种类型的错误,事务不会立即中止,而是等待事务超时。您可以使用ServiceBehaviorAttribute修改服务的事务超时。

若要在计算机范围内更改事务超时,请修改 machine.config 文件并设置适当的事务超时。请务必注意,根据事务中的超时设置,事务最终会中止并返回到队列,其中止计数会递增。 最后,消息变为病毒消息,并按照用户的设置进行正确处理。

会话和病毒消息

会话遵循与单个消息相同的重试和有毒消息处理过程。 以前列出的病毒消息的属性适用于整个会话。 这意味着整个会话将会重试,并前进到最终病毒消息队列或发送方的死信队列(如果消息被拒绝)。

批处理和病毒消息

如果消息成为有害消息并且是批处理的一部分,则整个批处理会被回滚,通道一次只读取一条消息。 有关批处理的详细信息,请参阅 事务中的批处理消息

对病毒队列中的消息进行病毒消息处理

在病毒消息队列中放置消息时,病毒消息处理不会结束。 还必须读取和处理病毒消息队列中的消息。 当从最终病毒子队列读取消息时,可以使用病毒消息处理设置的子集。 适用的设置是 ReceiveRetryCountReceiveErrorHandling。 您可以将 ReceiveErrorHandling 设置为“删除”、“拒绝”或“错误”。 MaxRetryCycles 被忽略,如果 ReceiveErrorHandling 设置为 Move,则会引发异常。

Windows Vista、Windows Server 2003 和 Windows XP 差异

如前所述,并非所有病毒消息处理设置都适用于 Windows Server 2003 和 Windows XP。 Windows Server 2003、Windows XP 和 Windows Vista 上的消息队列之间的以下主要区别与病毒消息处理相关:

  • Windows Vista 中的消息队列支持子队列,而 Windows Server 2003 和 Windows XP 不支持子队列。 子队列用于病毒消息处理。 重试队列和病毒队列是基于病毒消息处理设置创建的应用程序队列的子队列。 MaxRetryCycles 决定要创建多少重试子队列。 因此,在 Windows Server 2003 或 Windows XP 上运行时, MaxRetryCycles 将被忽略且 ReceiveErrorHandling.Move 不允许。

  • Windows Vista 中的消息队列支持负确认,而 Windows Server 2003 和 Windows XP 则不支持。 接收队列管理器发送的否定确认会导致发送队列管理器将被拒绝的消息放入死信队列中。 因此,不允许在 Windows Server 2003 和 Windows XP 上使用 ReceiveErrorHandling.Reject

  • Windows Vista 中的消息队列支持一种消息属性,该属性可记录尝试传递消息的次数。 此中止计数属性在 Windows Server 2003 和 Windows XP 上不可用。 WCF 会在内存中维护中止计数,所以当场中的多个 WCF 服务读取同一消息时,此属性包含的值可能不精确。

另请参阅