Обработка сообщений о сбое

В этом разделе описан способ, которым приложение, использующее компонент Service Broker, может обнаружить опасное сообщение и удалить его из очереди, не полагаясь на автоматическое обнаружение опасных сообщений.

Компонент Service Broker обеспечивает автоматическое определение опасных сообщений. В процессе автоматического обнаружения опасных сообщений состояние очереди устанавливается в значение OFF, если транзакция, получающая сообщения из очереди, пять раз подвергается откату. Такая мера безопасности направлена против катастрофических сбоев, которые приложение не может определить программным образом. Однако в ходе регулярной обработки приложение не должно полагаться на эту защитную функцию. Поскольку при автоматическом обнаружении опасных сообщений очередь останавливается, эта функция фактически приостанавливает всю обработку, выполняемую приложением, пока не будет удалено опасное сообщение. Вместо этого необходимо реализовать в логике приложения обнаружение и удаление опасных сообщений.

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

  • Является ли предельное число ошибок достаточным в условиях данного приложения? В некоторых приложениях периодические перебои в обработке сообщений являются нормальной ситуацией. Например, в приложении для регистрации заказов служба, обрабатывающая заказ, может работать быстрее, чем служба, добавляющая новую запись клиента. В этом случае будет нормальной ситуация, когда заказ для нового клиента не будет обрабатываться немедленно. Приложение должно учитывать такую задержку при определении опасности сообщения. В службе может понадобиться разрешить несколько ошибок обработки перед удалением сообщения.

  • Может ли приложение быстро и достоверно проанализировать содержимое сообщения, чтобы обнаружить, что его не удастся успешно обработать? Если да, такой подход может оказаться более эффективным, чем подсчет числа неуспешных попыток обработки сообщения в программе. Например, если отчет о расходах не содержит имя сотрудника или его идентификатор, то отчет нельзя обработать. В этом случае немедленный вывод ошибки в ответ на сообщение, которое невозможно обработать, может стать более эффективным, чем попытка обработки сообщения. Также рассмотрите возможность проверки по другим признакам. Например, если идентификатор присутствует, но выходит за пределы диапазона назначенных номеров (например, является отрицательным), приложение может немедленно завершить диалог.

  • Следует ли удалять сообщение после любой ошибки? Если приложение обрабатывает большой объем сообщений, каждое из которых имеет малый полезный срок жизни, самым эффективным может оказаться немедленное удаление сообщения, приводящего к сбою операции. Например, если сообщение содержит отчет о состоянии от целевой службы, то служба вызывающей стороны может удалить пустой отчет о состоянии, зафиксировав получение сообщения и не выполняя его обработку. В этом случае диалог продолжается.

При принятии решения о способе обработки опасных сообщений в приложении следует ответить на следующие вопросы.

  • Должно ли приложение в случае ошибки заносить в журнал содержимое сообщения? Во многих случаях это необязательно, но для некоторых приложений сохранение содержимого сообщения может оказаться подходящим решением.

  • Должно ли приложение заносить в журнал другие сведения об ошибке? В некоторых случаях может понадобиться отслеживать другие данные о диалоге. Например, с помощью представления каталога sys.conversation_endpoints можно определить удаленный экземпляр компонента Service Broker, который стал источником опасного сообщения.

  • Должно ли приложение завершать диалог с ошибкой, или контракт для службы должен позволять приложению сообщать об ошибке без прекращения диалога? Для многих служб получение опасного сообщения означает, что задачу, описанную в контракте, невозможно выполнить. В этом случае приложение завершает диалог с ошибкой. В других случаях, несмотря на сбой одного сообщения, можно продолжить диалог. Например, служба, принимающая данные о товарах, доступных в складских помещениях, может получить сообщение с неизвестным артикулом. Вместо завершения диалога служба может сохранить такое сообщение в отдельной таблице, чтобы оператор позже мог его просмотреть.

Пример. Обнаружение опасного сообщения

В этом примере на языке Transact-SQL показана простая служба, в которой не сохраняется состояние, но применяется логика обработки опасных сообщений. Перед получением сообщения хранимая процедура сохраняет транзакцию. Если эта процедура не может обработать сообщение, выполняется откат транзакции до точки сохранения. В результате частичного отката сообщение возвращается в очередь, но для сообщения удерживается блокировка группы сообщений. Поскольку программа удерживает блокировку группы сообщений, она может обновить таблицу, хранящую список ошибочных сообщений, исключая возможность обработки сообщения другим агентом чтения очереди.

В следующем примере для приложения определяется хранимая процедура активации.

CREATE PROCEDURE ProcessExpenseReport
AS
BEGIN
  WHILE (1 = 1)
    BEGIN
      BEGIN TRANSACTION ;
      DECLARE @conversationHandle UNIQUEIDENTIFIER ;
      DECLARE @messageBody VARBINARY(MAX) ;
      DECLARE @messageTypeName NVARCHAR(256) ;

      SAVE TRANSACTION UndoReceive ;

        WAITFOR ( 
                  RECEIVE TOP(1)
                    @messageTypeName = message_type_name,
                    @messageBody = message_body,
                    @conversationHandle = conversation_handle
                    FROM ExpenseQueue
                 ), TIMEOUT 500 ;

        IF @@ROWCOUNT = 0
        BEGIN
          ROLLBACK TRANSACTION ;
          BREAK ;
        END ;

        -- Typical message processing loop: dispatch to a stored
        -- procedure based on the message type name.  End conversation
        -- with an error for unknown message types.

        -- Process expense report messages. If processing fails,
        -- roll back to the save point and track the failed message.

        IF (@messageTypeName =
              '//Adventure-Works.com/AccountsPayable/ExpenseReport')
          BEGIN
            DECLARE @expenseReport NVARCHAR(MAX) ;
            SET @expenseReport = CAST(@messageBody AS NVARCHAR(MAX)) ;
            EXEC AdventureWorks2008R2.dbo.AddExpenseReport
              @report = @expenseReport ;
            IF @@ERROR <> 0
             BEGIN
               ROLLBACK TRANSACTION UndoReceive ;
               EXEC TrackMessage @conversationHandle ;
             END ;
            ELSE
             BEGIN
               EXEC AdventureWorks2008R2.dbo.ClearMessageTracking
                 @conversationHandle ;
             END ;
           END ;
        ELSE

        -- For error messages and end dialog messages, end the
        -- conversation.

        IF (@messageTypeName =
              'https://schemas.microsoft.com/SQL/ServiceBroker/Error' OR
             @messageTypeName =
              'https://schemas.microsoft.com/SQL/ServiceBroker/EndDialog')
          BEGIN
            END CONVERSATION @conversationHandle ;
            EXEC dbo.ClearMessageTracking @conversationHandle ;
          END ;


         COMMIT TRANSACTION ;
    END ;
END ;

Хранимая процедура TrackMessage отслеживает количество раз, когда сообщение вызывало ошибку. Если ранее сообщение не вызывало ошибок, процедура вставляет новый счетчик для сообщения в таблицу ExpenseServiceFailedMessages. В противном случае процедура считывает из счетчика число ошибок, вызванных сообщением. Если значение счетчика меньше ранее заданного числа, процедура увеличивает счетчик на единицу. Если значение счетчика превышает заранее заданное число, процедура завершает диалог с ошибкой и удаляет из таблицы счетчик для этого диалога.

CREATE PROCEDURE TrackMessage
@conversationHandle uniqueidentifier
AS
BEGIN
  IF @conversationHandle IS NULL
    RETURN ;

  DECLARE @count INT ;
  SET @count = NULL ;
  SET @count = (SELECT count FROM dbo.ExpenseServiceFailedMessages
                  WHERE conversation_handle = @conversationHandle) ;

  IF @count IS NULL
    BEGIN
      INSERT INTO dbo.ExpenseServiceFailedMessages
        (count, conversation_handle)
        VALUES (1, @conversationHandle) ;
    END ;
  IF @count > 3
    BEGIN
      EXEC dbo.ClearMessageTracking @conversationHandle ;
      END CONVERSATION @conversationHandle
        WITH ERROR = 500
        DESCRIPTION = 'Unable to process message.' ;
    END ;
  ELSE
    BEGIN
      UPDATE dbo.ExpenseServiceFailedMessages
        SET count=count+1
        WHERE conversation_handle = @conversationHandle ;
    END ;
END ;
GO

Определение таблицы ExpenseServiceFailedMessages содержит только столбец conversation_handle и столбец count, как показано в следующем образце:

CREATE TABLE ExpenseServiceFailedMessages (
  conversation_handle uniqueidentifier PRIMARY KEY,
  count smallint
) ;

Процедура ClearMessageTracking удаляет счетчик для диалога из таблицы ExpenseServiceFailedMessages, как показано в следующем образце:

CREATE PROCEDURE ClearMessageTracking
  @conversationHandle uniqueidentifier
AS
BEGIN
   DELETE FROM dbo.ExpenseServiceFailedMessages
     WHERE conversation_handle = @conversationHandle ;
END ;
GO

Здесь описана намеренно упрощенная стратегия. Приемы из этого раздела следует использовать в качестве основы для построения приложения в соответствии с актуальными требованиями. Например, если приложение сохраняет сведения о состоянии, то более эффективным решением может оказаться включение отслеживаемых сведений об ошибочных сообщениях в таблицы состояния для приложения.

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

Если есть основания считать, что обработка сообщения может привести к сбою транзакции, для обработки ошибки можно использовать инструкции TRY и CATCH. Дополнительные сведения об обработке ошибок см. в разделе Обработка ошибок компонента Database Engine.