Sql server SQL Message Broker在发送队列中保留消息

Sql server SQL Message Broker在发送队列中保留消息,sql-server,sql-server-2008,service-broker,Sql Server,Sql Server 2008,Service Broker,我们通过表上的触发器将消息放入SQL Server消息队列。(当一个字段被更新时,我们构建一些XML,并调用下面的触发器) 这很有效。消息被放置在队列中 然后将消息发送到同一服务器上的接收队列-不同的数据库。然后,我们每分钟运行一次proc,它从目标队列获取消息,并将其处理到一个暂存表中进行处理。然后消息将从目标队列中退出,这一切都可以正常工作 然而 当我检查消息来自的initiaitor队列时,它充满了消息 SELECT TOP 1000 *, casted_message_body = C

我们通过表上的触发器将消息放入SQL Server消息队列。(当一个字段被更新时,我们构建一些XML,并调用下面的触发器)

这很有效。消息被放置在队列中

然后将消息发送到同一服务器上的接收队列-不同的数据库。然后,我们每分钟运行一次proc,它从目标队列获取消息,并将其处理到一个暂存表中进行处理。然后消息将从目标队列中退出,这一切都可以正常工作

然而

当我检查消息来自的initiaitor队列时,它充满了消息

SELECT TOP 1000 *, casted_message_body = 
CASE message_type_name WHEN 'X' 
  THEN CAST(message_body AS NVARCHAR(MAX)) 
  ELSE message_body 
END 
FROM [ICMS].[dbo].[IcmsCarePayInitiatorQueue] WITH(NOLOCK)
我本以为当消息从发起方传递到目标方时,发起方就会消失。但它似乎正在被填满

我注意到发起程序中的消息的“message_type_id”为2,“validation”为“E”,消息体和强制转换的消息体为空。所有邮件的邮件类型名称均为“”

在目标数据库端,以下是用于从队列获取消息的过程:

CREATE PROCEDURE [dbo].[up_CarePayBrokerReceiveXml]   
AS
BEGIN  
  SET NOCOUNT ON;  

  DECLARE @XML XML, @Response XML = 'OK', @ConversationHandle UNIQUEIDENTIFIER, @message_type_name SYSNAME, @message_body VARBINARY(MAX), @source_table VARCHAR(100)
  DECLARE @Message VARCHAR(MAX), @Line INT, @Proc VARCHAR(MAX), @Exception VARCHAR(MAX)  

  WHILE ( 1 = 1 )
  BEGIN  
    -- Clear variables, as they may have been populated in previous loop.
    SET @message_type_name = NULL
    SET @message_body = NULL
    SET @ConversationHandle = NULL  
    SET @source_table = NULL

    BEGIN TRY 
      BEGIN TRAN

        WAITFOR (    -- Pop off a message at a time, and add to storage table.
           RECEIVE TOP (1) 
               @message_type_name = message_type_name  
             , @message_body = message_body  
             , @ConversationHandle = conversation_handle  
             , @source_table = CAST([message_body] AS XML).value('(/row/@SourceTable)[1]', 'varchar(50)')  
           FROM dbo.IcmsCarePayTargetQueue  
        ), TIMEOUT 3000;  

        IF @@ROWCOUNT = 0
        BEGIN  
          ROLLBACK  -- Complete the Transaction (Rollback, as opposeed to Commit, as there is nothing to commit).
          BREAK  
        END

        -- Code removed for example, but the fields are saved to a staging table in the database here...

         -- Respond to Initiator  
        SEND ON CONVERSATION @ConversationHandle MESSAGE TYPE [//IcmsCarePay/Message/Response](@Response);  
        END CONVERSATION @ConversationHandle;  

      COMMIT -- End of Transaction

    END TRY
    BEGIN CATCH
      -- End the conversation
      END CONVERSATION @ConversationHandle WITH CLEANUP  

      -- Get details about the issue.
      SELECT  @Exception = ERROR_MESSAGE(), @Line = ERROR_LINE(), @Proc = ERROR_PROCEDURE(), @Message = 'proc: ' + @Proc + '; line: ' + CAST(@Line AS VARCHAR) + '; msg: ' + @Exception  
      SELECT  @Message -- Displays on Concole when debugging.

      -- Log the issue to the Application Log.
      INSERT  INTO dbo.ApplicationLog
              ( LogDate ,
                Thread ,
                Level ,
                Logger ,
                Message ,
                Exception  
              )
      VALUES  ( GETDATE() , -- LogDate - datetime  
                'None' , -- Thread - varchar(255)  
                'FATAL' , -- Level - varchar(50)  
                '____up_CarePayBrokerReceiveXml' , -- Logger - varchar(255)  
                @Message , -- Message - varchar(4000)  
                @Exception  -- Exception - varchar(2000)  
              )  
      COMMIT -- We have stored the erronous message, and popped it off the queue. Commit these changes.
    END CATCH 
  END  -- end while  

END
为什么这些信息留在那里

保留在启动器队列中的消息的详细信息如下:

Status: 1
Priority: 5
queuing_order: 395
mess_sequence_number: 0
service_name: //IcmsCarePay/Service/Initiator
service_contract_name: //IcmsCarePay/Contract
message_type_name: http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog
message_type_id: 2
validation: E
message_body: NULL
casted_message_body: NULL

看起来您在这些对话中使用了一次性对话框。您的目标存储过程从目标队列检索消息,然后关闭它们的对话框,但您不在启动器队列上处理它

由于对话框是分布式的,为了关闭它,它必须在发起方和目标方都关闭。当你的目标进程发出
结束对话@Handle
在目标上,ServiceBroker将您提到的类型的消息发送给启动器,通知它此特定对话框已成为历史

如果操作正确,启动器激活程序将收到此消息,并在其一侧发出相应的
结束对话
,对话框关闭

由于您不在启动器端处理任何消息,因此这些系统消息会在那里累积

这里有两种可能的解决方案:

  • 处理
    EndDialog
    消息。这实际上应该在两侧进行,因为对话框可以在其任意一侧关闭
  • 重复使用对话框,这样您就不必在每次需要发送内容时都创建一个新的对话框。这将节省一些重要的资源,特别是如果流量足够大的话
  • 注意#1应该完成,不管您是使用持久对话框还是一次性对话框

    编辑:以下是一个默认处理过程的示例,取自我的一个项目:

    create procedure [dbo].[ssb_Queue_DefaultProcessor]
    (
        @Handle uniqueidentifier,
        @MessageType sysname,
        @Body xml,
        @ProcId int
    ) with execute as owner as
    
    set nocount, ansi_nulls, ansi_padding, ansi_warnings, concat_null_yields_null, quoted_identifier, arithabort on;
    set numeric_roundabort, xact_abort, implicit_transactions off;
    
    declare @Error int, @ErrorMessage nvarchar(2048);
    
    declare @Action varchar(20);
    
    begin try
    
    -- System stuff
    if @MessageType in (
        N'http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog',
        N'http://schemas.microsoft.com/SQL/ServiceBroker/Error'
        ) begin
    
        -- Depending on the actual message, action type will be different
        if @MessageType = N'http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog' begin
            set @Action = 'PURGE';
        end else if @MessageType = N'http://schemas.microsoft.com/SQL/ServiceBroker/Error'
            set @Action = 'CLOSE';
    
        -- Close the dialog
        exec dbo.ssb_DialogPools_Maintain @Action = @Action, @DialogHandle = @Handle, @Error = @Error output, @ErrorMessage = @ErrorMessage output;
    
        if nullif(@Error, 0) is not null
            throw 50000, @ErrorMessage, 1;
    
    end else
        -- Some unknown messages may end up here, log them
        throw 50011, 'Unknown message type has been passed into default processor.', 1;
    
    end try
    begin catch
    
    if nullif(@Error, 0) is null
        select @Error = error_number(), @ErrorMessage = error_message();
    
    -- Don't try to resend messages from default processing
    exec dbo.ssb_Poison_Log @ErrorNumber = @Error, @ErrorMessage = @ErrorMessage, @MessageType = @MessageType, @MessageBody = @Body, @ProcId = @ProcId;
    
    end catch;
    return;
    
    当它们遇到任何类型的消息而不是它们应该处理的消息时,所有激活过程都会调用它。 以下是此类激活程序之一的示例:

    create procedure [dbo].[ssb_QProcessor_Clients]
    with execute as owner as
    
    
    set nocount, ansi_nulls, ansi_padding, ansi_warnings, concat_null_yields_null, quoted_identifier, arithabort on;
    set numeric_roundabort, xact_abort, implicit_transactions off;
    
    declare @Handle uniqueidentifier, @MessageType sysname, @Body xml, @MessageTypeId int;
    declare @Error int, @ErrorMessage nvarchar(2048), @ProcId int = @@procid;
    declare @TS datetime2(4), @Diff int, @Delay datetime;
    
    
    -- Fast entry check for queue contents
    if not exists (select 0 from dbo.ssb_OY_Clients with (nolock))
        return;
    
    while exists (select 0 from sys.service_queues where name = 'ssb_OY_Clients' and is_receive_enabled = 1) begin
    
        begin try
        begin tran;
    
        -- Receive something, if any
        waitfor (
            receive top (1) @Handle = conversation_handle,
                @MessageType = message_type_name,
                @Body = message_body
            from dbo.ssb_OY_Clients
        ), timeout 3000;
    
        if @Handle is null begin
    
            -- Empty, get out
            rollback;
            break;
    
        end;
    
        -- Check for allowed message type
        select @MessageTypeId = mt.Id
        from dbo.ExportMessageTypes mt
            inner join dbo.ExportSystems xs on xs.Id = mt.ExportSystemId
        where mt.MessageTypeName = @MessageType
            and xs.Name = N'AUDIT.OY.Clients';
    
        if @MessageTypeId is not null begin
    
            -- Store the data
            exec dbo.log_Clients @MessageType = @MessageType, @Body = @Body, @Error = @Error output, @ErrorMessage = @ErrorMessage output;
    
            -- Check the result
            if nullif(@Error, 0) is not null
                throw 50000, @ErrorMessage, 1;
    
        end else
            -- Put it into default processor
            exec dbo.ssb_Queue_DefaultProcessor @Handle = @Handle, @MessageType = @MessageType, @Body = @Body, @ProcId = @ProcId;
    
        commit;
        end try
        begin catch
    
        if nullif(@Error, 0) is null
            select @Error = error_number(), @ErrorMessage = error_message();
    
        -- Check commitability of the transaction
        if xact_state() = -1
            rollback;
        else if xact_state() = 1
            commit;
    
        -- Try to resend the message again
        exec dbo.[ssb_Poison_Retry] @MessageType = @MessageType, @MessageBody = @Body, @ProcId = @ProcId, @ErrorNumber = @Error, @ErrorMessage = @ErrorMessage;
    
        end catch;
    
        -- Reset dialog handle
        select @Handle = null, @Error = null, @ErrorMessage = null;
    
    end;
    
    -- Done!
    return;
    

    当然,在这个例子中,它比您可能需要的要多一些,但是我希望一般的方法是显而易见的。您需要处理启动器和目标上的
    EndDialog
    Error
    消息类型,因为您永远不知道它们将出现在哪里。

    谢谢@RogerWolf-我想选择选项1。我不是100%确定你所说的“处理EndDialog消息”是什么意思。上面的过程需要这些代码吗?我已经修改了这个问题,以显示接收方的阅读过程。@Craig:不要在发送后立即结束发起者的对话。是一种错误的反模式,请参阅我建议您在
    IcmsCarePayInitiatorQueue
    上添加正确的消息处理。Craig,这意味着为发起程序队列创建一个(激活)存储过程,接收那些EndDialog消息,并为它们的对话框发出
    结束对话
    。我敦促你们听雷姆斯的话——对话应该在最后一方使用它们的时候关闭,在你们的情况下,它是启动器。另外,
    使用cleanup
    是一个非常危险的想法-您可能会突然意外地丢失数据。错误登录到一个单独的表中会是一个更好的选择。@Craig,刚刚更新了答案,以演示这个想法,但你已经很好地解决了,看起来:)
    create procedure [dbo].[ssb_QProcessor_Clients]
    with execute as owner as
    
    
    set nocount, ansi_nulls, ansi_padding, ansi_warnings, concat_null_yields_null, quoted_identifier, arithabort on;
    set numeric_roundabort, xact_abort, implicit_transactions off;
    
    declare @Handle uniqueidentifier, @MessageType sysname, @Body xml, @MessageTypeId int;
    declare @Error int, @ErrorMessage nvarchar(2048), @ProcId int = @@procid;
    declare @TS datetime2(4), @Diff int, @Delay datetime;
    
    
    -- Fast entry check for queue contents
    if not exists (select 0 from dbo.ssb_OY_Clients with (nolock))
        return;
    
    while exists (select 0 from sys.service_queues where name = 'ssb_OY_Clients' and is_receive_enabled = 1) begin
    
        begin try
        begin tran;
    
        -- Receive something, if any
        waitfor (
            receive top (1) @Handle = conversation_handle,
                @MessageType = message_type_name,
                @Body = message_body
            from dbo.ssb_OY_Clients
        ), timeout 3000;
    
        if @Handle is null begin
    
            -- Empty, get out
            rollback;
            break;
    
        end;
    
        -- Check for allowed message type
        select @MessageTypeId = mt.Id
        from dbo.ExportMessageTypes mt
            inner join dbo.ExportSystems xs on xs.Id = mt.ExportSystemId
        where mt.MessageTypeName = @MessageType
            and xs.Name = N'AUDIT.OY.Clients';
    
        if @MessageTypeId is not null begin
    
            -- Store the data
            exec dbo.log_Clients @MessageType = @MessageType, @Body = @Body, @Error = @Error output, @ErrorMessage = @ErrorMessage output;
    
            -- Check the result
            if nullif(@Error, 0) is not null
                throw 50000, @ErrorMessage, 1;
    
        end else
            -- Put it into default processor
            exec dbo.ssb_Queue_DefaultProcessor @Handle = @Handle, @MessageType = @MessageType, @Body = @Body, @ProcId = @ProcId;
    
        commit;
        end try
        begin catch
    
        if nullif(@Error, 0) is null
            select @Error = error_number(), @ErrorMessage = error_message();
    
        -- Check commitability of the transaction
        if xact_state() = -1
            rollback;
        else if xact_state() = 1
            commit;
    
        -- Try to resend the message again
        exec dbo.[ssb_Poison_Retry] @MessageType = @MessageType, @MessageBody = @Body, @ProcId = @ProcId, @ErrorNumber = @Error, @ErrorMessage = @ErrorMessage;
    
        end catch;
    
        -- Reset dialog handle
        select @Handle = null, @Error = null, @ErrorMessage = null;
    
    end;
    
    -- Done!
    return;