You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Service Broker对话池性能优化问询:高并发卡顿问题求解

问题背景与现状
  • 多年前部署Service Broker(简称SB),约1年后引入对话池提升性能。
  • 当前消息量激增:100+数据源调用存储过程,峰值可达100万条/小时,系统逐渐卡顿,需重启SQL服务才能恢复;正常状态可处理80万条/小时,卡顿期间仅能处理5万条/小时。
  • 问题定位在DialogPool的访问阻塞或等待环节,但因多年未接触SB,遗忘相关细节,不知从何入手修复。
  • 曾考虑“150技巧”(仅使用每第150个对话以跨页存储),但未实现过,也不清楚在当前架构中如何应用。
  • 当前激活存储过程在32核服务器上运行30个线程。
  • 需求:找到无需每日多次重启SQL服务的解决方案;微软暂未给出有效建议,下一步计划将DialogPool改为内存优化表。

相关对象定义

初始存储过程:BundleParse_SvcBroker_INS

SET ANSI_NULLS ON
GO
CREATE  PROCEDURE [svcBroker].[BundleParse_SvcBroker_INS] (@Message XML)
AS
SET NOCOUNT ON
DECLARE @fromService sysname,
        @toService sysname,
        @onContract sysname,
        @messageType NVARCHAR(128),
        @messageBody NVARCHAR(MAX)
SET @fromService = 'DynamicParser\\my_InitiatorService'
SET @toService = 'DynamicParser\\my_TargetService'
SET @onContract = 'svcBroker_ods_claim_contract'
SET @messageType = 'svcBroker_ods_claim_request'
SET @messageBody = CONVERT(NVARCHAR(MAX), @message)
EXEC svcBroker.usp_send @fromService, @toService, @onContract, @messageType, @messageBody
GO

DialogPool表结构

(
[FromService] [sys].[sysname] NOT NULL,
[ToService] [sys].[sysname] NOT NULL,
[OnContract] [sys].[sysname] NOT NULL,
[Handle] [uniqueidentifier] NOT NULL,
[OwnerSPID] [int] NOT NULL,
[CreationTime] [datetime] NOT NULL,
[SendCount] [bigint] NOT NULL
) ON [PRIMARY]
GO
ALTER TABLE [svcBroker].[DialogPool] ADD CONSTRAINT [UQ__DialogPo__FE5BB31A4CC05EF3] UNIQUE NONCLUSTERED ([Handle]) ON [PRIMARY]
GO

发送消息存储过程:usp_send

SET QUOTED_IDENTIFIER ON
SET ANSI_NULLS ON
GO
CREATE PROCEDURE [svcBroker].[usp_send] (
      @fromService SYSNAME,
      @toService SYSNAME,
      @onContract SYSNAME,
      @messageType SYSNAME,
      @messageBody NVARCHAR(MAX))
AS
BEGIN
    SET NOCOUNT ON;
    DECLARE @dialogHandle UNIQUEIDENTIFIER;
    DECLARE @sendCount BIGINT;     
    DECLARE @counter INT;
    DECLARE @error INT;
    SELECT @counter = 1;
    BEGIN TRANSACTION;
        WHILE (1=1)
            BEGIN
                EXEC svcBroker.usp_get_dialog @fromService, @toService, @onContract, @dialogHandle OUTPUT, @sendCount OUTPUT;
                IF (@messageBody IS NOT NULL)
                BEGIN
                    SEND ON CONVERSATION @dialogHandle MESSAGE TYPE @messageType (@messageBody);
                END
                ELSE
                BEGIN
                    SEND ON CONVERSATION @dialogHandle MESSAGE TYPE @messageType;
                END
                SELECT @error = @@ERROR;
                IF @error = 0
                BEGIN
                    SET @sendCount = @sendCount + 1;
                    BREAK;
                END
                SELECT @counter = @counter+1;
                IF @counter > 10
                BEGIN
                    RAISERROR('Failed to SEND on a conversation for more than 10 times. Error %i.', 16, 1, @error) WITH LOG;
                    BREAK;
                END
                EXEC svcBroker.usp_delete_dialog @dialogHandle;
                SELECT @dialogHandle = NULL;
        END
        IF @sendCount > 1000
        BEGIN
            EXEC svcBroker.usp_delete_dialog @dialogHandle ;
            SEND ON CONVERSATION @dialogHandle MESSAGE TYPE [svcBroker_EndOfStream];
        END
        ELSE
        BEGIN
            EXEC svcBroker.usp_free_dialog @dialogHandle, @sendCount;
        END
    COMMIT
END;
GO

获取对话存储过程:usp_get_dialog

SET ANSI_NULLS ON
GO
CREATE PROCEDURE [svcBroker].[usp_get_dialog] (
      @fromService SYSNAME,
      @toService SYSNAME,
      @onContract SYSNAME,
      @dialogHandle UNIQUEIDENTIFIER OUTPUT,
      @sendCount BIGINT OUTPUT)
AS
BEGIN
    SET NOCOUNT ON;
    DECLARE @dialog TABLE (
            FromService SYSNAME NOT NULL,
            ToService SYSNAME NOT NULL,
            OnContract SYSNAME NOT NULL,
            Handle UNIQUEIDENTIFIER NOT NULL,
            OwnerSPID INT NOT NULL,
            CreationTime DATETIME NOT NULL,
            SendCount BIGINT NOT NULL);
    SET TRANSACTION ISOLATION LEVEL READ COMMITTED
    BEGIN TRANSACTION;
        DELETE @dialog;
        UPDATE  TOP(1) svcBroker.DialogPool WITH(READPAST)
        SET     OwnerSPID = @@SPID
        OUTPUT INSERTED.* INTO @dialog 
        WHERE   FromService = @fromService
        AND     ToService = @toService
        AND     OnContract = @OnContract
        AND     OwnerSPID = -1;
        IF @@ROWCOUNT > 0
        BEGIN
            SET @dialogHandle = (SELECT Handle FROM @dialog);
            SET @sendCount = (SELECT SendCount FROM @dialog);          
        END
        ELSE
        BEGIN
            BEGIN DIALOG CONVERSATION @dialogHandle
            FROM SERVICE @fromService
            TO SERVICE @toService
            ON CONTRACT @onContract
            WITH ENCRYPTION = OFF;
            INSERT  INTO svcBroker.DialogPool (
                    FromService, ToService, OnContract, Handle, OwnerSPID, CreationTime, SendCount)
            VALUES (@fromService, @toService, @onContract, @dialogHandle, @@SPID, GETDATE(), 0);
            SET @sendCount = 0;
        END
    COMMIT
END;
GO

删除对话存储过程:usp_delete_dialog

SET ANSI_NULLS ON
GO
CREATE PROCEDURE [svcBroker].[usp_delete_dialog] (@dialogHandle UNIQUEIDENTIFIER)
AS
BEGIN
      SET NOCOUNT ON;
      BEGIN TRANSACTION;
      DELETE svcBroker.DialogPool WHERE Handle = @dialogHandle;
      COMMIT
END;
GO

激活存储过程:ODS_TargetQueue_Receive

SET QUOTED_IDENTIFIER ON
SET ANSI_NULLS ON
GO
CREATE PROCEDURE [svcBroker].[ODS_TargetQueue_Receive]
AS
BEGIN
    set nocount on
    DECLARE @receive_table TABLE(
            queuing_order BIGINT,
            conversation_handle UNIQUEIDENTIFIER,
            message_type_name SYSNAME,
            message_body xml);
    DECLARE message_cursor CURSOR LOCAL FORWARD_ONLY READ_ONLY
            FOR SELECT
            conversation_handle,
            message_type_name,
            message_body
            FROM @receive_table ORDER BY queuing_order;
     DECLARE @conversation_handle UNIQUEIDENTIFIER;
     DECLARE @message_type SYSNAME;
     DECLARE @message_body xml;
     DECLARE @error_number INT;
     DECLARE @error_message VARCHAR(4000);
     DECLARE @error_severity INT;
     DECLARE @error_state INT;
     DECLARE @error_procedure SYSNAME;
     DECLARE @error_line INT;
     DECLARE @error_dialog VARCHAR(50);
     BEGIN TRY
       WHILE (1 = 1)
       BEGIN
         BEGIN TRANSACTION;
         WAITFOR (
            RECEIVE TOP (1000)
               [queuing_order],
               [conversation_handle],
               [message_type_name],
               convert(xml, [message_body])
            FROM svcBroker.ODS_TargetQueue
            INTO @receive_table
         ), TIMEOUT 2000;
         IF @@ROWCOUNT = 0
         BEGIN
              COMMIT;
              BREAK;
         END
         ELSE
         BEGIN
              OPEN message_cursor;
              WHILE (1=1)
              BEGIN
                  FETCH NEXT FROM message_cursor
                            INTO @conversation_handle,
                                 @message_type,
                                 @message_body;
                  IF (@@FETCH_STATUS != 0) BREAK;
                  BEGIN TRY
                      IF @message_type = 'svcBroker_ods_claim_request'
                      BEGIN
                          exec ParseMessages @message_body
                      END
                      ELSE IF @message_type in ('svcBroker_EndOfStream', 'http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog')
                      BEGIN
                          END CONVERSATION @conversation_handle;
                      END
                      ELSE IF @message_type = 'http://schemas.microsoft.com/SQL/ServiceBroker/Error'
                      BEGIN
                           WITH XMLNAMESPACES ('http://schemas.microsoft.com/SQL/ServiceBroker/Error' AS ssb)
                           SELECT
                           @error_number = CAST(@message_body AS XML).value('(//ssb:Error/ssb:Code)[1]', 'INT'),
                           @error_message = CAST(@message_body AS XML).value('(//ssb:Error/ssb:Description)[1]', 'VARCHAR(4000)');
                           SET @error_dialog = CAST(@conversation_handle AS VARCHAR(50));
                           RAISERROR('Error in dialog %s: %s (%i)', 16, 1, @error_dialog, @error_message, @error_number);
                           END CONVERSATION @conversation_handle;
                      END
                  END TRY
                  BEGIN CATCH
                     SET @error_number = ERROR_NUMBER();
                     SET @error_message = ERROR_MESSAGE();
                     SET @error_severity = ERROR_SEVERITY();
                     SET @error_state = ERROR_STATE();
                     SET @error_procedure = ERROR_PROCEDURE();
                     SET @error_line = ERROR_LINE();
                     IF XACT_STATE() = -1
                     BEGIN
                          ROLLBACK TRANSACTION;
                          BEGIN TRANSACTION;
                            INSERT  INTO svcBroker.target_processing_errors (
                                    error_conversation,[error_number],[error_message],[error_severity],
                                    [error_state],[error_procedure],[error_line],[doomed_transaction],
                                    [message_body])
                            VALUES  (NULL, @error_number, @error_message,@error_severity,
                                    @error_state, @error_procedure, @error_line, 1, @message_body);
                          COMMIT;
                          RAISERROR ('Message processing error', 16, 1);
                     END
                     ELSE IF XACT_STATE() = 1
                     BEGIN
                            INSERT  INTO svcBroker.target_processing_errors (
                                    error_conversation,[error_number],[error_message],[error_severity],
                                    [error_state],[error_procedure],[error_line],[doomed_transaction],
                                    [message_body])
                            VALUES  (NULL, @error_number, @error_message,@error_severity,
                                    @error_state, @error_procedure, @error_line, 0, @message_body);
                     END
                  END CATCH
              END
              CLOSE message_cursor;
              DELETE @receive_table;
         END
         COMMIT;
       END
     END TRY
     BEGIN CATCH
         SET @error_number = ERROR_NUMBER();
         SET @error_message = ERROR_MESSAGE();
         SET @error_severity = ERROR_SEVERITY();
         SET @error_state = ERROR_STATE();
         SET @error_procedure = ERROR_PROCEDURE();
         SET @error_line = ERROR_LINE();
         IF XACT_STATE() = -1
         BEGIN
              ROLLBACK TRANSACTION;
              BEGIN TRANSACTION;
              INSERT INTO svcBroker.target_processing_errors (
                    error_conversation,[error_number],[error_message],[error_severity],
                    [error_state],[error_procedure],[error_line],[doomed_transaction],
                    [message_body])
              VALUES(NULL, @error_number, @error_message,@error_severity, @error_state, @error_procedure, @error_line, 1, @message_body);
              COMMIT;
         END
         ELSE IF XACT_STATE() = 1
         BEGIN
              INSERT INTO svcBroker.target_processing_errors (
                    error_conversation,[error_number],[error_message],[error_severity],
                    [error_state],[error_procedure],[error_line],[doomed_transaction],
                    [message_body])
              VALUES(NULL, @error_number, @error_message, @error_severity, @error_state, @error_procedure, @error_line, 0, @message_body);
              COMMIT;
         END
    END CATCH
END;
GO

注:ParseMessages存储过程仅用于处理消息内容,不含任何Service Broker相关代码。

内容的提问来源于stack exchange,提问作者mbourgon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 07:17:04