Service Broker を使用すると SQL Server のインスタンス間で順番保障されたメッセージの交換を行えます。単一DB内でのメッセージ交換は[SQL Server 2012] Service Brokerで単一データベース内でメッセージ交換してみたでやってみました。今回はSQL Server インスタンスが異なる DB 間でメッセージを交換する処理を下記チュートリアルに従ってやってみました。その覚書を記載します。ほぼチュートリアルと同じですが、ENDPOINT間の認証がうまくいかなかったので、CONNECT権の付与を追加しています。

インスタンス間のメッセージ交換の完了
http://msdn.microsoft.com/ja-jp/library/bb839483(v=sql.105).aspx

冒頭にも記載しましたが、基本的に チュートリアルのまんまです。

動作確認環境

  • ADサーバー1 台, SQL Server 2012 がインストールされた メンバサーバー 2台
  • OS はすべて SQL Server 2012
  • SQL Server 2012 は既定のインスタンスとしてインストールしています

前提条件

配信側、配信先 SQL Server のマシン上で次の設定が行われていることとします。

  • ファイアウォール(FW) の受信設定 、具体的には検証時にはTCP 1433,1434,4022 ポート を受信許可しました。
    (名前付きSQL Server インスタンスの場合は sqlserverのexe に受信許可設定をしてください。)
  • SQL Server の通信プロトコルで TCP/IP が有効になっている

本サンプルでは、発信先のDBは TargetDB, 発信側のDB を InitiatorDB として作成しています。そのほかマスターキーに使用するパスワードなど適宜 自分の環境に合うように置き換えて読んでください。

1. 発信先でService のエンドポイントの作成からサービスの作成まで

発信先のSQL Server のインスタンスで次のことをします。

  • master DB で Service Broker 用の エンドポイントを作成
    ポートは 4022, WIndows 認証を使用するようにしています。(この場合 SQL Server インスタンスのユーザーになります。)
  • 発信先DB(TargetDB)を作成
  • 発信先DBにマスターキーの作成
  • 発信先サービス用のユーザー作成(ログインにマップしない)。そのユーザー(TargetUser)に証明書を関連付けます。
    公開鍵証明書は 発信側で作成するTargetUserに関連付けるためにファイルに退避しています。
  • メッセージ交換用に メッセージ型、コントラクト、受信キュー、サービスを定義します。サービスの所有者を TargetUser にしています。

サービスの所有者を TargetUser にし、 TargetUserに証明書を関連付けるのはなんでだろうとおもったのですが、CREATE REMOTE SERVICE BINDINGの説明で納得しました。CREATE REMOTE SERVICE BINDING は リモートサービスに接続するユーザーを必須でWITH USER 句で指定するようですが、その説明で次のように記載されています。

USER = user_name

TO SERVICE 句のリモート サービスに関連付けられた証明を所有しているデータベース プリンシパルを指定します。この証明は、リモート サービスと交換されるメッセージの暗号化と認証に使用されます。

-- 発信先SQL Serverでの作業
-- 前提:FW 1433,1434,4022 受信許可設定済み
--       SQL Server で TCP接続プロトコルを有効化済み

use master;
GO

-- エンドポイントの作成
IF EXISTS (SELECT * FROM master.sys.endpoints
           WHERE name = N'TestTargetEndpoint')
     DROP ENDPOINT TestTargetEndpoint;
GO

-- AUTHENTICATIONがWINDOWSなので SQL Server サービスインスタンスのユーザー
CREATE ENDPOINT TestTargetEndpoint
STATE = STARTED
AS TCP ( LISTENER_PORT = 4022 )
FOR SERVICE_BROKER (AUTHENTICATION = WINDOWS );
GO

-- DB の作成, Service Broker は既定で有効化されている
IF EXISTS (SELECT * FROM sys.databases
           WHERE name = N'TargetDB')
     DROP DATABASE TargetDB;
GO
-- DB の作成
CREATE DATABASE TargetDB;
GO

USE TargetDB;
GO

-- TargetDBにマスターキーの作成
IF NOT EXISTS(SELECT * FROM sys.symmetric_keys
                      WHERE name = N'##MS_DatabaseMasterKey##')
  BEGIN
    CREATE MASTER KEY
       ENCRYPTION BY PASSWORD = N'passwordXXX!';
  END;
GO

-- 発信先サービス用のユーザー作成
CREATE USER TargetUser WITHOUT LOGIN;
GO


-- サービス用のユーザーに証明書を関連付ける
CREATE CERTIFICATE TargetUserCertificate 
     AUTHORIZATION TargetUser
     WITH SUBJECT = 'Target Certificate',
          EXPIRY_DATE = N'12/31/2099';


-- 発信側で参照するために公開鍵証明書を退避
BACKUP CERTIFICATE TargetUserCertificate
  TO FILE = N'C:\work\ServiceBrokerSample01\TargetUserCertificate.cer';
GO

-- メッセージ型の作成
CREATE MESSAGE TYPE [//Test/Sample1/RequestMessage]
       VALIDATION = WELL_FORMED_XML;
CREATE MESSAGE TYPE [//Test/Sample1/ReplyMessage]
       VALIDATION = WELL_FORMED_XML;
GO

-- コントラクトの作成
CREATE CONTRACT [//Test/Sample1/SimpleContract]
      ([//Test/Sample1/RequestMessage]
         SENT BY INITIATOR,
       [//Test/Sample1/ReplyMessage]
         SENT BY TARGET
      );
GO

-- 発信先のキュー作成
CREATE QUEUE TestTargetQueue;
GO

-- 発信先のサービス作成(所有者を TargetUser)にする
-- 後ほどの処理で発信側から発信先サービスへのRemote Binding
-- を作成するが、このときに使用するユーザーとして指定するのが
-- (退避した証明書を関連付けた)発信側で作成されたTargetUserとなる。
CREATE SERVICE [//TestTargetDB/Sample1/TargetService]
       AUTHORIZATION TargetUser
       ON QUEUE TestTargetQueue
       ([//Test/Sample1/SimpleContract]);
GO

2. 発信側でエンドポイントの作成から サービスの作成まで

発信側のSQL Server のインスタンスで次のことをします。

  • master DB で Service Broker 用の エンドポイントを作成
    ポートは 4022, WIndows 認証を使用するようにしています。(この場合 SQL Server インスタンスのユーザーになります。)
  • 発信側DB(InitiatorDB)を作成
  • 発信側DBにマスターキーの作成
  • 発信側サービス用のユーザー作成(ログインにマップしない)。そのユーザー(InitiatorUser)に証明書を関連付けます。
    公開鍵証明書は 発信先で作成するInitiatorUserに関連付けるためにファイルに退避しています。
  • メッセージ交換用に メッセージ型、コントラクト、受信キュー、サービスを定義します。サービスの所有者を InitiatorUser にしています。

メッセージ型、コントラクトは発信先,発信側で同じになるように定義してください。

-- 発信側SQL Serverでの作業
-- 前提:FW 1433,1434,4022 受信許可設定済み
--       SQL Server で TCP接続プロトコルを有効化済み

use master;
GO

-- エンドポイントの作成
IF EXISTS (SELECT * FROM master.sys.endpoints
           WHERE name = N'TestInitiatorEndpoint')
     DROP ENDPOINT TestInitiatorEndpoint;
GO

-- AUTHENTICATIONがWINDOWSなので SQL Server サービスインスタンスのユーザー
-- ポートは4022 を使用する
CREATE ENDPOINT TestInitiatorEndpoint
STATE = STARTED
AS TCP ( LISTENER_PORT = 4022 )
FOR SERVICE_BROKER (AUTHENTICATION = WINDOWS );
GO

-- DB の作成, Service Broker は既定で有効化されている
IF EXISTS (SELECT * FROM sys.databases
           WHERE name = N'InitiatorDB')
     DROP DATABASE InitiatorDB;
GO

CREATE DATABASE InitiatorDB;
GO

USE InitiatorDB;
GO

-- InitiatorDBにマスターキーの作成
IF NOT EXISTS(SELECT * FROM sys.symmetric_keys
                      WHERE name = N'##MS_DatabaseMasterKey##')
  BEGIN
    CREATE MASTER KEY
       ENCRYPTION BY PASSWORD = N'passwordxxxx!';
  END;
GO

-- 発信側サービス用のユーザー作成
CREATE USER InitiatorUser WITHOUT LOGIN;
GO


-- サービス用のユーザーに証明書を関連付ける
CREATE CERTIFICATE InitiatorUserCertificate 
     AUTHORIZATION InitiatorUser
     WITH SUBJECT = 'Initiator Certificate',
          EXPIRY_DATE = N'12/31/2099';


-- 発信先で参照するために公開鍵証明書を退避
BACKUP CERTIFICATE InitiatorUserCertificate
  TO FILE = N'C:\work\ServiceBrokerSample01\InitiatorUserCertificate.cer';
GO

-- メッセージ型の作成(発信先と同じであること)
CREATE MESSAGE TYPE [//Test/Sample1/RequestMessage]
       VALIDATION = WELL_FORMED_XML;
CREATE MESSAGE TYPE [//Test/Sample1/ReplyMessage]
       VALIDATION = WELL_FORMED_XML;
GO

-- コントラクトの作成(発信先と同じであること)
CREATE CONTRACT [//Test/Sample1/SimpleContract]
      ([//Test/Sample1/RequestMessage]
         SENT BY INITIATOR,
       [//Test/Sample1/ReplyMessage]
         SENT BY TARGET
      );
GO

-- 発信側のキュー作成
CREATE QUEUE TestInitiatorQueue;
GO

-- 発信側のサービス作成(所有者をInitiatorUser)にする
-- 後ほどの処理で発信先から発信側サービスへのRemote Binding
-- を作成するが、このときに使用するユーザーとして指定するのが
-- (退避した証明書を関連付けた)発信先で作成されたInitiatorUserとなる。
-- コントラクトを指定しないことで発信先としてサービスが使用される
-- ことを明示的に指定している。
CREATE SERVICE [//TestInitiatorDB/Sample1/InitiatorService]
       AUTHORIZATION InitiatorUser
       ON QUEUE TestInitiatorQueue;
GO

ここまでで サービスの定義まで終わりました。

3. 発信側で発信先サービスへの参照設定

インスタンスが異なる DB でメッセージを交換する場合は、 発信先、発信側でルーティングの設定が必要になります。発信先、発信側サービスで認証が各サービスのユーザーで認証されるようにリモートバインディングの設定も行います。

発信側DBで次のことを行います。

  • 発信先サービス接続用のユーザー(TargetUser)を作成し、退避した公開鍵証明書と関連付ける
  • 発信先、発信側サービスへのルーティング設定を登録する
  • 発信先サービスへのリモートバインディングを定義しユーザーを 発信先サービス接続用のユーザー TargetUser にします。

ルーティングの設定は、 発信先サービスへは InitiatorDB, ローカルの配信側サービスのルーティングは msdb に作成するので注意してください。発信先のサービスのADDRESSのポートはService Broker のエンドポイント作成時に指定したポートになります。

-- 発信側での作業
USE InitiatorDB;
GO

-- 発信先Service接続用のユーザー作成
CREATE USER TargetUser WITHOUT LOGIN;

-- 発信先で退避した公開先証明書を使用して証明書を作成し
-- 発信側で作成した TargetUser に関連付ける
CREATE CERTIFICATE TargetCertificate 
   AUTHORIZATION TargetUser
   FROM FILE = N'C:\work\ServiceBrokerSample01\TargetUserCertificate.cer'
GO

-- ルートの作成
-- 発信先サービスへのRouteを作成。発信先DBのエンドポイントアドレスをADDRSSに指定する
CREATE ROUTE TestTargetRoute
  WITH SERVICE_NAME = N'//TestTargetDB/Sample1/TargetService'
     , ADDRESS = 'TCP://SBW2012SQL02:4022';


use msdb;
GO

-- 発信側サービスへのRouteを作成。ADDRESSはLOCALを指定する
CREATE ROUTE TestInitiatorRoute
  WITH SERVICE_NAME = N'//TestInitiatorDB/Sample1/InitiatorService'
     , ADDRESS = 'LOCAL';

use InitiatorDB;
GO

-- リモートサービスバインディングの作成
-- WITH USER で指定するのが 発信先サービスのユーザーとして
-- 認証されるように作成した公開鍵証明書が関連付けられた
-- InitiatorDBのTargetUser
CREATE REMOTE SERVICE BINDING TestTargetBinding
      TO SERVICE
         N'//TestTargetDB/Sample1/TargetService'
      WITH USER = TargetUser;

4. 発信先で発信側サービスへの参照設定

発信側で実施したのと同じく、発信先からも発信側サービスへの参照、ルーティングの設定、リモートバインディングの作成を行います。

発信先DBで次のことを行います。

  • 発信側サービスへの接続用のユーザー(InitiatorUser)を作成し、退避した公開鍵証明書と関連付ける
  • 発信側、発信先サービスへのルーティング設定を登録する
  • 発信側サービスへのリモートバインディングを定義しユーザーを 発信側サービス接続用のユーザー InitiatorUserにします。

ルーティングの設定は、 発信側サービスへの登録は TargetDB, ローカルの発信先サービスのルーティングは msdb に作成するので注意してください。発信側のサービスのADDRESSのポートは発信側でService Broker のエンドポイント作成時に指定したポートになります。

-- 発信先での作業

USE TargetDB;
GO

-- 発信側Service接続用のユーザー作成
CREATE USER InitiatorUser WITHOUT LOGIN;

-- 発信側で退避した公開鍵証明書を使用して証明書を作成する
-- 発信先で作成した InitiatorUser に関連付ける
CREATE CERTIFICATE InitiatorCertificate 
   AUTHORIZATION InitiatorUser
   FROM FILE = N'C:\work\ServiceBrokerSample01\InitiatorUserCertificate.cer'
GO

-- リモートサービスへのルートを作成

-- 発信側サービスへのRouteを作成。ADDRSSに発信側SQL Serverのエンドポイントを指定する
CREATE ROUTE TestInitiatorRoute
  WITH SERVICE_NAME = N'//TestInitiatorDB/Sample1/InitiatorService'
     , ADDRESS = 'TCP://SBW2012SQL01:4022';

use msdb;
GO

-- 発信先サービスへのRouteを作成。ADDRESSはLOCALを指定する
CREATE ROUTE TestTargetRoute
  WITH SERVICE_NAME = N'//TestTargetDB/Sample1/TargetService'
     , ADDRESS = 'LOCAL';
GO

use TargetDB;
GO

-- 発信先サービスでInitiator に SEND 権限を付与する
-- ないと以下のエラー
-- Service Broker received an error message on this conversation. Service Broker will not transmit the message; 
-- it will be held until the application ends the conversation.
-- D  29975 //TestTargetDB/Sample1/TargetService TargetDB
--        ユーザー InitiatorUser は、サービス //TestTargetDB/Sample1/TargetService で SEND 権限がありません
GRANT SEND
      ON SERVICE::[//TestTargetDB/Sample1/TargetService]
      TO InitiatorUser;


-- リモートサービスバインディングの作成
-- WITH USER で指定するのが 発信側サービスのユーザーとして
-- 認証されるように作成した公開鍵証明書が関連付けられた
-- TargetDBのInitiatorUser
CREATE REMOTE SERVICE BINDING TestInitiatorBinding
      TO SERVICE
         N'//TestInitiatorDB/Sample1/InitiatorService'
      WITH USER = InitiatorUser;

以上でチュートリアルまでの準備完了。

このまま実行すると次のようなエラーが発生しました。

An error occurred while receiving data: '10054(既存の接続はリモート ホストに強制的に切断されました。)'.
とか
Connection handshake failed. The login 'DomainName\SQLServiceインスタンスの実行ユーザー' does not have CONNECT permission on the endpoint. State 84.

なので、発信先、発信側でSQL Server インスタンスの実行ユーザーのログインを作成し、各エンドポイントへの接続許可を設定しました。

5. CONNECT 権限の付与

今回は、発信先、発信側 の各SQL Server で次のコマンドを実行します。

-- 発信先での操作

CREATE LOGIN [DomainName\SQLServiceのユーザー] FROM WINDOWS;
 GO
GRANT CONNECT ON ENDPOINT::[TestTargetEndpoint] TO [DomainName\SQLServiceのユーザー];
 GO
-- 発信先での操作
CREATE LOGIN [DomainName\SQLServiceのユーザー] FROM WINDOWS;
 GO
GRANT CONNECT ON ENDPOINT::[TestInitiatorEndpoint] TO [DomainName\SQLServiceのユーザー];
 GO

ここまででメッセージ交換が行えるようになりました。

6. メッセージ交換のテスト

あとはチュートリアルに従って 発信側でメッセージ交換の開始、発信先でメッセージの受信と応答メッセージの送信とメッセージ交換終了処理、発信側で応答メッセージの受信とメッセージ交換終了処理を実施します。

6.1 発信側からメッセージ交換開始

USE InitiatorDB;
GO

DECLARE @InitDlgHandle UNIQUEIDENTIFIER;
DECLARE @RequestMsg NVARCHAR(100);

BEGIN TRANSACTION;

BEGIN DIALOG @InitDlgHandle
     FROM SERVICE [//TestInitiatorDB/Sample1/InitiatorService]
     TO SERVICE N'//TestTargetDB/Sample1/TargetService'
     ON CONTRACT [//Test/Sample1/SimpleContract]
     WITH
         ENCRYPTION = ON;

SELECT @RequestMsg = N'<RequestMsg>Message for Target service.</RequestMsg>';

SEND ON CONVERSATION @InitDlgHandle
     MESSAGE TYPE [//Test/Sample1/RequestMessage]
     (@RequestMsg);

SELECT @RequestMsg AS SentRequestMsg;

COMMIT TRANSACTION;
GO

6.2 発信先でのメッセージ受信と応答メッセージ送信とメッセージ交換終了処理

-- 発信先処理
-- メッセージを受信し応答メッセージ送信

USE TargetDB;
GO

DECLARE @RecvReqDlgHandle UNIQUEIDENTIFIER;
DECLARE @RecvReqMsg NVARCHAR(100);
DECLARE @RecvReqMsgName sysname;

BEGIN TRANSACTION;

WAITFOR
( RECEIVE TOP(1)
    @RecvReqDlgHandle = conversation_handle,
    @RecvReqMsg = message_body,
    @RecvReqMsgName = message_type_name
  FROM TestTargetQueue
), TIMEOUT 1000;

SELECT @RecvReqMsg AS ReceivedRequestMsg;

IF @RecvReqMsgName = N'//Test/Sample1/RequestMessage'
BEGIN
     DECLARE @ReplyMsg NVARCHAR(100);
     SELECT @ReplyMsg =
        N'<ReplyMsg>Message for Initiator service.</ReplyMsg>';

     SEND ON CONVERSATION @RecvReqDlgHandle
          MESSAGE TYPE [//Test/Sample1/ReplyMessage]
          (@ReplyMsg);

     END CONVERSATION @RecvReqDlgHandle;
END

SELECT @ReplyMsg AS SentReplyMsg;

COMMIT TRANSACTION;
GO

6.3 発信側での応答メッセージ受信とメッセージ交換終了処理

USE InitiatorDB;
GO

DECLARE @RecvReplyMsg NVARCHAR(100);
DECLARE @RecvReplyDlgHandle UNIQUEIDENTIFIER;

BEGIN TRANSACTION;

WAITFOR
( RECEIVE TOP(1)
    @RecvReplyDlgHandle = conversation_handle,
    @RecvReplyMsg = message_body
  FROM TestInitiatorQueue
), TIMEOUT 1000;

If @RecvReplyDlgHandle is not null
	END CONVERSATION @RecvReplyDlgHandle;

-- Display recieved request.
SELECT @RecvReplyMsg AS ReceivedReplyMsg;

COMMIT TRANSACTION;
GO

 

メッセージはXML形式で送信しています。xmlに対する処理は[SQL Server 2012] Service Brokerで単一データベース内でメッセージ交換してみたの発信先のメッセージ応答処理や次のMSDNドキュメントを参考にして下さい。

xml データ型のメソッド
http://msdn.microsoft.com/ja-jp/library/ms190798.aspx

7 まとめ

説明は以上です。私の環境ではメッセージ交換が行えるようになりました。 エンドポイント間の認証は Windows 認証を使用していますが証明書を使うパターンも時間があればやってみようと思います。

もしうまくいかない場合は、マスターキーや証明書の作成先やルートの作成先などが間違っていないことなどを確認してください。またそのほか転送キュー(sys.transmission_queue) や各受信キューの確認、SQL Service Broker や 受信キューが無効になっていないことを確認してください。

そのほか、 SQL Service Profier で Broker 関連のイベントと Audito のBroker 関連のイベントをチェックしてメッセージを確認してください。ssbdiagnose ツールを使用して メッセージ交換中にエラーが発生しないことを確認してください。次のようなコマンドを実行してメッセージ交換処理を行うだけでも簡単な診断を行えます。

ssbdiagnose runtime connect to -S SQLServerインスタンスA connect to -S SQLServerインスタンスB