2009-09-24 40 views
7

我有一个应用程序使用MSMQ进行某些事情的异步处理。托管*稳定* WCF 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 

     } 
    } 
} 

回答

5

我不知道为什么你的服务主机挂,但我可以肯定地想到一些事情,以使其更可靠:

  • 我会确保挂钩到服务主机的Faulted event。这通常是认识到你需要重新生成主机的好地方。
  • 我会为服务设置一个方法让自己ping通,方法是在远程队列服务器上有一个特殊的运行状况队列,并在队列上有第二个自定义的WCF服务。然后我会服务主机刚刚火的消息到该队列定期检查:

一),它可以成功地把他们和

b)该消息被拾起并处理在该队列上侦听的本地WCF健康服务。这可以用来检测一些可能的错误情况。

+0

故障事件看起来很方便。我假设你相信如果MSMQ服务器失败,它不会进入这个状态? (因为你在推荐“ping”服务) “ping”服务听起来像是测试服务器可用性的一个很好的解决方法,我只是认为WCF中会有内置的东西来处理这个问题。 无论如何,我会尝试Faulted事件,所以谢谢指出我的方向。 – David 2009-09-28 08:57:28

+0

说实话,我只是不知道,所以我推荐这两个选项:) – tomasr 2009-09-28 12:23:16

3

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

现在,当你运行你的服务时,它会跟踪并给你更多的细节。而不是通过XML紧张你的眼睛与MS服务跟踪查看器调出这个日志文件。

当我有这个问题,我是越来越“System.ServiceModel.ProtocolException”:

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

+0

感谢您的想法,我会打开追踪,并给它一个去。 – David 2009-09-28 08:52:50

1

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

如果您手动处理您的队列,您将能够清楚地看到&处理抛出的MessageQueueExceptions的情况。你将能够赶上MessageQueueErrorCodes,如QueueNotFound或MachineNotFound。

不幸的是这也意味着管理带毒邮件队列中,增加交易的处理,增加了超时时间到队列等所有的东西,WCF需要很好地为您服务。

使用WCF的主要好处是,你就可以使用WAS实例化一个Web应用程序,而不是一个连续运行的Windows服务。如果你没有利用这个好处,那么我不会看到WCF的任何好处 - 它只是将很多你需要触摸和看到的内容抽象出来。


正如一个侧面说明;也许你可以在将消息放入队列时检查服务器是否可用?如果服务器可以将消息放入队列中,那么侦听器将能够立即处理它们。