Sql 当目标队列为本地队列时,sys.conversation_端点是否可靠?

Sql 当目标队列为本地队列时,sys.conversation_端点是否可靠?,sql,sql-server,queue,service-broker,Sql,Sql Server,Queue,Service Broker,(有关解决方案,请参阅本原始帖子底部的“我的编辑”) 安装程序 我的Microsoft SQL Server 2005 Express数据库中有两个存储过程: WaitForMyMessage(@myName-NVARCHAR(50),@myMessage-NVARCHAR(最大)输出) ProvideMessage(@name-NVARCHAR(50),@message-NVARCHAR(MAX)) 我想阻止WaitForMyMessage(),直到有人用相应的名称调用ProvideMes

(有关解决方案,请参阅本原始帖子底部的“我的编辑”)

安装程序 我的Microsoft SQL Server 2005 Express数据库中有两个存储过程:

  • WaitForMyMessage(@myName-NVARCHAR(50),@myMessage-NVARCHAR(最大)输出)
  • ProvideMessage(@name-NVARCHAR(50),@message-NVARCHAR(MAX))
我想阻止
WaitForMyMessage()
,直到有人用相应的名称调用
ProvideMessage()
。如果有人已经使用该名称调用了
ProvideMessage()
,那么
WaitForMyMessage()
将立即返回所提供的值

我最初考虑用一个具有FIFO队列行为的简单表来实现这一点,但找不到阻止
INSERT
插入此表的方法。因此,
WaitForMyMessage()
必须进行轮询,这显然是不可接受的

问题1: 是否有一种有效的方法来阻止某条记录出现在表中?WAITFOR语句将非常棒,但SQL似乎不支持它用于查询(仅支持
延迟
时间
接收
)。但是像这样的事情会很好

e、 g:

-- It would be great is SQL supported this, but as far as I can tell it doesn't.
DECLARE @t TABLE (ans NVARCHAR(MAX));
WAITFOR (
  WITH A AS (
    SELECT TOP (1) * 
    FROM ProviderMessage A 
    WHERE ProviderMessage.Name = @myName
    ORDER BY A.ID
  )
  DELETE FROM A 
  OUTPUT deleted.ID INTO @t
);
SET @myMessage = (SELECT ans FROM @t);
因此,在有人将具有适当的
名称的记录插入
ProviderMessage
表之前,它将一直处于空闲状态,一旦发生这种情况,则该记录将被上述人员删除,同时检索其
字段以返回给调用者

其他想法 唉,我找不到Q1的答案,所以我继续使用ServiceBroker提供的实际消息队列来实现它。考虑到ServiceBroker的实力和影响力,这似乎有些过分了,但由于没有回答第一季度的问题,我不得不尝试一下。我将我的服务和简单队列定义如下:

CREATE QUEUE q1
CREATE SERVICE s1 ON QUEUE q1 ([DEFAULT])
然后,
WaitForMyMessage()
变成:

DECLARE @farHandle UNIQUEIDENTIFIER;
SET @farHandle = (
  SELECT FarHandle 
  FROM ProviderInfo 
  WHERE ProviderInfo.Name = @myName
);
WAITFOR (
  RECEIVE @myMessage = CONVERT(NVARCHAR(MAX), message_body) 
  FROM q1
  WHERE conversation_handle = @farHandle
);
ProvideMessage()
将发送消息,如下所示:

DECLARE @nearHandle UNIQUEIDENTIFIER;
SET @nearHandle = (
  SELECT NearHandle 
  FROM ProviderInfo 
  WHERE ProviderInfo.Name = @name
);
SEND ON CONVERSATION @nearHandle (@message)
-- Create the conversation
DECLARE @nearHandle UNIQUEIDENTIFIER; 
BEGIN DIALOG CONVERSATION @nearHandle 
FROM SERVICE s1 
TO SERVICE 's1' 
WITH ENCRYPTION = OFF; 

-- Queue an initialization message
SEND ON CONVERSATION @nearHandle ('');

-- Figure out the handle to the receiving side of this conversation
DECLARE @farHandle UNIQUEIDENTIFIER;
SET @farHandle = (
  SELECT conversation_handle 
  FROM sys.conversation_endpoints 
  WHERE conversation_id = (
    SELECT A.conversation_id 
    FROM sys.conversation_endpoints A 
    WHERE A.conversation_handle = @nearHandle 
  ) AND conversation_handle <> @nearHandle 
);

-- Get our initialization message out of the queue
DECLARE @unused TINYINT;
WAITFOR (
  RECEIVE @unused = status 
  FROM q1
  WHERE conversation_handle = @farHandle
);

-- Store both conversation handles, associated with this name
INSERT INTO ProviderInfo (Name, NearHandle, FarHandle)
 SELECT @name, @nearHandle, @farHandle
这一切都很好地工作,除了一件事:服务代理似乎不支持发现给定会话的近句柄和远句柄。我必须了解这两个过程,这样我就可以填充
ProviderInfo
表,以便两个过程都能私下进行通信

问题2: 如何获得新对话的近对话句柄和远对话句柄?现在我通过查询
sys.conversation\u端点来实现这一点,如下所示:

DECLARE @nearHandle UNIQUEIDENTIFIER;
SET @nearHandle = (
  SELECT NearHandle 
  FROM ProviderInfo 
  WHERE ProviderInfo.Name = @name
);
SEND ON CONVERSATION @nearHandle (@message)
-- Create the conversation
DECLARE @nearHandle UNIQUEIDENTIFIER; 
BEGIN DIALOG CONVERSATION @nearHandle 
FROM SERVICE s1 
TO SERVICE 's1' 
WITH ENCRYPTION = OFF; 

-- Queue an initialization message
SEND ON CONVERSATION @nearHandle ('');

-- Figure out the handle to the receiving side of this conversation
DECLARE @farHandle UNIQUEIDENTIFIER;
SET @farHandle = (
  SELECT conversation_handle 
  FROM sys.conversation_endpoints 
  WHERE conversation_id = (
    SELECT A.conversation_id 
    FROM sys.conversation_endpoints A 
    WHERE A.conversation_handle = @nearHandle 
  ) AND conversation_handle <> @nearHandle 
);

-- Get our initialization message out of the queue
DECLARE @unused TINYINT;
WAITFOR (
  RECEIVE @unused = status 
  FROM q1
  WHERE conversation_handle = @farHandle
);

-- Store both conversation handles, associated with this name
INSERT INTO ProviderInfo (Name, NearHandle, FarHandle)
 SELECT @name, @nearHandle, @farHandle
我们可以更简单(更有效)地做到这一点:


谢谢大家

如果您需要来回传递消息,ServiceBroker将为您提供良好的服务。在插入行之前,没有可阻止的内置功能,但是您可以使用
查询订阅
在查询结果集发生更改时通知您。这是建立在ServiceBroker体系结构之上的,我认为您需要从.NET而不是SQL设置订阅

回到ServiceBroker,如果它是本地的,您不必担心很多事情,比如路由等,所以您的案例是最简单的。传输队列只是需要发送的消息的保留区,无需担心

我认为让你绊倒的是,你不需要担心如何处理对话,因为你无论如何都能处理好

(1)在队列上设置消息接收者块:

declare @status tinyint, @far_handle uniqueidentifier, @myMessage nvarchar(max);

waitfor (
    receive @status = status, 
            @far_handle = conversation_handle, 
            @myMessage = CONVERT(NVARCHAR(MAX), message_body) 
    from q1
)
(2)从您的服务开始对话:

declare @near_handle uniqueidentifier

begin dialog conversation @near_handle
from service s1 to service 's1'
with encryption = off

send on conversation @near_handle ('hello')
现在,当您在(2)中开始对话时,您将获得连接端的对话句柄,然后您可以使用该句柄执行所需操作,即插入到表中,等等

在阻塞端,当消息到达时,您收集状态和消息正文,以及会话句柄,这是会话该端的句柄。您可以使用它来回复、存储、更新表行等

现在的问题是,因为一开始它接收时没有会话句柄,因为它没有会话句柄,当会话建立时,它应该把它的会话句柄放在接收的where子句中

receive @status = status, 
        @myMessage = CONVERT(NVARCHAR(MAX), message_body) 
from q1
where conversation_handle = @far_handle
否则,它将开始接收自己发送的消息。这是因为在同一队列中有一个服务在与自己对话。您可以通过使用两个服务相互交谈来解决此问题。这通常是一种更干净的方法

这基本上消除了转到
sys.conversation\u端点的需要,这实际上是用于会话管理的

此外,为了让对话干净地结束,你应该从双方结束对话。永远不要让自己陷入需要使用
结束与cleanup的对话的境地

要同时处理多个会话,可以使用名为
队列激活的ServiceBroker功能。如果你不需要同时处理它们,你就不需要这个。要使用激活,最好使用两个服务&队列

完整示例 (1)进行一些设置

create queue srcq
create service src on queue srcq([DEFAULT])
GO

create queue destq
create service dest on queue destq([DEFAULT])
GO
(2)创建一个过程来处理收到的消息

create procedure messageHandler as 

declare @far_handle uniqueidentifier,
        @message xml,
        @message_type nvarchar(256),
        @name varchar(32),
        @payload nvarchar(max),
        @handler varchar(128)

waitfor (
    receive @far_handle = conversation_handle, @message_type = message_type_name, @message = cast(message_body as xml) 
    from destq
)

if (@message_type = 'http://schemas.microsoft.com/SQL/ServiceBroker/Error')
    -- Deal with error
    exec dealWithError 
else if (@message_type = 'http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog') 
begin
    -- End the Conversation
    end conversation @far_handle; 
end 
else  
begin
    set @name = @message.value('(/xml/name)[1]', 'varchar(32)');
    set @payload = @message.value('(/xml/payload)[1]', 'nvarchar(max)');

    if (select ReceiverHandle from ProviderInfo where Name = @name) is null
        update ProviderInfo
            set ReceiverHandle = @far_handle
        where Name = @name;

    -- Now Process @name however you want to
    -- This basically creates a string, say 'bobHandler', and then executes it as an sp, passing it the payload
    set @handler = @name + 'Handler';
    exec @handler @payload; 
end 
GO
(3)为与名称“bob”关联的消息创建处理程序

create procedure bobHandler (@payload nvarchar(max))
as
    print 'hello'
GO
(4)将目标队列设置为使用激活

alter queue destq 
with activation (
    status = on,
    procedure_name = messageHandler,
    max_queue_readers = 10,
    execute as 'dbo'
)
GO
(5)在发件人上,启动对话,存储发送句柄,然后发送消息

declare @near_handle uniqueidentifier
begin dialog conversation @near_handle
from service src to service 'dest'
with encryption = off

-- Store this handle somewhere for future use
merge into ProviderInfo p
using (
    select 'bob' as Name, @near_handle as SenderHandle
) t on p.Name = t.Name
when matched then
    update set SenderHandle = t.SenderHandle, ReceiverHandle = null
when not matched then
    insert (Name, SenderHandle) values (t.Name, t.SenderHandle);

send on conversation @near_handle ('<xml><name>bob</name><payload>89237981273982173</payload></xml>')
GO

我认为,就好像您在谈论SQLServer环境中的消息交换一样,ServiceBroker无疑为您提供了解决方案。这不是所有任务和所有可能需求的完美解决方案,但它肯定会起作用

通常,您的需求需要详细说明,但要考虑mi
create procedure handleMessage (@name varchar(32))
as
    declare @far_handle uniqueidentifier,
        @message xml,
        @message_type nvarchar(256),
        @payload nvarchar(max),
        @handler varchar(128),
        @loop bit = 1

    while (@loop = 1)
    begin
        -- Wait for a handle with our name
        select @far_handle = conversation_handle
        from destq
        where cast(message_body as xml).value('(/xml/name)[1]', 'varchar(32)') = @name

        if (@far_handle is not null)
            set @loop = 0
        else
            waitfor delay '00:00:02'
    end

    set @loop = 1

    while (@loop = 1)
    begin
        waitfor (
            receive @message_type = message_type_name, @message = cast(message_body as xml) 
            from destq
            where conversation_handle = @far_handle
        )

        if (@message_type = 'http://schemas.microsoft.com/SQL/ServiceBroker/Error')
            -- Deal with error
            exec dealWithError
        else if (@message_type = 'http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog')
        begin
            -- End the Conversation
            end conversation @far_handle;

            --Exit
            set @loop = 0
        end
        else 
        begin
            set @payload = @message.value('(/xml/payload)[1]', 'nvarchar(max)');

            if (select ReceiverHandle from ProviderInfo where Name = @name) is null
                update ProviderInfo
                    set ReceiverHandle = @far_handle
                where Name = @name;

            -- Now Process @name however you want to
            -- This basically creates a string, say 'bobHandler', and then executes it as an sp, passing it the payload
            set @handler = @name + 'Handler';
            exec @handler @payload;
        end
    end
GO