3

因此,我的任务是设置 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 托管服务,所以我不能 100% 确定。

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

4

1 回答 1

6

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

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

然后,您可以告诉 WCF,您的服务操作将在接收到要处理的消息时加入事务。您可以通过在服务操作实现上定义行为来做到这一点:

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

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

此时会发生什么取决于您的服务绑定配置

<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 我们需要事务,每条消息都将按发送顺序仅发送一次。

所以你原来的方法实际上是正确的,你只需要正确配置你的服务来实现你想要的行为。

于 2012-11-29T21:02:20.083 回答