正确的方式来托管*稳定的* WCF MSMQ Windows服务

时间:2009-09-24 14:39:05

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

我有一个使用MSMQ进行异步处理的应用程序。

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

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

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

任何代码建议或批评都是受欢迎的。显然我首先为谷歌做了这个,但是大多数例子都向我展示了我已经拥有的东西,并且我想让我的系统比那更强大。

目前我有这个:

(注意: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

        }
    }
}

3 个答案:

答案 0 :(得分:5)

我不确定为什么你的服务主机会挂起,但我绝对可以想到一些让它更可靠的事情:

  • 我确保挂钩到服务主机的Faulted event。这通常是一个认识到你需要重新生成主机的好地方。
  • 我通过在远程队列服务器上设置一个特殊的健康状态队列并让第二个自定义WCF服务侦听该队列,为服务自行设置了一种方法。然后我让服务主机定期向该队列发送消息并检查:

a)它可以成功发送它们

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

答案 1 :(得分:3)

底层WCF MSMQ侦听器可能在它到达您的代码之前抛出异常。这是令人沮丧的情况,因为它看起来没有任何反应,最糟糕的是你的消息被丢弃。打开服务配置文件中的WCF service tracing

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

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

  

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

答案 2 :(得分:1)

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

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

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

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


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