共用方式為


處理有害訊息

本主題描述一個方法,即使用 Service Broker 的應用程式可以偵測有害訊息,並從佇列移除訊息,而不須仰賴有害訊息的自動偵測。

Service Broker 提供有害訊息的自動偵測。如果從佇列收到訊息的交易回復五次,則有害訊息自動偵測會將佇列狀態設定為 OFF。這個功能為應用程式無法以程式設計方式偵測的重大錯誤提供保護。不過,應用程式不應該仰賴這個功能來進行一般處理。因為有害訊息自動偵測會停止佇列,所以這個功能會有效地暫止應用程式的所有處理,直到移除有害訊息為止。相反的,應用程式應該將嘗試偵測和移除有害訊息,做為應用程式邏輯的一部分。

在本章節中所述的策略,是假設如果訊息失敗達某次數,即應予以移除。對於許多應用程式,這個假設是有效的。不過,在您的應用程式中使用此策略之前,請考慮下列問題:

  • 失敗計數對於您的應用程式而言是可靠的嗎?視您的應用程式而定,訊息偶爾失敗可能是正常的。例如,在訂單輸入應用程式中,處理訂單的服務可能會比加入新客戶記錄的服務,花較少的處理時間。在此情況下,無法立即處理新客戶的訂單可能是正常的。在判斷訊息是否為有害訊息時,應用程式需要說明延遲的原因。服務可能需在移除訊息之前先允許一些失敗。

  • 您的應用程式是否可以快速且可靠地檢查訊息內容,以偵測它是否永遠都不會成功?如果是這樣,比起計數程式無法處理訊息的次數,這是較好的策略。例如,未包含員工名稱或是員工識別碼的費用報表將無法處理。在此情況下,如果程式立即回應發生錯誤且無法處理的訊息,而不是嘗試處理訊息,程式可能會更有效率。請同時考慮其他的驗證。例如,如果有識別碼,但是落在指派號碼的範圍之外 (例如,負數),則應用程式可以立即結束交談。

  • 您是否應該在任何失敗之後移除訊息?如果應用程式處理大量的訊息,其中每個訊息的可用壽命有限,則立即移除任何造成作業失敗的訊息,可能會最有效率。例如,如果訊息提供來自目標服務的進度報表,起始服務可能會選擇認可接收,而不處理訊息,來捨棄空的進度報表。在此情況下,交談會繼續。

當您決定應用程式如何處理有害訊息時,請考慮下列問題:

  • 應用程式是否應該記錄訊息的失敗和內容?在許多情況下,都不需要這樣做。不過,對於某些應用程式而言,保留訊息內容可能是適當的。

  • 您的應用程式是否應該記錄失敗的其他資訊?在某些情況下,您可能需要追蹤交談的其他資訊。例如,您可以使用目錄檢視 sys.conversation_endpoints 來識別產生有害訊息的遠端 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 錯誤>。