WCF, MSMQ и независимые транзакции на 2 очереди - PullRequest
0 голосов
/ 26 ноября 2018

Я создал службу WCF, которая обрабатывает MSMQ, давайте назовем службу 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 должно быть отправлено сообщение с указанием статуса запроса.

В файле app.config QueueService я добавил:

  <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 голосов
/ 28 ноября 2018

Я думаю, вы можете исправить это, поместив вызов в клиент очереди журналов в область транзакции с установленным TransactionScopeOption.Suppress.Это заставит действие происходить вне внешней транзакции.

Что-то вроде:

using (var scope = new TransactionScope (TransactionScopeOption.Suppress))
{
    // Call to enqueue log message
}

Ваша теория имеет смысл.Поскольку вы используете транзакционные очереди, WCF обеспечивает согласованность транзакций, задействуя все в обработчике сообщений в транзакции DTC.Это, очевидно, включает в себя постановку в очередь сообщения регистрации и ожидаемое, если нежелательное, поведение.

...