0

(解決策については、この元の投稿の下部にある私の編集を参照してください)

設定

Microsoft SQL Server2005Expressデータベースに2つのストアドプロシージャがあります。

  • WaitForMyMessage(@myName NVARCHAR(50), @myMessage NVARCHAR(MAX) OUTPUT)
  • ProvideMessage(@name NVARCHAR(50), @message NVARCHAR(MAX))

誰かが対応する名前でWaitForMyMessage()電話をかけるまでブロックしたい。ProvideMessage()誰かがすでにProvideMessage()その名前で電話をかけていた場合WaitForMyMessage()は、提供された値ですぐに戻ってきます。

私は当初、FIFOキューの動作を備えた単純なテーブルでこれを実装することを検討しましたが、このテーブルにブロックする方法を見つけることができませんでしたINSERT。したがってWaitForMyMessage()、ポーリングする必要があり、それは明らかな理由で受け入れられませんでした。

Q1:

特定のレコードがテーブルに表示されるまでブロックする効率的な方法はありますか?このWAITFORステートメントは素晴らしいものですが、SQLはクエリに対してそれをサポートしていないようです(、、、またはのみをサポートしていDELAYますTIMERECEIVE。しかし、それのようなものは素晴らしいでしょう、

例えば:

-- 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);

Nameしたがって、誰かが適切なレコードをテーブルに挿入するまでアイドル状態にProviderMessageなり、それが発生するとすぐに、そのレコードは上記によって削除されValue、呼び出し元に返されるフィールドが取得されます。

その他のアイデア

残念ながら、 Q1の回答が見つからなかったため、ServiceBrokerが提供する実際のメッセージキューを使用してこれを実装しました。Service Brokerのパワーとリーチを考えると、これはやり過ぎのように見えましたが、 Q1に対する回答がなかったため、試してみる必要がありました。サービスと単純なキューを次のように定義しました。

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)

これはすべて完全に機能しますが、1つだけ例外があります。特定の会話のニアハンドルとファーハンドルの両方を検出することは、ServiceBrokerではサポートされていないようです。両方の手順でプライベートに通信するためにテーブルにデータを入力できるように、両方を知っている必要があります。ProviderInfo

Q2:

新しい会話の近距離会話ハンドルと遠距離会話ハンドルの両方を取得するにはどうすればよいですか?sys.conversation_endpoints現在、次のよう にクエリを実行してこれを行っています。

-- 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

しかし、分散サービスなどを含むはるかに複雑なシナリオをサポートするように設計されたService Brokerアーキテクチャでは、メッセージがsys.transmission_queueローカルに配置される可能性があり、その他の実装が複雑であるため、私のアプローチが本番環境に十分に堅牢であるとは確信していません。 。

それで、私がそれをしている方法が頑強でないならば、「正しい」方法はありますか?会話グループを使用してこの必要性を回避しようと考えましたが、本質的に同じ問題(会話グループIDも相手側に伝達されない)のために解決できませんでした。私が見つけたこれらのトピックは解決策を提供しません。また:

結論

これを機能させるためのハードルは、このように使用されることを意図していないことを心配させます。そのため、特定の本番シナリオでは機能しないか、将来的にサポートされなくなる可能性があります。誰かがこの方法が信頼できることを示すドキュメントを提供できますか、または信頼性があり、それでも効率的な代替ソリューション(Service Brokerの有無にかかわらず)を提供できますか?

ありがとう!

編集:(解決策)

ここでの中心的な質問はQ2でした(新しい会話の近距離会話ハンドルと遠距離会話ハンドルの両方を取得するにはどうすればよいですか?)。

何人かの寄稿者の良いアイデアのおかげで、明らかになった(そして今では明らかなようです!)答えは、初期化メッセージのintのSELECT直後にキュー自体からingするだけで、遠いハンドルを取得することです。キュー列!SENDSELECT

したがって、元の投稿のようにこれを行う代わりに、次のようにします。

-- 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
...

これをはるかに簡単に(そして効率的に!)行うことができます:

-- Queue an initialization message with a unique identifier
DECLARE @initbin VARBINARY(36);
SET @initbin = CONVERT(VARBINARY(32), @nearHandle);
SEND ON CONVERSATION @nearHandle (@initbin);

-- Figure out the handle to the receiving side of this conversation using the known unique identifier
DECLARE @farHandle UNIQUEIDENTIFIER;
SET @farHandle = (SELECT conversation_handle FROM q1 WHERE message_body = @initbin)

-- Get our initialization message out of the queue
...

皆さんありがとう!

4

2 に答える 2

1

メッセージをやり取りする必要がある場合は、ServiceBrokerが適切に機能します。行が挿入されるまでブロックする組み込み機能はありませんがquery subscription、クエリの結果セットが変更されたときに通知するためにを使用できます。これはServiceBrokerアーキテクチャの上に構築されており、SQLからではなく.NETからサブスクリプションをセットアップする必要があると思います。

Service Brokerに戻ると、ローカルの場合はルーティングなどのことをあまり気にする必要がないので、あなたのケースが最も簡単です。送信キューは、送信する必要のあるメッセージの単なる保持領域であり、心配する必要はありません。

私はあなたをつまずかせているのは、とにかく会話のハンドルを取得するので、会話のハンドルを取得することを心配する必要がないということだと思います。

(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句にconversation_handleを配置する必要があります。

receive @status = status, 
        @myMessage = CONVERT(NVARCHAR(MAX), message_body) 
from q1
where conversation_handle = @far_handle

それ以外の場合は、送信された独自のメッセージの受信を開始します。これは、同じキューで1つのサービスがそれ自体と通信しているためです。これを回避するには、2つのサービスが相互に通信します。これは通常、よりクリーンなアプローチです。

これにより、基本的にに行く必要がなくなりますsys.conversation_endpoints。これは、実際には会話の管理者向けです。

また、会話をきれいに終わらせるためには、両側から終わらせる必要があります。を使用する必要がある状況に陥らないでくださいend conversation with cleanup

同時に処理される複数の会話を処理するには、と呼ばれるServiceBroker機能を使用できますqueue activation。それらを同時に処理する必要がなければ、これは必要ありません。アクティベーションを使用するには、2つのサービスとキューを使用するのが最適です。

完全な例

(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

メッセージを送信すると、メッセージハンドラーがウェイクアップし、「bobHandler」ストアドプロシージャが呼び出されます。10に設定max_queue_readersすると、10個のメッセージを同時に処理できるようになります。

アクティベーションを使用したくない場合は、受信者の1つのスレッドが着信するすべてのメッセージを処理します。これを行うには、宛先キューでそれをオフにし、'messageHandler'ストアドプロシージャを使用するように変更してwait for (receive)、それを実行します。ループ内のコード。

これがすべて終わった場合、実際に人間に受信手順を呼び出してもらいたいので、アクティベーションを忘れて、これを試してください。

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
于 2013-02-28T22:08:22.470 に答える
0

SQL Server環境でのメッセージ交換について話しているかのように、ServiceBrokerは確実にソリューションを提供するものだと思います。これは、すべてのタスクとすべての可能な要件に対して完全なソリューションではありませんが、確実に機能します。

通常どおり、要件をより詳細に指定する必要がありますが、いくつかの仮定を考慮して、2つの可能な解決策を見つけることができます。SBがSender(ProvideMessage)とTarget(WaitForMyMessage)の間の会話用に構成されているとします。

  1. ターゲットがWaitFor_Any_Messageプロシージャを使用するとします。Targetがメッセージを受信すると、WaitFor_Any_Messageを呼び出します。これにより、名前がチェックされ、このメッセージを処理するためにWaitForMyMessageが呼び出されます。問題は、処理する別のメッセージが必要ですか?いいえの場合-問題ありません、それらをドロップします。はいの場合-それらを処理するために別の手順を呼び出します。

  2. 2番目の方法は、任意の名前に対して個別のキューを構成することです。

とにかく、SBは単一のデータベースを使用したローカルメッセージ交換に適しています。これは、いくつかの本番環境でうまく機能しています。

于 2013-03-01T12:00:49.050 に答える