首页 > 解决方案 > 是否应该在循环中设置来自消息队列激活的过程?

问题描述

我正在尝试开始使用 SQL Server Service Broker 来异步审核 Intranet 应用程序的用户正在做什么。

我已经创建了消息、合同、队列和服务。设置在激活此队列时触发的过程。

到现在为止还挺好。正在发送和接收消息。

该过程从该队列接收 Top 1 消息并执行它需要做的事情(基本上是插入到表中)并退出。

我的问题:接收消息的过程是否需要无限循环?Queue的MAX_QUEUE_READERS设置为 2。这是否意味着无论队列中的消息数量如何,都将始终运行 2 个过程实例?

标签: sql-serversql-server-2016service-broker

解决方案


是的,最好在激活过程中有一个循环。简而言之,Service Broker 仅在新消息到达队列时调用您的激活过程。但是,如果过程已经在运行,并且 MAX_QUEUE_READERS 池已用尽,则它不能产生任何额外的处理线程。

因此,如果您的队列填充速度快于您的过程完成其工作的速度,您将开始看到未处理的消息开始在队列中累积。

另一件事是,调用过程会产生额外的成本,无论多么小。如果您尝试通过增加 MAX_QUEUE_READERS 值来缓解问题,最终您可能会开始注意到这种开销。那,它仍然不能保证所有消息都会被处理,并且不会被遗忘。

以下是此类过程的典型骨架结构,如果您要构建可靠、有弹性的系统,则应遵循此方法:

create procedure [dbo].[ssb_QProcessor_MyQueue]
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;
declare @Error int, @ErrorMessage nvarchar(2048), @ProcId int = @@procid;


-- Fast entry check for queue contents
if not exists (select 0 from dbo.MySBQueueName with (nolock))
    return;

while exists (select 0 from sys.service_queues where name = 'MySBQueueName' 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.MySBQueueName
    ), timeout 3000;

    if @Handle is null begin
        -- Empty, get out
        rollback;
        break;
    end;

    -- Whatever processing logic you have should be put here



    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 (up to you)
    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;

推荐阅读