在WCF应用程序中设置MSMQ以确保可靠的邮件传递

时间:2012-11-29 16:29:38

标签: wcf msmq

所以我的任务是设置MSMQ,这样如果我们的邮件服务器出现故障(这似乎经常发生),消息就会在队列中结束,并在他们重新启动时发送。有了这个说我不得不说除了我在过去24小时内学到的东西之外我对此并不多了但是我相信我知道我采取了正确的方法,但我想问社区中的某个人,因为有一些混乱我的同事在我们的WCF应用程序中给出了一些现有设置。

目前我们有一些使用msmq作为端点协议的服务。端点看起来像这样

<endpoint address="net.msmq://localhost/private/Publisher"
behaviorConfiguration="BatchBehaviour" 
binding="netMsmqBinding"
bindingConfiguration="MSMQNoSecurity"
contract="HumanArc.Compass.Shared.Publisher.Interfaces.Service.IPublisherSubscriber"
name="PublishSubscriber"/>

这当然可以让客户进行服务呼叫,如果由于某种原因服务没有启动,它将确保当服务恢复时,将处理呼叫。我不认为它会做的是你在服务方法中有类似的东西。

try
{
    smtp.Send(mail);
    return true;
}
catch (System.Net.Mail.SmtpFailedRecipientException ex)
{
     throw new Exception("User Credentials for sending the Email are Invalid",ex);
}
catch (System.Net.Mail.SmtpException smtpEx)
{
   throw new Exception(string.Format("Application encountered a problem send a mail message to {0} ", smtpHostName),smtpEx);
}

WCF不会重试并以某种方式再次发送消息,我对这个假设是否正确?

我认为我们应该拥有的内容类似于以下代替上面对smtp.send()的调用。 (来自http://www.bowu.org/it/microsoft/net/email-asp-net-mvc-msmq-2.html

  string queuePath = @".\private$\WebsiteEmails";
  MessageQueue msgQ;
  //if this queue doesn't exist we will create it
  if(!MessageQueue.Exists(queuePath))
       MessageQueue.Create(queuePath);
  msgQ = new MessageQueue(queuePath);
  msgQ.Formatter = new BinaryMessageFormatter();
  msgQ.Send(msg);

然后在服务启动的某个地方(我不确定在哪里)我们设置了一个事件处理程序,它实际上会调用SmtpClient对象上的send()。像这样的东西

 msgQ.ReceiveCompleted += new ReceiveCompletedEventHandler(msgQ_ReceiveCompleted)

总而言之,我的第一个问题是哪种方式更好?创建一个使用net:msmq作为协议的服务,或者只是更改电子邮件方法以将消息放入队列并为其设置处理程序?接下来的问题,如果我关于更改调用SmtpClient.Send()的方法的假设是正确的那么我应该在程序的哪个地方连接ReceiveCompleted? Out WCF服务托管在Windows服务中,这意味着实际上有一个对ServiceBase.Run(servicesToRun)的调用。有没有我可以把它连接起来的地方?我使用WCF的经验是使用更简单的IIS托管服务,所以我不是百分百确定。

谢谢 - 我意识到这是一个很长的问题,但我一直在努力研究它并且有很多信息,而且我似乎无法找到一种明确的解释,即以某种方式做事与另一种做法的好处。

1 个答案:

答案 0 :(得分:6)

使用msmq解决下游依赖项(在本例中为smtp服务器)的可用性的方法是有效的。但是,首先应该了解一些关于msmq的事情。

如果在msmq中创建队列,则默认情况下它是非事务性的。在此模式下,队列不会提供您需要的保证传递语义。因此,将您的队列创建为事务性的。

然后,您可以告诉WCF,当您收到要处理的消息时,您的服务操作将在事务中登记。您可以通过定义behavior on your service operation实现来执行此操作:

[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
public void SendEmail(Something mail)
{
    ....
    smtp.Send(mail);
}

TransactionScopeRequired告诉WCF服务操作应该在用于将消息从发送方传输到接收方的同一事务中登记。 TransactionAutoComplete表示一旦操作成功,服务方法应提交事务。因此,在回答上述查询时,服务操作中的失败将导致事务回滚。

此时发生的情况取决于您的service bindings configuration

<netMsmqBinding>
  <binding name="netMsmqBinding_IMyServiceInterface" 
           exactlyOnce="true" 
           maxRetryCycles="3" 
           retryCycleDelay="00:01:00" 
           receiveErrorHandling="Move"> <-- this defines behavior after failure
    ...
  </binding>
</netMsmqBinding> 

无论出于何种原因,事务未提交(例如,发生未处理的异常),WCF会将消息回滚到队列并每分钟重试一次,最多3次(由maxRetryCycles定义和retryCycleDelay)。

如果在此时间之后消息仍然处理失败,则receiveErrorHandling属性告诉WCF下一步该做什么(上述绑定指定将消息移动到系统病毒消息队列)。

注意:exactlyOnce告诉WCF我们需要进行交易,每条消息将按照发送顺序一次传送。

因此,您的原始方法实际上是正确的,您只需要正确配置服务即可实现所需的行为。