Service Broker 可以创建异步的、数据驱动的消息应用程序,它允许一个数据库发送消息到其他数据库,而不需要等待响应,即使远程数据库不能立即处理这些消息,发送数据库也可以继续其他操作。通过使用T-SQL对象和命令,就可以完成管理Service Broker。

 

Service Broker 为SQL Server提供消息队列,这样就可以从数据库中发送异步事务性消息到队列,在队列中这些消息将会被其他服务获取和处理,该服务可能运行在其他数据库、或服务器上。另外,对于异步程序,发送一条消息,并且应用程序不需要等待原始消息已被接收、或处理的确认信息,就可以处理其他相关的任务。一旦完成特定任务,两个Service Broker 服务之间的会话就可以显式的结束。

 

 

Service Broker包含了一些开箱即用的特性,它处理当试图创建自己的异步消息系统时,可能经常遇到的复杂问题。

比如:

首先,Service Broker 消息要保证以适当的顺序,或者以他们发送的原始顺序进行接收,而且这些消息也只能被接收一次(调度程序保证不会重复的读取),并且可以作为同一个会话的一部分发送,与任务的同一个实例相关;

 

其次,Service Broker 保证了消息的发送,当尝试发送第一个消息时,目标数据库(消息的接收者)不可用,消息将加入到发送方的数据库的队列中,当目标数据库变为可用时,发送者将会尝试发送这个消息;

 

再次,由于Service Broker 是内建在SQL Server数据库中的,可以与数据库的其余部分仪器备份,所以这些消息在数据库发生故障的情况下也是可以恢复的;

 

最后使用Service Broker 的事件通知功能,可以跟踪数据库和SQL Server实例的事件,这个与SQL Trace相似,但事件通知是异步的,并且对SQL Server实例整体的性能的影响最小,而SQL Trace对性能的影响较大。

use master go  if not exists(select 1 from sys.databases where name = 'BOOKSTORE') 	CREATE DATABASE bookstore go   if not exists(select 1 from sys.databases where name = 'bookDistribution') 	CREATE DATABASE bookDistribution go   --1.启动Service broker alter database bookstore set enable_broker   --disable_broker可以禁用  alter database bookstore set trustworthy on  --指明SQL Server实例是否信任该数据库以及其中的内容  alter database bookDistribution set enable_broker  alter database bookDistribution set trustworthy on   --2.创建数据库主密钥 use bookstore go  create master key encryption by password = '123abc'  use bookDistribution go  create master key encryption by password = 'abc123'    --3.创建消息类型,定义了从Service Broker端点发送的消息中包含的数据类型 use bookstore go  create message type [//SackConsulting/SendBookOrder] validation = WELL_FORMED_XML go  create message type [//SackConsulting/BookOrderReceived] validation = WELL_FORMED_XML    use bookDistribution go   create message type [//SackConsulting/SendBookOrder] validation = well_formed_xml go  create message type [//SackConsulting/BookOrderReceived] validation = well_formed_xml go   --4.创建约定,定义了在任务级别可以发送或者接收的消息类型 use bookstore go  create contract [//SackConsulting/BookOrderContract] ( 	[//SackConsulting/SendBookOrder] sent by initiator,  --可以由会话的发起方发送的消息类型 	[//SackConsulting/BookOrderReceived] sent by target  --可以由会话的目标方发送的消息类型 )   use bookDistribution go  create contract [//SackConsulting/BookOrderContract] ( 	[//SackConsulting/SendBookOrder] sent by initiator, 	[//SackConsulting/BookOrderReceived] sent by target  ) go   --5.创建队列,队列用来保存数据 --通过select语句来查询队列,或者用receive命令从队列检索一条、多条消息 --检索程序可以是外部的.net程序,不过通过存储过程来实现更方便 use bookstore go  create queue bookStoreQueue with status = on  go   --在创建queue时可以把自动处理消息的程序,绑定到队列的激活选项, --此处通过手动控制队列中的信息交换 use bookDistribution go  create queue bookStoreDistributionQueue with status = on  go    --6.创建服务这样就可以把消息队列绑定到一个或者多个约定上 --服务使用队列和约定来定义一个或一组任务 --服务是消息的发起方和接收方强制约定的规则,并将消息路由到正确的队列 use bookstore go  create service [//SackConsulting/BookOrderService] on queue dbo.bookStoreQueue ([//SackConsulting/BookOrderContract])   use bookDistribution go  create service [//SackConsulting/BookStoreDistributionService] on queue dbo.bookStoreDistributionQueue ([//SackConsulting/BookOrderContract])     --7.1开始会话,发送消息 use bookstore go  declare @conversation_handler uniqueidentifier; declare @order_msg xml;   begin dialog conversation @conversation_handler from service [//SackConsulting/BookOrderService] to service '//SackConsulting/BookStoreDistributionService' on contract [//SackConsulting/BookOrderContract];   set @order_msg =  	'<order id="1234" customer="22" orderdate="2012-10-01"> 	<LineItem ItemNumber="1" ISBN="1-12345-123-0" Quantity="1" /> 	</order> 	';  --send语句可以发送消息	 send on conversation @conversation_handler message type [//SackConsulting/SendBookOrder] (@order_msg);   --7.2.1检索消息 use bookDistribution go  select message_type_name,      --消息类型名        CAST(message_body as xml) as message,  --消息        queuing_order,                         --队列顺序,从0开始                conversation_handle,                   --会话句柄        conversation_group_id                  --会话组id from dbo.bookstoreDistributionQueue    create table dbo.bookOrderReceived ( 	bookOrderReceivedID int identity(1,1) not null, 	conversation_handle uniqueidentifier not null, 	conversation_group_id uniqueidentifier not null, 	message_body xml not null )   --7.2.2receive语句会从队列中读取消息,并且把已经读取的消息删除 declare @conversation_handler uniqueidentifier declare @conversation_group uniqueidentifier  declare @order_msg xml  declare @Text_response_msg varchar(max) declare @response_msg xml  declare @orderID int;   receive top(1) 	@order_msg = message_body, 	@conversation_handler = conversation_handle, 	@conversation_group = conversation_group_id from dbo.bookStoreDistributionQueue;   insert into dbo.bookOrderReceived (conversation_handle,conversation_group_id,message_body) values(@conversation_handler,        @conversation_group,        @order_msg)   select @orderID = @order_msg.value('(/order/@id)[1]','int')  select @Text_response_msg =  	'<orderreceived id="' + CAST(@orderID as varchar(10)) + '"/>'  select @response_msg = CAST(@Text_response_msg as xml);  --7.2.3发送回复消息 send on conversation @conversation_handler message type [//SackConsulting/BookOrderReceived] (@response_msg);    --7.3查看返回的消息,结束会话 use bookstore go   create table dbo.bookOrderConfirmation ( 	bookorderconfirmationID int identity(1,1) not null, 	conversation_handle uniqueidentifier not null, 	datereceived datetime not null default getdate(), 	message_body xml not null )   declare @conversation_handler uniqueidentifier declare @conversation_group uniqueidentifier  declare @order_msg xml declare @text_response_msg varcahr(max);   receive top(1) 	@conversation_handler = conversation_handle, 	@order_msg = message_body from dbo.bookstorequeue   insert into dbo.bookOrderConfirmation (conversation_handle,message_body) values(@conversation_handler,@order_msg)   end conversation @conversation_handler;  --结束会话    --7.4取出消息,判断是否是结束会话消息类型,如果是,那么结束会话 use bookDistribution go   declare @conversation_handler uniqueidentifier declare @conversation_group uniqueidentifier  declare @order_msg xml  declare @message_type_name nvarchar(256);   receive top(1) 	@conversation_handler =  conversation_handle, 	@order_msg = message_body, 	@message_type_name = message_type_name from dbo.bookstoredistributionqueue  /*====================================================== 结束会话会自动发送: http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog 的消息类型到目标数据库,双方,包括发起方和目标,必须都结束会话  ========================================================*/ if @message_type_name = 'http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog' begin 	end conversation @conversation_handler; end   --7.5查看会话端点的状态 select * from sys.conversation_endpoints     --8.1设定Serivce Broker会话的优先级 ALTER DATABASE BOOKSTORE set honor_broker_priority on  alter database bookdistribution set honor_broker_priority on   --8.2查看数据库的属性 select name,        is_honor_broker_priority_on from sys.databases where name in ('bookstore','bookdistribution')   --8.3创建Service Broker优先级 use bookstore go  create broker priority conversation_priority_bookordercontract_bookorderservice for conversation set ( 		contract_name = [//SackConsulting/BookOrderContract], 		local_service_name = [//SackConsulting/BookOrderService], 		remote_service_name = any, 		priority_level = 10 	)  select cp.name,        cp.priority,             --优先级                cp.service_contract_id,        sc.name,                 --约定名称                cp.local_service_id,        s.name,                  --服务名称       cp.remote_service_name         from sys.conversation_priorities cp  inner join sys.service_contracts sc         on cp.service_contract_id = sc.service_contract_id          inner join sys.services s         on s.service_id = cp.local_service_id   use bookDistribution go  create broker priority conversation_priority_bookordercontract_bookstoredistributionservice for conversation set 	( 		contract_name = [//SackConsulting/BookOrderContract], 		local_service_name = [//SackConsulting/BookStoreDistributionService], 		remote_service_name = any, 		priority_level = 10 	)  select cp.name,        cp.priority,             --优先级                cp.service_contract_id,        sc.name,                 --约定名称                cp.local_service_id,        s.name,                  --服务名称        cp.remote_service_name        from sys.conversation_priorities cp inner join sys.service_contracts sc         on cp.service_contract_id = sc.service_contract_id          inner join sys.services s         on s.service_id = cp.local_service_id 	   --8.4修改优先级 use bookstore go  alter broker priority conversation_priority_bookordercontract_bookorderservice for conversation set 	( 		remote_service_name = '//SackConsulting/BookStoreDistributionService' 	)   use bookDistribution go  alter broker priority conversation_priority_bookordercontract_bookstoredistributionservice for conversation set 	( 		priority_level = 9 	) 	  --8.5删除优先级 drop broker priority conversation_priority_bookordercontract_bookstoredistributionservice


 创建处理消息的存储过程

前面使用了临时的T-SQL来处理从队列传入的消息,也可以通过存储过程或外部应用程序创建服务程序,来自动的激活并处理队列中的消息的服务程序,同时还可以指定同时执行的服务程序的数量。

use bookDistribution go  create procedure dbo.usp_service_broker_ReceiveOrders as  declare @conversation_handler uniqueidentifier declare @conversation_group uniqueidentifier declare @order_msg xml declare @text_response_msg varchar(8000)  declare @response_msg xml declare @message_type_name nvarchar(156) declare @orderID int  --当发生运行时错误时,会自动回滚事务 set xact_abort on   begin tran; 	 	--接收消息 	receive top(1)  		@order_msg = message_body, 	    @conversation_handler = conversation_handle, 	    @conversation_group = conversation_group_id, 	    @message_type_name = message_type_name 	from dbo.bookStoreDistributionQueue 	 	--消息类型 	if @message_type_name = '//SackConsulting/SendBookOrder' 	begin 		insert into dbo.bookOrderReceived 		(conversation_handle,conversation_group_id,message_body) 		values(@conversation_handler, 		       @conversation_group, 		       @order_msg) 		        		select @orderID = @order_msg.value('(/order/@id)[1]','int') 		 		select @text_response_msg = '<orderreceived id="' +  		                             CAST(@orderID as varchar(10)) + 		                             '"/>'; 		 		select @response_msg = CAST(@text_response_msg as XML); 		 		send on conversation @conversation_handler 		message type [//SackConsulting/BookOrderReceived] 		(@response_msg); 	end 	 	--如果收到结束会话的消息 	if @message_type_name =  	       'http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog'     begin	 		end conversation @conversation_handler; 	end 	 commit tran  go 					  --修改队列设置 alter queue dbo.BookstoreDistributionQueue with activation  ( 	status = on, 	procedure_name = dbo.usp_service_broker_receiveOrders, 	max_queue_readers = 2,   --存储过程执行的最大数量 	execute as self )	   /*========================================== 通过drop:删除与队列关联的所有激活信息  alter queue dbo.BookstoreDistributionQueue with activation  ( 	drop )	 ============================================*/     use bookstore go  declare @conversation_handler uniqueidentifier  declare @order_msg xml   begin dialog conversation @conversation_handler from service [//SackConsulting/BookOrderService] to service '//SackConsulting/BookStoreDistributionService' on contract [//SackConsulting/BookOrderContract];   set @order_msg =  	'<order id="1234" customer="22" orderdate="2012-10-01"> 	<LineItem ItemNumber="1" ISBN="1-12345-123-0" Quantity="1" /> 	</order> 	';  --send语句可以发送消息	 send on conversation @conversation_handler message type [//SackConsulting/SendBookOrder] (@order_msg);   --可以接收到一个消息,这个消息是对方在接受到下消息后,激活存储过程后,由存储过程回复的消息 select conversation_handle,        CAST(message_body as xml) from dbo.bookStoreQueue         


 实现远程服务器的Service Broker

前面的例子是同一个SQL Server实例的两个数据库,但大部分情况会将Service Broker设置为,使用在两个或多个SQL Server实例上的数据库。为了实现跨越服务器通信,可以通过Windows身份验证或基于证书的身份验证,启用传输安全模式、启用对话安全模式、创建路由、创建远程绑定。

--在实例1上 use master go  if exists(select 1 from sys.databases where name = 'bookstore') 	drop database bookstore else 	create database bookstore go   alter database bookstore set enable_broker  alter database bookstore set trustworthy on   use bookstore go  create message type [//BookConsulting/SendBookOrder] validation = well_formed_xml go  create message type [//BookConsulting/BookOrderReceived] validation = well_formed_xml go   create contract [//BookConsulting/BookOrderContract] ( 	[//BookConsulting/SendBookOrder] sent by initiator, 	[//BookConsulting/BookOrderReceived] sent by target ) go    create queue BookStoreQueue with status = on   create service [//BookConsulting/BookOrderService] on queue dbo.BookStoreQueue ( 	[//BookConsulting/BookOrderContract] )    --在实例2上 use master go  if exists(select 1 from sys.databases where name = 'bookdistribution') 	drop database bookdistribution else 	create database bookdistribution go   alter database bookdistribution set enable_broker  alter database bookdistribution set trustworthy on   use bookdistribution go  create message type [//BookConsulting/SendBookOrder] validation = well_formed_xml go  create message type [//BookConsulting/BookOrderReceived] validation = well_formed_xml go   create contract [//BookConsulting/BookOrderContract] ( 	[//BookConsulting/SendBookOrder] sent by initiator, 	[//BookConsulting/BookOrderReceived] sent by target ) go    create queue BookDistributionQueue with status = on   create service [//BookConsulting/BookDistributionService] on queue dbo.BookDistributionQueue ( 	[//BookConsulting/BookOrderContract] )     --启用传输安全模式,只是限制其他实例是否能访问本地服务器的端点  --实例1  use master  go   --1.删除已经存在的数据库主密钥 drop master key   --2.创建数据库主密钥 create master key encryption by password = '123456!@#'   --3.创建证书 create certificate bookMasterCert with subject = 'book Transport Security Service Broker', 	 expiry_date = '2012-12-31'    --4.备份证书 backup certificate bookMasterCert to file = 'c:\bookMasterCert.cer' go   --5.创建端点 create endpoint service_broker_book_endpoint state = started as tcp (listener_port = 4020) for service_broker ( 						authentication = certificate bookMasterCert, 						encryption = required                    )   --6.创建SQL Server的登录名 create login service_broker_login with password = 'service_broker_login123'   --7.创建数据库用户名 create user service_broker_user for login service_broker_login   --8.授予数据库用户可以连接端点 grant connect on endpoint::service_broker_book_endpoint                to service_broker_login   --9.通过另一个实例复制到本地服务器上的证书文件,来创建证书 create certificate bookDistributionMasterCert authorization service_broker_user from file = 'c:\bookDistributionMasterCert.cer' go     --实例2  use master  go   --删除数据库主密钥 drop master key  create master key encryption by password = '123456&^%'   create certificate bookDistributionMasterCert with subject = 'bookDistribution Transport Security Service Broker', 	 expiry_date = '2012-12-31'    backup certificate bookDistributionMasterCert to file = 'c:\bookDistributionMasterCert.cer'   create endpoint service_broker_bookdistribution_endpoint state = started as tcp (listener_port = 4021) for service_broker ( 						authentication = certificate bookDistributionMasterCert, 						encryption = required                    )   create login service_broker_login with password = 'service_broker_login123'   create user service_broker_user for login service_broker_login   grant connect on endpoint::service_broker_bookdistribution_endpoint                to service_broker_login   create certificate bookMasterCert authorization service_broker_user from file = 'c:\bookMasterCert.cer' go     --启用对话安全模式 --实例1 use bookstore go  --1.创建数据库主密钥 create master key encryption by password = '123456!@#'     --2.创建证书,这里可以给当前数据库用户创建多个证书,不会有影响 --当接收到其他服务器传送过来的消息时,可以用这个证书来解密消息 create certificate BookStoreCert with subject = 'BookStore service broker cert',      expiry_date = '2012-12-31'   --3.备份证书 backup certificate bookstorecert to file = 'c:\bookstorecert.cer' go   --4.创建数据库用户,此用户只可以有一个证书 create user bookDistributionUser without login go   --5.通过从另一个实例复制过来的证书,来创建证书,并指定所有者为此用户 create certificate bookDistributionCert authorization bookDistributionUser        --此用户只能拥有一个证书,                                           --在发送消息时会用这个证书来加密消息 from file = 'c:\bookDistributionCert.cer'   --6.授予此用户名在某个服务上发送的权限 grant send on service::[//BookConsulting/BookOrderService] to bookDistributionUser go   --7.创建路由 create route route_bookDistribution with service_name = '//BookConsulting/BookDistributionService',      address = 'tcp://192.168.1.16:4021'   --8.创建远程绑定 create remote service binding bookDistributionBinding to service '//BookConsulting/BookDistributionService' with user = bookDistributionUser   --9.开始会话,发送消息 declare @conversation_handler uniqueidentifier declare @order_msg xml;   begin dialog conversation @conversation_handler from service [//BookConsulting/BookOrderService] to service '//BookConsulting/BookDistributionService' on contract [//BookConsulting/BookOrderContract]   set @order_msg =  	'<order id="1234" customer="22" orderdate="2012-10-01"> 	<LineItem ItemNumber="1" ISBN="1-12345-123-0" Quantity="1" /> 	</order> 	';   --send语句可以发送消息	 send on conversation @conversation_handler message type [//BookConsulting/SendBookOrder] (@order_msg);     --启用对话安全模式 --实例2 use bookdistribution go  create master key encryption by password = '123456&^%'     --当接收到对方发送的消息后,用此证书来解密 create certificate BookDistributionCert with subject = 'BookDistribution service broker cert',      expiry_date = '2012-12-31'   backup certificate bookDistributioncert to file = 'c:\bookDistributioncert.cer'   create user bookStoreUser without login   --在发送之前,用此证书来加密消息 create certificate bookStoreCert authorization bookStoreUser from file = 'c:\bookStoreCert.cer'   grant send on service::[//BookConsulting/BookDistributionService] to bookStoreUser   create route route_bookStore with service_name = '//BookConsulting/BookOrderService',      address = 'tcp://192.168.9.67:4020'   create remote service binding bookStoreBinding to service '//BookConsulting/BookOrderService' with user = bookStoreUser   --查询消息 SELECT * FROM dbo.bookdistributionqueue            


事件通知
事件通知是集成到Service Broker的功能,这样可以在SQL Server实例中异步捕获SQL事件,将事件信息路由到特定的队列中。只需要最小的系统开销,就可以跟踪发生在SQL Server实例的事件,比如用户登录,存储过程重新编译,权限修改,对象处理(包括:对数据库、程序集、角色、表的create/alter/drop事件)。

使用事件通知只需要创建队列和Service Broker组件,在SQL Server中已经内建了,用来捕捉和发送事件通知的消息类型、约定。

IF NOT exists(select 1               from sys.databases               where name = 'EventTracking')                	create database EventTracking else 	drop database EventTracking 	 go   use eventtracking go   --1.创建队列 create queue SQLEventQueue with status = on go   --2.在队列上创建服务,关联到内建的事件通知约定 create service [//EventTracking/TrackLoginAlterService] on queue SQLEventQueue ([http://schemas.microsoft.com/SQL/Notifications/PostEventNotification]);   --3.查询guid select service_broker_guid --6AE234DF-C4BB-4C1B-9E08-F4EA66359B6A from sys.databases where name = 'EventTracking'   --4.创建server范围的事件通知,来跟踪SQL Server实例中所有登录名的创建,修改,删除 create event notification EVENT_LoginEvent on server for create_login,alter_login,drop_login to service '//EventTracking/TrackLoginAlterService',    '6AE234DF-C4BB-4C1B-9E08-F4EA66359B6A'   --service_broker_guid   --5.创建一个登录名,事件通知,把消息放入队列 create login login_ggg with password = '123456!@'   --6.查询消息 select CAST(message_body as xml) as event_Info from dbo.sqleventqueue