【问题标题】:Correct way to host a *stable* WCF MSMQ windows service托管 *stable* WCF MSMQ Windows 服务的正确方法
【发布时间】:2010-12-01 02:58:52
【问题描述】:

我有一个使用 MSMQ 异步处理某些事情的应用程序。

我使用 WCF 将消息放入队列,并有一个 WCF MSMQ 侦听器(一个 Windows 服务)来接收消息并处理它们。

我的问题是保持稳定。处理(例如)队列服务器(这是一个单独的盒子)宕机的正确方法是什么?前几天发生了这种情况,服务只是坐在那里 - 没有抛出异常,它只是停止接收消息。 我希望它在队列服务器出现故障时引发异常,然后重新尝试连接到它,直到它能够连接为止。

我还注意到,在服务上执行“停止”通常会导致它在最终停止之前挂起很长一段时间。

欢迎任何代码建议或批评。显然,我首先为此使用了 Google,但大多数示例向我展示了我已经拥有的东西,我想让我的系统比这更强大。

目前我有这个:

(注意:IMyExampleServiceContract 是我的 WCF 服务合约,而 QueueHandler 是实现它的对象)

namespace xyz.MyExample.MSMQListener
{
    /// <summary>
    /// The class that handles starting and stopping of the WCF MSMQ Listener windows service.
    /// It will respond to start and stop commands from within the windows services administration snap-in
    /// It creates a WCF NetMsmqBinding that watches a particular queue for messaages defined by a contract
    /// in the ServiceContracts project.
    /// </summary>
    public partial class MsmqListenerService : ServiceBase
    {
        /// <summary>
        /// The WCF service host
        /// </summary>
        private ServiceHost _serviceHost;

        /// <summary>
        /// Defines the maximum size for a WCF message
        /// </summary>
        private const long MaxMessageSize = 1024 * 1024 * 1024; // 1 gb
        /// <summary>
        /// Defines the maximum size for a WCF array
        /// </summary>
        private const int MaxArraySize = 1024 * 1024 * 1024; // 1 gb

        /// <summary>
        /// The queue name
        /// </summary>
        private readonly string _queueName;
        /// <summary>
        /// The queue server
        /// </summary>
        private readonly string _queueServer;

        /// <summary>
        /// Initializes a new instance of the <see cref="MsmqListenerService"/> class.
        /// </summary>
        public MsmqListenerService()
        {
            InitializeComponent();
            using (ConfigManager config = new ConfigManager())
            {
                _queueName = config.GetAppSetting("QueueName");
                _queueServer = config.GetAppSetting("QueueServer");
            }
        }

        /// <summary>
        /// When implemented in a derived class, executes when a Start command is sent to the service by the Service Control Manager (SCM) or when the operating system starts (for a service that starts automatically). Specifies actions to take when the service starts.
        /// <para>
        /// The logic in this method creates a WCF service host (i.e. something that listens for messages) using the <see cref="IMyExampleServiceContract"/> contract.
        /// The WCF end point is a NetMSMQBinding to the MyExample MSMQ server/queue.
        /// It sets up this end point and provides a class to handle the messages received on it.
        /// The NetMSMQBinding is a Microsoft WCF binding that handles serialisation of data to MSMQ. It is a ms proprietary format and means that the message on the queue
        /// can only be read by a WCF service with the correct contract information.
        /// </para>
        /// </summary>
        /// <param name="args">Data passed by the start command.</param>
        protected override void OnStart(string[] args)
        {
            try
            {
                Logger.Write("MyExample MSMQ listener service started.", StandardCategories.Information);

                Uri serviceUri = new Uri("net.msmq://" + QueueServer + QueueName);

                NetMsmqBinding serviceBinding = new NetMsmqBinding();
                serviceBinding.Security.Transport.MsmqAuthenticationMode = MsmqAuthenticationMode.None;
                serviceBinding.Security.Transport.MsmqProtectionLevel = System.Net.Security.ProtectionLevel.None;
                serviceBinding.MaxReceivedMessageSize = MaxMessageSize;
                serviceBinding.ReaderQuotas.MaxArrayLength = MaxArraySize;

                //QueueHandler implements IMyExampleServiceContract
                _serviceHost = new ServiceHost(typeof(QueueHandler));
                _serviceHost.AddServiceEndpoint(typeof(IMyExampleServiceContract), serviceBinding, serviceUri);

                _serviceHost.Open();
                Logger.Write("MyExample MSMQ listener service completed OnStart method.", StandardCategories.Information);
            }
            catch (Exception ex)
            {
                ExceptionReporting.ReportException(ex, "DefaultExceptionPolicy");
                throw;
            }
        }

        /// <summary>
        /// Gets the name of the queue to send to. 
        /// This is retrieved from the application settings under QueueName
        /// </summary>
        private string QueueName
        {
            get { return _queueName; }
        }

        /// <summary>
        /// Gets the name of the queue server to send to. 
        /// This is retrieved from the application settings under QueueServer
        /// </summary>
        private string QueueServer
        {
            get { return _queueServer; }
        }

        /// <summary>
        /// When implemented in a derived class, executes when a Stop command is sent to the service by the Service Control Manager (SCM). Specifies actions to take when a service stops running.
        /// </summary>
        protected override void OnStop()
        {
            if (_serviceHost != null)
            {
                _serviceHost.Close();
                _serviceHost = null;
            }
        }

        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        public static void Main()
        {
            //Code will have to be compiled in release mode to be installed as a windows service
            #if (!DEBUG)
                try
                {
                    Logger.Write("Attempting to start queue listener service.", StandardCategories.Information);
                    ServiceBase[] ServicesToRun;
                    ServicesToRun = new ServiceBase[]
                            {
                            new MsmqListenerService()
                            };
                    ServiceBase.Run(ServicesToRun);
                    Logger.Write("Finished ServiceBase.Run of queue listener service.", StandardCategories.Information);
                }
                catch (Exception e)
                {
                    ExceptionReporting.ReportException(e, "DefaultExceptionPolicy");
                    throw;
                }
            #else
                //This allows us to run from within visual studio
                MsmqListenerService service = new MsmqListenerService();
                service.OnStart(null);
                System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite);
            #endif

        }
    }
}

【问题讨论】:

    标签: c# .net wcf windows-services msmq


    【解决方案1】:

    我不确定您的服务主机为何挂起,但我绝对可以想出几件事来尝试使其更可靠:

    • 我会确保连接到服务主机的Faulted event。这通常是识别您需要重新启动主机的好地方。
    • 我为服务设置了一种 ping 自身的方法,方法是在远程队列服务器上设置一个特殊的健康状态队列,并在该队列上侦听第二个自定义 WCF 服务。然后我会让服务主机定期将消息发送到该队列并检查:

    a) 它可以成功发送它们并且

    b) 消息正在由本地 WCF 运行状况服务在该队列上侦听并拾取和处理。这可以用来检测一些可能的错误情况。

    【讨论】:

    • 故障事件看起来很方便。我假设您相信如果 MSMQ 服务器出现故障,它不会进入此状态? (正如您推荐“ping”服务)“ping”服务听起来是测试服务器可用性的好方法,我只是认为 WCF 中内置了一些东西可以为我处理这个问题。无论如何,我会尝试一下 Faulted 事件,感谢您为我指明方向。
    • 老实说,我只是不确定,因此我推荐这两个选项:)
    【解决方案2】:

    底层 WCF MSMQ 侦听器可能会在异常到达您的代码之前引发异常。这是一个令人沮丧的情况,因为看起来什么都没有发生,最糟糕的是你的消息被丢弃了。在您的服务配置文件中打开WCF service tracing

    现在,当您运行服务时,它会跟踪并为您提供更多详细信息。使用 MS 服务跟踪查看器打开此日志文件,而不是通过 XML 使您的眼睛紧张。

    当我遇到这个问题时,我得到了“System.ServiceModel.ProtocolException”:

    传入的 MSMQ 消息在其正文中包含无效或意外的 .NET 消息框架信息。无法接收消息。确保发送方使用具有匹配 SessionMode* 的兼容服务合同。我的服务合同已更改为具有属性 SessionMode = SessionMode.Required,但客户端没有发送带有事务的消息。

    【讨论】:

    • 感谢您的想法,我将打开跟踪并尝试一下。
    【解决方案3】:

    尽管 WCF 为 MSMQ 添加了一些很酷的功能,但有时如果您手动编写 MSMQ 处理代码,您将同样轻松地实现您的目标并且具有更大的控制力。

    如果您手动处理队列,您将能够准确地看到正在发生的事情并处理抛出的 MessageQueueExceptions,例如您将能够捕捉到MessageQueueErrorCodes,例如 QueueNotFound 或 MachineNotFound。

    不幸的是,这还意味着管理有害消息队列、向处理中添加事务、向队列中添加超时时间等等。WCF 可以很好地为您处理所有事情。

    使用 WCF 的主要好处是您可以使用 WAS 来实例化 Web 应用程序,而不是让 Windows 服务持续运行。如果你没有利用这个好处,那么我真的看不出 WCF 有什么好处——它只是抽象了很多你需要触摸和看到的东西。


    就像旁注一样;也许您可以在将消息放入队列时检查服务器是否可用?如果服务器可以将消息放入队列,那么侦听器将能够立即处理它们。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-08-06
      • 2012-04-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多