處理有害訊息
本主題描述一個方法,即使用 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 AdventureWorks.dbo.AddExpenseReport
@report = @expenseReport ;
IF @@ERROR <> 0
BEGIN
ROLLBACK TRANSACTION UndoReceive ;
EXEC TrackMessage @conversationHandle ;
END ;
ELSE
BEGIN
EXEC AdventureWorks.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 錯誤>。