WCF,MSMQ和2个队列上的独立事务

时间:2018-11-26 19:23:10

标签: wcf transactions msmq

我已经构建了一个处理MSMQ的WCF服务,我们称之为服务QueueService。合同看起来像这样:

// Each call to the service will be dispatched separately, not grouped into sessions.
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class QueueServiceContract : IQueueServiceContract
{
    [OperationBehavior(TransactionScopeRequired = true)]
    public void QueueItems(List<Item> items)     // really should be called 'HandleQueueItems
    {
        //  Early in the processing I do:
        Transaction qTransaction = Transaction.Current;
        ...
        // I then check if the destination database is available.
        if(DB.IsAvailable)
            ... process the data
        else
            qTransaction.Rollback;
        ...
}

IQueueServiceContract看起来像这样:

// The contract will be session-less.  Each post to the queue from the client will create a single message on the queue.
[ServiceContract(SessionMode = SessionMode.NotAllowed, Namespace = "MyWebService")]
public interface IQueueServiceContract
{
    [OperationContract(IsOneWay = true)]
    void QueueItems(List<Item> items);
}

队列服务的App.config的相关部分如下所示。

<services>
  <service name="QueueService.QueueServiceContract">
    <endpoint address="net.msmq://localhost/private/MyQueueServiceQueue" binding="netMsmqBinding" contract="QueueService.IQueueServiceContract">
      <identity>
        <dns value="localhost" />
      </identity>
    </endpoint>
...
  <netMsmqBinding>
    <binding exactlyOnce="true" maxRetryCycles="1000" receiveRetryCount="1"
      retryCycleDelay="00:10:00" timeToLive="7.00:00:00" useActiveDirectory="false">
    </binding>
  </netMsmqBinding>

一切正常。当数据库不可用时,回滚会使队列条目放入我配置为每7分钟每10分钟重试一次的retry子队列中。一切正常,并且已经投入生产了6个月左右。

现在,我正在向服务添加日志记录。 QueueService会将日志条目排队到另一个我们称为的队列中:LogQueue。要求是无论是否回滚qTransaction,都应该向LogQueue发送一条消息,指示请求的状态。

在QueueService app.config中,我添加了:

  <client>
  <endpoint address="net.msmq://localhost/private/MyLogQueue"
    binding="netMsmqBinding" bindingConfiguration="NetMsmqBinding_ILogContract"
    contract="LogServiceReference.ILogContract" name="NetMsmqBinding_ILogContract">
    <identity>
      <dns value="localhost" />
    </identity>
  </endpoint>
</client>
...
    <binding name="NetMsmqBinding_ILogContract" timeToLive="7.00:00:00">
      <security mode="None" />
    </binding>

在LogService app.config中,我具有:

    <service name="LogService.LogContract">
    <endpoint address="net.msmq://localhost/private/MyLogQueue" binding="netMsmqBinding" contract="LogService.ILogContract">
      <identity>
        <dns value="localhost" />
      </identity>
    </endpoint>
... 
  <netMsmqBinding>
    <binding exactlyOnce="true" maxRetryCycles="1000" receiveRetryCount="1" retryCycleDelay="00:10:00" timeToLive="7.00:00:00"  useActiveDirectory="false">
    </binding>
  </netMsmqBinding>
...

然后,在QueueItems方法的末尾执行以下操作:

LogContractClient proxy = new LogContractClient();
proxy.LogTransaction(myLoggingInformation);             // This queues myLoggingInformation to the LogQueue.

这一切也都可以正常工作...直到...数据库不可用并且事务被回滚。

回滚将发生在调用proxy.LogTransaction之前,我将得到:

System.ServiceModel.CommunicationException: 'An error occurred while sending to the queue: The transaction specified cannot be enlisted. (-1072824232, 0xc00e0058).Ensure that MSMQ is installed and running. If you are sending to a local queue, ensure the queue exists with the required access mode and authorization.'

如果将proxy.LogTransaction移至qTransaction.Rollback之前,则日志条目永远不会放入LogQueue。

我的工作原理是WCF考虑对两个队列的操作:从QueueService队列读取并写入LogQueue,作为单个事务。因此,如果在回滚后尝试写入LogQueue,则事务已经结束,但是如果在调用回滚之前对LogQueue进行写入,则对队列的写入也会回滚。

有什么方法可以保留回滚queueService事务而不同时回滚LogService事务的能力?

1 个答案:

答案 0 :(得分:1)

我认为您可以通过将调用包装到具有TransactionScopeOption.Suppress设置的事务范围中的日志队列客户端来解决此问题。这将迫使该动作发生在环境事务之外。

类似的东西:

 db.getCollection('emps').aggregate([
       {$group: {_id: null, Sal: {$avg: "$Sal"}}}

 ]).forEach(function(dd){
        db.getCollection('emps').find({Sal: {$gte: dd.Sal}}, {Ename: 1, _id: -1}).forEach(function(emp_name){print(emp_name)})
})

您的理论很有意义。因为您使用的是事务队列,所以WCF通过在DTC事务中征服消息处理程序内的所有内容来确保事务一致性。显然,这包括日志消息的入队,并且如果不希望出现的话,则是预期的行为。