在这篇文章里,我们将讨论用来建立分布式Service Broker应用程序的高级service broker对象。我们将看看消息在两个不同的服务器之间的两个不同数据库之间是怎样传递信息的。
必要条件:应该对Service Broker的基本概念如消息类型、约定、服务、队列、会话、在会话上发送消息和接收消息有一个基本的了解。
高级Service Broker对象:对于在同一个服务器的同一个数据库里使用Service Broker的应用程序而言,我们不必使用高级Service Broker对象。
下面分布式Service Broker应用程序使用的高级Service Broker对象:
- 终端: 终端会在特定的端口上接收输入和输出的TCP/IP连接。对实例来说,在它的所有服务之间仅能共享一个终端。
- 路由: 路由用来定位发送消息的服务。当没有对服务明确指定路由时Service Broker将在目前实例内部发送消息。
- 远程服务绑定: 远程服务绑定(RSB)用来建立安全证书,它用来启动服务以验证自身的远程服务。RSB使用证书去关联特定的数据库用户账户以便连接到远程实例。
安全: 在开始之前,即使服务在不同的计算机上,我们也应该先了解一下Service Broker安全是怎样允许服务安全地进行通信的。
Service Broker安全依赖于在远程数据库之间共享的证书,但没有其他的信息是共享的。Service Broker允许两种类型的安全:
- 对话安全:当消息离开发送实例直到它到达目标实例(端对端加密),对话安全为会话的特定服务和加密的单个消息都提供远程验证。
- 传输安全: 它防止从发送Service Broker消息到本地实例的数据库的未经验证的网络连接。它也控制在两个实例之间哪个实例能通信并提供加密,但它不保护单个消息的内容。
- 创建基本的Service Broker对象(如消息类型、约定、服务、队列等等)
- 设置传输安全:
- 为master数据库创建一个主密钥
- 创建证书和支持基于验证的证书的终端(如为服务器创建一个私钥)
- 在远程实例里创建并安装证书的一个备份
- 从拷贝到另一个服务器的证书备份文件里创建证书(如在目前服务器里创建远程服务器的一个公钥)
- 为在第四步里创建的证书创建登录
- 在终端上分配登录、了解权限
- 设置对话安全:
- 在本地数据库也就是要用于应用程序的数据库里创建主密钥
- 创建一个用户证书(也就是为服务器创建一个私钥)
- 做一个已创建的用户证书的备份并在远程实例上安装
- 创建一个和在另一个数据库上有访问权限的用户的名称相同的用户
- 从拷贝自另一个服务器的用户证书备份文件里创建一个用户证书,允许授权给第四步里创建的用户(也就是在目前服务器里创建远程服务器的一个公钥)
- 给用户分配连接权限
- 在本地服务上给用户分配发送权限
- 使用创建的用户创建一个远程服务绑定
- 发送消息&接收消息
下面是示例应用程序执行的任务
a) ServerA发送消息给ServerB
b) ServerB接收消息并给ServerA发送一个消息
步骤:
I.创建基本的Service Broker对象:
在ServerA的DatabaseA里,让我们执行下面的操作:
1) 创建消息类型
Create Message Type SenderMessageType validation=NONE Create Message Type ReceiverMessageType validation=NONE |
Create Contract SampleContract( SenderMessageType SENT BY INITIATOR, ReceiverMessageType SENT BY TARGET) |
Create Queue InitiatorQueue WITH status = ON |
Create Service SenderService ON QUEUE InitiatorQueue (SampleContract) |
1) 创建消息类型:
Create Message Type SenderMessageType validation=NONECreate Message Type ReceiverMessageType validation=NONE |
Create Contract SampleContract( SenderMessageType SENT BY INITIATOR, ReceiverMessageType SENT BY TARGET) |
Create Queue TargetQueue WITH status= ON |
Create Service ReceiverService ON QUEUE TargetQueue (SampleContract) |
II. 创建路由:
一旦在两个服务器上创建了服务,我们就需要在每个数据库里创建路由并联合远程服务来发送消息.
在ServerA的DatabaseA里
Create Route RouteAWITH SERVICE_NAME = 'ReceiverService', BROKER_INSTANCE = '1B9C40BC-7FCF-41F7-9813-61C11A49D0DE', ADDRESS = 'TCP://157.57.100.9:4022'GO |
select service_broker_guid from sys.databases where name = 'DatabaseB' |
在ServerB的DatabaseB里
用相同的方式创建路由(在我们的例子里需要创建这个路由,因为一旦我们在ServerB里处理了ServerA发来的消息,我们要给ServerA发回消息)。
Create Route RouteBWITH SERVICE_NAME = 'SenderService', BROKER_INSTANCE='D164787D-590A-47AC-83AB-987F880E3F2A', ADDRESS = 'TCP://172.22.26.216:4022'GO |
III. 设置传输安全:
注意:所有关于传输安全的操作都将在服务器的master数据库里执行。
1) 为master数据库创建主密钥
2) 创建证书和支持基于验证的证书的终端
Server A:
Use masterGo--1. Create a master key for master database.Create Master Key Encryption BY Password = 'gs53&"f"!385'Go/*2.Create certificate and End Point that support certificate based authentication */Create Certificate EndPointCertificateAWITH Subject = 'A.Server.Local', START_DATE = '01/01/2006', EXPIRY_DATE = '01/01/2008'ACTIVE FOR BEGIN_DIALOG = ON;GOCREATE ENDPOINT ServiceBrokerEndPoint STATE=STARTED AS TCP (LISTENER_PORT = 4022) FOR SERVICE_BROKER ( AUTHENTICATION = CERTIFICATE EndPointCertificateA, ENCRYPTION = SUPPORTED ); |
Use masterGo --1. Create a master key for master database.Create Master Key Encryption BY Password = '45Gme*3^&fwu';Go--2.Create certificate and End Point that support certificate based authentication.Create Certificate EndPointCertificateBWITH Subject = 'B.Server.Local', START_DATE = '01/01/2006', EXPIRY_DATE = '01/01/2008'ACTIVE FOR BEGIN_DIALOG = ON;GOCREATE ENDPOINT ServiceBrokerEndPoint STATE=STARTED AS TCP (LISTENER_PORT = 4022) FOR SERVICE_BROKER ( AUTHENTICATION = CERTIFICATE EndPointCertificateB, ENCRYPTION = SUPPORTED ); |
3) 做一个已创建好的证书的一个备份,并拷贝到另一个服务器上,把它安装到远程实例里。
Server A:
BACKUP CERTIFICATE EndPointCertificateA TO FILE = 'C:/Documents and Settings/Santhi/Desktop/Service Broker/Session/EndPointCertificateA.cer';GO |
把证书从上面的位置拷贝到下面的位置
目标: Server B
路径: C:/Documents and Settings/Santhi/Desktop/ServiceBroker/
Server B:
BACKUP CERTIFICATE EndPointCertificateB TO FILE= 'C:/Documents and Settings/Santhi/Desktop/ServiceBroker/EndPointCertificateB.cer';GO |
把证书从上面的位置拷贝到下面的位置
目标: Server A
路径: C:/Documents and Settings/Santhi/Desktop/ServiceBroker/Session/
4) 从另一个服务器里拷贝过来的证书备份文件里创建证书。
Server A:
Create Certificate EndPointCertificateB From FILE = 'C:/Documents and Settings/Santhi/Desktop/Service Broker/Session/EndPointCertificateB.cer';GO |
Create Certificate EndPointCertificateA From FILE = 'C:/Documents and Settings/Santhi/Desktop/ServiceBroker/EndPointCertificateA.cer';GO |
5) 在目前的服务器里从证书里创建一个远程服务器的登录。
Server A:
CREATE LOGIN sbLogin FROM CERTIFICATE EndPointCertificateB;GO |
CREATE LOGIN sbLogin FROM CERTIFICATE EndPointCertificateA;GO |
6) 在终端上分配登录和连接权限。
Server A:
GRANT CONNECT ON ENDPOINT::ServiceBrokerEndPoint To sbLoginGO |
GRANT CONNECT ON ENDPOINT::ServiceBrokerEndPoint To sbLoginGO |
IV. 设置对话安全:
注意:所有与对话安全相关的操作都是再ServerA的DatabaseA和ServerB的DatabaseB里执行的,而不是在master数据库里。
1) 在本地数据库也就是我们的应用程序将要使用的数据库里创建主密钥。
Server A:
Use DatabaseAGOCreate Master Key Encryption BYPassword = 'gs53&"f"!385'Go |
Use DatabaseBGOCreate Master Key Encryption BYPassword = '45Gme*3^&fwu';Go |
2) 创建用户证书
Server A:
Create Certificate UserCertificateA WITH Subject = 'A.Server.Local', START_DATE = '01/01/2006', EXPIRY_DATE = '01/01/2008'ACTIVE FOR BEGIN_DIALOG = ON;GO |
Create Certificate UserCertificateB WITH Subject = 'B.Server.Local', START_DATE = '01/01/2006', EXPIRY_DATE = '01/01/2008'ACTIVE FOR BEGIN_DIALOG = ON;GO |
3) 备份创建好的用户证书并在远程实例上安装。
Server A:
BACKUP CERTIFICATE UserCertificateA TO FILE='C:/Documents and Settings/Santhi/Desktop/Service Broker/Session/UserCertificateA.cer';GO |
把上面位置的证书复制到下面的位置
目标: Server B
路径: C:/Documents and Settings/Santhi/Desktop/ServiceBroker/
Server B:
BACKUP CERTIFICATE UserCertificateB TOFILE='C:/Documents and Settings/Santhi/Desktop/ServiceBroker/UserCertificateB.cer';GO |
目标: Server A
路径: C:/Documents and Settings/Santhi/Desktop/ServiceBroker/Session/
4) 创建一个和在另一个数据库里有访问权限的用户的名称相同的用户。
Server A:
Create User UserB WITHOUT LOGINGO |
Create User UserA WITHOUT LOGINGO |
5) 使用在第4步里创建的用户的验证,来从复制到另一个服务器上的证书备份文件里创建一个用户证书。
Server A:
CREATE CERTIFICATE UserCertificateB AUTHORIZATION UserB FROM FILE = 'C:/Documents and Settings/Santhi/Desktop/Service Broker/Session/UserCertificateB.cer';GO |
CREATE CERTIFICATE UserCertificateA AUTHORIZATION UserAFROM FILE = 'C:/Documents and Settings/Santhi/Desktop/ServiceBroker/UserCertificateA.cer';GO |
6) 给用户分配连接权限。
Server A:
GRANT CONNECT TO UserB; |
GRANT CONNECT TO UserA; |
7) 在本地服务上给用户分配发送权限。
Server A:
GRANT SEND ON SERVICE::SenderService To UserB;GO |
GRANT SEND ON SERVICE::ReceiverService To UserA;GO |
8) 使用创建的用户来创建一个远程服务绑定。
Server A:
CREATE REMOTE SERVICE BINDING ServiceBindingB TO SERVICE 'ReceiverService' WITH USER = UserB |
CREATE REMOTE SERVICE BINDING ServiceBindingA TO SERVICE 'SenderService' WITH USER = UserA |
V. 从ServerA发送消息
在DatabaseA里:
/**********Begin a Dialog and Send a Message******************/Declare @ConversationHandle uniqueidentifierBegin TransactionBegin Dialog @ConversationHandle From Service SenderService To Service 'ReceiverService' On Contract SampleContract WITH Encryption=off;SEND ON CONVERSATION @ConversationHandle Message Type SenderMessageType ('<test>test</test>')Commit |
现在在DatabaseB(serverB上)的TargetQueue里检查这条记录。
select cast(message_body as xml) from TargetQueue |
在DatabaseB里:
/*****Receive the Message and send a message to the ender**********/Declare @ConversationHandle as uniqueidentifierDeclare @MessageBody as nvarchar(max)Declare @MessageType as sysnameBegin TransactionPrint 'Started Receiving ';RECEIVE top (1) @MessageType = message_type_name, @ConversationHandle = conversation_handle, @MessageBody = message_bodyFROM TargetQueue;if @MessageType = 'SenderMessageType' Begin SEND ON CONVERSATION @ConversationHandle Message Type ReceiverMessageType ('Message is received') END Conversation @ConversationHandle ENDCommit |
上面的代码打开一个事务并从TargetQueue里接收第一条消息。在接收消息后,我们就可以执行一些其他的逻辑了,但在我们的例子里,为了简化已收到的消息,我们仅仅把消息发回发送者。
现在在DatabaseB(ServerB上)检查一下记录。当记录被成功处理后就会被删除。现在检查一个DatabaseA(ServerA上)的InitiatorQueue的记录。当使用了终端会话时,两条新的记录将被插入,一条是相关于会话的。另一条是相关于终端对话的。
select cast(message_body as xml) from InitiatorQueue |
结论:
本文没有讨论Service Broker的基本概念。它用来处理分布式service broker应用程序的建立。