WCF、MSMQ 和 2 个队列上的独立事务
WCF, MSMQ and independent transactions on 2 queues
我已经构建了一个处理 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>
一切正常。当数据库不可用时,回滚会导致队列条目放入我已配置为每 10 分钟重试 7 天的重试子队列中。它的一切都有效,并且已经投入生产 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,则对队列的写入也会回滚。
有什么方法可以在不同时回滚 LogService 事务的同时保留回滚 queueService 事务的能力?
我认为您可以通过在设置了 TransactionScopeOption.Suppress 的事务范围内包装对日志队列客户端的调用来解决此问题。这将强制操作在环境事务之外发生。
类似于:
using (var scope = new TransactionScope (TransactionScopeOption.Suppress))
{
// Call to enqueue log message
}
你的理论很有道理。因为您使用的是事务队列,所以 WCF 通过在 DTC 事务中的消息处理程序中登记所有内容来确保事务的一致性。这显然包括日志消息的排队,并且如果不需要的话,这是预期的行为。
我已经构建了一个处理 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>
一切正常。当数据库不可用时,回滚会导致队列条目放入我已配置为每 10 分钟重试 7 天的重试子队列中。它的一切都有效,并且已经投入生产 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,则对队列的写入也会回滚。
有什么方法可以在不同时回滚 LogService 事务的同时保留回滚 queueService 事务的能力?
我认为您可以通过在设置了 TransactionScopeOption.Suppress 的事务范围内包装对日志队列客户端的调用来解决此问题。这将强制操作在环境事务之外发生。
类似于:
using (var scope = new TransactionScope (TransactionScopeOption.Suppress))
{
// Call to enqueue log message
}
你的理论很有道理。因为您使用的是事务队列,所以 WCF 通过在 DTC 事务中的消息处理程序中登记所有内容来确保事务的一致性。这显然包括日志消息的排队,并且如果不需要的话,这是预期的行为。