SQLServer -ServiceBroker

版权声明:神毓逍遥 https://blog.csdn.net/qq_26347097/article/details/81634916

1.ServiceBroker简介

直接把文档原话copy过来吧:

SQL Service Broker is one of the best recent features in SQL Server 2005 because it makes it

possible to write queuing and message-based applications. With Service Broker, database

developers can write asynchronous applications to communicate between databases or servers and

can easily build distributed, secure, and reliable applications.

简单来说,Service Broker就是个消息队列,并且可以用来写分布式的异步的应用程序。

2.ServiceBroker的结构:

Service Broker的建立还是有点复杂的,说具体情况前:

先说下Service Broker基本构成:message type,

然后是根据message type创建的contracts,

接着是queue,

最后是基于queue和contracts建立的service,

以上的message type,contract,queue,还有service是service broker的基本构成。

另外servicebroker是可以在同一个sqlserver实例上两个数据库间进行,也可以在两个sqlserver实例间进行(当然这个的配置会比

前者麻烦的多,但也只有这种的模式才更有意义),所以实现servicebroker至少需要两个数据库,一个发起数据库,一个目标数据库。

3.ServiceBroker的创立

在创立之前,先要执行以下脚本修改数据库的TRUSTWORTHY属性, 

如果不是在两个sqlserver实例间,而只是在同一sqlserver实例中的两个数据库间使用servicebroker就需要打开这个属性。

如果是两个sqlserver实例间,因为会使用transport的安全(这个跟看的书讲的有点差别,书上说的可以不开启这个属性的情况

只有在使用证书安全并启了会话加密的情况才不需要设置这个属性)就不需要开启这个属性了。 

另外还有检查当前数据库是不是开启了servicebroker功能,这个功能默认可能是关闭的,如果是关闭的,可以在要使用的数据库

的属性窗口的选项页中,开启这个功能。

修改trustworth设置和检查servicebroker是否开启的脚本:

/*If you are using certificate security with dialog encryption ON (explained later in

the chapter), then setting the TRUSTWORTHY bit to ON is not necessary.

在两个sqlserver实例间进行servicebroker,也不需要将其设置为on,即使endpoint的验证使用的是windows模式*/

ALTER DATABASE dbname SET TRUSTWORTHY ON
GO
use initialDB
GO


/*如果is_broker_enabled没有开启,则需要在数据库属性中打开,否则

无法使用servicebroker*/

select service_broker_guid,is_broker_enabled,is_trustworthy_on

from sys.databases where database_id=DB_ID()

go

创建message type的脚本,这里建立了三个message type(比较懒,就没创立xml类型的消息,只建立了个任意类型的消息,另外

消息类型名起的也不标准,准确具体的参见sql的联机丛书吧,下面的各命令也都一样,具体的参见sql联机帮助吧):

create message type mtesttype

validation=none

go

create message type mtesttype1

validation=none

go

create message type mtesttype2

validation=none

go

创建合同contract,这里是根据上面创建的三个消息类型,定义mtesttype只能被初始数据库发送,type1只能由目标数据库发出,

type2可以被任意(也就是初始和目标)数据库发送。

create contract mtestcontract
(
mtesttype sent by initiator,
mtesttype1 sent by target,
mtesttype2 sent by any
)


下面是创建一个在数据库收到消息的时候自动执行的存储过程,这个存储过程将用在创建队列的时候使用,因为这个存储过程是建立在发起数据库上的,所以在这个存储过程里只接收了消息,并对消息的类型进行了判断,然后根据消息类型执行结束会话或者将从目标数据库发来的消息插入一张表中。
create proc processmsg
as
declare 
@msgtype varchar(255),
@msgbody varchar(255),
@cid uniqueidentifier
begin try
begin tran
waitfor(
/*You can also specify a WHERE clause in the RECEIVE statement, but it is not a full-featured WHERE
clause. You can only specify a conversation_handle or conversation_group_id.*/
receive top(1)
@msgtype=message_type_name,
@msgbody=case when validation='N'
then cast(message_body as varchar(255))
end,
@cid=conversation_handle
from dbo.testqueue),timeout 3000
if(@@ROWCOUNT = 0 or @@ERROR <> 0)
begin
rollback tran
goto done
end
else
begin
if @msgtype = 'mtesttype1'
begin
insert into t2(msg,cid,mtype) values(@msgbody,@cid,@msgtype)
end
if @msgtype = 'http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog'
begin
end conversation @cid
end
end
commit tran
end try
begin catch
while(@@TRANCOUNT > 0) rollback tran
select ERROR_NUMBER() as en,ERROR_LINE() as el,ERROR_MESSAGE() as emsg,
ERROR_SEVERITY() as es 
end catch
done:
go


然后是创建队列 queue(一些具体的参数参见sql联机丛书吧,里面有详细说明)
create queue testqueue
with status = on,
retention = off,
activation(
status = off, --这里没有打开收到消息时执行的存储过程
procedure_name=dbo.processmsg,
max_queue_readers=5,
execute as self 
)


因为上面没有激活收到消息执行的存储过程,这里修改队列以激活它
alter queue testqueue
with status = on,
retention = off,
activation(
status = on,
procedure_name=dbo.processmsg,
max_queue_readers=5,
execute as self
)


最后create service
create service testservice
on queue testqueue(mtestcontract)


以上就是创建最基本的servicebroker需要执行的脚本,以上的脚本是在起始数据库上创建servicebroker的脚本,在目标服务器上的创建脚本略有不同:
一是处理接收消息的存储过程不同,因为这里目标服务器要对起始服务器进行消息的回发,并发出结束会话的消息(在消息会话处理结束时,一定要结束会话,不然会对性能造成严重影响)

use targetdb
go
create proc processmsg
as
declare 
@msgtype varchar(255),
@msgbody varchar(255),
@cid uniqueidentifier
begin try
begin tran
waitfor(
/*You can also specify a WHERE clause in the RECEIVE statement, but it is not a full-featured WHERE
clause. You can only specify a conversation_handle or conversation_group_id.*/
receive top(1)
@msgtype=message_type_name,
@msgbody=case when validation='N'
then cast(message_body as varchar(255))
end,
@cid=conversation_handle
from dbo.testqueue),timeout 3000
if(@@ROWCOUNT = 0 or @@ERROR <> 0)
begin
rollback tran
goto done
end
else
begin
if @msgtype = 'mtesttype'
begin
insert into t2(Msg,Cid,Mtype) values(@msgbody,@cid,@msgtype);
send on conversation @cid
message type mtesttype1('p1 has received ur msg,test ok');
end
end conversation @cid
end
commit tran
end try
begin catch
while(@@TRANCOUNT > 0) rollback tran
select ERROR_NUMBER() as en,ERROR_LINE() as el,ERROR_MESSAGE() as emsg,
ERROR_SEVERITY() as es 
end catch
done:
go


第二个不同的,就是服务名不要跟初始数据库上的相同,不然在同一sqlserver实例上,起始数据库发的消息目标数据库可能收不到:
--------targetservice不能跟initiatorservice同名,不然无法收到消息
--create service testservicet
--on queue testqueue(mtestcontract)


其他的创建脚本都跟起始数据库没差别,名字同名都没有问题。
消息创建好了,就可以进行发送了,发送消息使用如下脚本:

use initialdb
go
declare @cid uniqueidentifier,
@msg varchar(300)
set @msg = 'this is a service broker test msg from initialdb'
begin try
begin dialog conversation @cid 
from service testservice
to service 'testservicet'
on contract mtestcontract


/*off并不意味着加密功能被停用,只代表不需要,当建立了remote service binding
的时候,即使是off,一样会使用加密,如果没有建立binding,则不会加密*/

with encryption = off
select * from sys.conversation_endpoints where conversation_handle=@cid;
send on conversation @cid
message type mtesttype(@msg);
end try
begin catch
select ERROR_NUMBER() as enumber,
ERROR_MESSAGE() as emsg,
ERROR_SEVERITY() as esvty,
ERROR_LINE() as eline
end catch


接收消息和回发的脚本如下:
这个在目标数据库上的存储过程中有写,这里不重复了,只写个核心的部分

waitfor(
/*You can also specify a WHERE clause in the RECEIVE statement, but it is not a full-featured WHERE
clause. You can only specify a conversation_handle or conversation_group_id.*/
receive top(1)
@msgtype=message_type_name,
@msgbody=case when validation='N'
then cast(message_body as varchar(255))
end,
@cid=conversation_handle
from dbo.testqueue),timeout 3000
--因为是消息回发,所以不需要再创建conversation
send on conversation @cid
message type mtesttype1('p1 has received ur msg,test ok');


servicebroker基本的说完了,再说一下conversation group,

conversation group的一个作用是用来作为锁,一般在这种情况下可能会需要使用它:从初始数据库的一个队列开启了n个会话,然后分别接收这n个不同目标数据库发回来的处理结果,这时候会激活起始数据库上的跟这个队列绑定的接收存储过程,如果想只有一个存储过程实例来处理这些回发的消息,而不想同时启用多个存储过程的实例来同时处理这些回发的消息,也就是只想同步的来处理这些消息,这时候就需要把这n个会话,放在同一个会话组里,这样在消息发回来的时候,就会对这些消息使用锁,确保只有一个存储过程实例来一个个的处理这些回发的消息。在这里有一点要非常的注意,会话组只对发起方有效,也就是说只有发起方才知道这些会话属于同一个会话组,比如开启两个会话向同一个目标数据库发送消息,当消息到达目标数据库的时候,目标数据库并不能从消息中知道道这两个消息是属于同一个会话组的,它会为这两个消息分配不同的会话组,这样,在目标服务器上就不能使用一个存储过程实例来处理这两个会话发来的消息。
在这里让人很困扰的就是为什么在发起方属于同一会话组的消息,在接收方就不是了,我自己也无可奉告,我只能说微软就是这么定的。下面转一段来自《pro sqlserver 2008 service broker》中对这个问题的描述,反正我是看的一知半解对它的描述:

I assume that you now might be asking, “What the heck is going on here? Why did Service
Broker put all conversations in different conversation groups although I placed them into the
same conversation group with the WITH RELATED_CONVERSATION clause?”
The conversation group is a locking primitive. Conversations in the same group are
locked together, so that any transaction is guaranteed to be the only transaction that
processes messages on the current group. As such, conversation groups are pertinent only for
the side declaring the conversation group—the sender’s side. The conversation group information
doesn’t travel with the message to the other side—the target service. The two
conversations are related, but only on the sender’s side. If you actually send back a reply on
each dialog, the replies will have the same conversation_group_id.
Conversation groups can apply to either the initiator or the target or both. The issue
here is that a conversation group is limited to a single queue. This means you can put
conversations into a group on the initiator, but the conversation group ID isn’t sent over the
network to the target. You can also put conversations into a group on the target queue, but
this is independent of any conversation groups that you may have set up on the initiator. The
conversation group is primarily used for a locking context for Service Broker commands.
SEND and RECEIVE commands can’t span queues in a single command, so a lock that locks
conversations on two different queues doesn’t make sense.
Conversation groups aren’t sent along with messages from the initiator to the target,
because there is no way for the sender of the message to know whether the targets of the conversations
in the group are in the same queue. In fact, developers can change around the
destination queues, so in general, the conversation initiator has no knowledge of the queue
configuration of the target.
When you want to prove that both messages are sent on the same conversation group,
you must process the sent messages on both targets and send a response message back to the
initiator of both conversations. These response messages are then in the same conversation
group on the initiator’s side. To accomplish this, you can create the ProcessTargetQueue1 and
ProcessTargetQueue2 stored procedures that process incoming messages on both queues.
Listing 6-3 shows the implementation of one of these stored procedures.


关于会话组,最后列个脚本,怎么把会话放到一个会话组中,这里一共发了四个消息,使用begin dialog coversation的with related_conversation_group=@cgid:字句,就能将其他的会话放入@cgid会话的会话组中了。
use test2k8
go
declare @cid uniqueidentifier,
@cgid uniqueidentifier,
@msg varchar(300)
set @msg = '1,this is a service broker test msg from test2k8'
begin try
begin dialog conversation @cid 
from service testservice
to service 'testservicet'
on contract mtestcontract
with encryption = off;
select @cgid=conversation_group_id from sys.conversation_endpoints where conversation_handle=@cid;
send on conversation @cid
message type mtesttype(@msg);
end try
begin catch
select ERROR_NUMBER() as enumber,
ERROR_MESSAGE() as emsg,
ERROR_SEVERITY() as esvty,
ERROR_LINE() as eline
end catch;
set @msg = '2,this is a service broker test msg from test2k8'
begin try
begin dialog conversation @cid 
from service testservice
to service 'testservicet'
on contract mtestcontract
with
/*对会话加会话组锁,以保证同组的不同会话的消息可以被单一存储过程实例来处理
但只对会话发起方有效,对会话接收方无效,因为conversation_group_id不随消息
一起被发送到接收方,所以接收方无法自动将消息设为同一会话组而锁住来使用
单一存储过程实例来处理接收到的消息,可以将消息在同一个会话中发送,以确保只
有单一实例来处理接收到的消息*/
related_conversation_group=@cgid,
encryption = off;
select * from sys.conversation_endpoints where conversation_handle=@cid;
send on conversation @cid
message type mtesttype(@msg);
end try
begin catch
select ERROR_NUMBER() as enumber,
ERROR_MESSAGE() as emsg,
ERROR_SEVERITY() as esvty,
ERROR_LINE() as eline
end catch;
set @msg = '3,this is a service broker test msg from test2k8'
begin try
begin dialog conversation @cid 
from service testservice
to service 'testservicet'
on contract mtestcontract
with
related_conversation_group=@cgid, 
encryption = off;
select * from sys.conversation_endpoints where conversation_handle=@cid;
send on conversation @cid
message type mtesttype(@msg);
end try
begin catch
select ERROR_NUMBER() as enumber,
ERROR_MESSAGE() as emsg,
ERROR_SEVERITY() as esvty,
ERROR_LINE() as eline
end catch;
set @msg = '4,this is a service broker test msg from test2k8'
begin try
begin dialog conversation @cid 
from service testservice
to service 'testservicet'
on contract mtestcontract
with
related_conversation_group=@cgid, 
encryption = off;
select * from sys.conversation_endpoints where conversation_handle=@cid;
send on conversation @cid
message type mtesttype(@msg);
end try
begin catch
select ERROR_NUMBER() as enumber,
ERROR_MESSAGE() as emsg,
ERROR_SEVERITY() as esvty,
ERROR_LINE() as eline
end catch;
另外这里还有一小段脚本:
DECLARE @conversationGroup UNIQUEIDENTIFIER;
WAITFOR (
GET CONVERSATION GROUP @conversationGroup FROM TargetQueue
), TIMEOUT 1000
This statement locks the first conversation group with associated messages on the queue and returns the conversation group ID as a result.(这个可以用来取得会话组的id。具体的参考sql联机丛书)
接下来说说service broker的安全问题,这也是它最麻烦的地方,如果要跨服务器(sqlserver 实例)使用service broker,就必须得设置它的安全,在两台服务器的sqlserver实例上建立endpoint。
先说下service broker的安全分类,它的安全分为两类,一类是transport安全,一类是dialog安全。
transport安全是指两台服务器间的tcp/ip连接的安全

dialog安全指的是(建议参考原文,个人能力有限):Secures each individual dialog between the two dialog endpoints. This ensures that the services exchanging the messages are who they say they are.


两者差别在于,dialog安全只是在消息发起端和消息结束端进行加密解密,即使中间有消息的转发点,也不会进行加密解密处理,但是如果使用transport安全的话,则会在每个转发点对消息进行解密和加密的处理,当然这会影响性能。
先说说transport安全,这个分两种,一种是windows验证,一种是基于证书的验证。
先说windows验证,这个一般是使用域用户验证,我自己的网络里没有域,在两台windows上设置相同的用户名一样可以用windows验证,当然,因为这里涉及到访问网络以及验证,所以sqlserver服务的启动账户要设成这个要使用windows用户才行。建立的脚本如下,这个要在起始和目标服务器上都创建这样的一个endpoint:

-------------------使用证书安全创建--------------------------------------
if exists(select 1 from sys.tcp_endpoints where name='SBTestEp')
drop endpoint SBTestEp
go
create endpoint SBTestEp
state=started
as tcp(listener_port=7777)
for service_broker(authentication=certificate sbtestcer,encryption=required)
如果在起始服务器和目标服务器上是使用不同的两个域用户作为两台服务器上sqlserver服务的启动账户的话,则还需要在起始服务器和目标服务器上分别执行如下脚本:
在目标服务器上给起始服务器的域用户设置登录并赋与连接权

CREATE LOGIN [MYDOM\InitiatorLogin] FROM WINDOWS
GRANT CONNECT ON ENDPOINT::TargetSSBEndPoint TO [MYDOM\InitiatorLogin]


在起始服务器上给目标服务器的sqlserver服务的启动用户(域用户)执行如下脚本:
CREATE LOGIN [MYDOM\TargetLogin] FROM WINDOWS
GRANT CONNECT ON ENDPOINT::InitiatorSSBEndPoint TO [MYDOM\TargetLogin]


如果两台服务器的sqlserver服务使用的是localsystem账户启动的,那么就需要把域用户名改成类似于 [MYDOM\InitiatorServer$],[MYDOM\TargetServer$]这样的名字。当然如果两台服务器的sqlserver服务的启动账户是一样的,就不需要以上脚本了。
接下来说下基于证书的验证,这个比起windows来就要复杂些了,但是当没有域或在不同域的时候,就可以使用证书验证。其脚本如下:

1首先是要在起始跟目标服务器的sqlserver中创建证书,并将它的公钥备分,然后进行互换。


/*要在发起和目标服务器上都创建证书,备份的证书要在两台服务器间交换使用,以用
来解密对方发来的信息,因为此证书是针对实例的servicebroker端点的,所以证书要
建在master数据库*/

use master
go
IF NOT EXISTS(SELECT 1 FROM sys.symmetric_keys where name = '##MS_DatabaseMasterKey##')
create master key encryption by password='123456'
go
IF NOT EXISTS (select 1 from sys.databases where [is_master_key_encrypted_by_server] = 1)
alter master key add encryption by service master key
go
create certificate sbtestcer
with subject='initiator server certificate'
go
backup certificate sbtestcer to file='c:\a\isbtest.cer'


创建了证书以后,就要创建使用证书验证的endpoint,脚本如下:
-------------------使用证书安全创建--------------------------------------
if exists(select 1 from sys.tcp_endpoints where name='SBTestEp')
drop endpoint SBTestEp
go
create endpoint SBTestEp
state=started
as tcp(listener_port=7777)
for service_broker(authentication=certificate sbtestcer,encryption=required)


最后就是将在两台服务器上备份的证书互换考备,然后使用这个证书在两台服务器上互相创建登录用户和与证书关联的用户,脚本如下:
/*将发起和目标服务器备份证书也就是公钥互换考备到对方机器,并创建公钥证书,
证书安全模式跟sqlserver服务的启动账户无关,只要创建登录并为其在master
数据库中关联用户,并对此master用户授权公钥并授与此登录用户对端点的连接权就
可进行两个sqlserver实例间的servicebroker
*/

use master
go
create login sblogin with password='123456'
create user sbtester for login sblogin
create certificate sbtestpubcer authorization sbtester
from file='c:\a\tsbtest.cer'
grant connect on endpoint::sbtestep to sblogin


建立好安全设置创建了endpoint后(windows验证或者证书验证),如果想让两台服务器可以进行servicebroker,则还要创建路由 route,在这里需要注意的是,因为路由是存储在msdb数据库中的sys.routes表中,所以在进行要进行两个sql实例间的service broker需要把msdb数据库的service
broker启用才可以,脚本如下:

use test2k8
go
create route SBTestRoute
with
service_name='testservice',/*队列的服务,此服务在tamaki服务器上*/
address='tcp://tamaki:7777'
go


另外在创建路由的时候,它的address项有两个特定值,LOCAL和TRANSPORT,local就是指本地的服务,而transport,则是可以通过以下脚本:
CREATE ROUTE RouteToInitiatorServer WITH ADDRESS = ‘TRANSPORT’
来创建一个通用路由,当从目标服务器往起始服务器回发消息的时候,可以使用这个通配路由,但是它要求起始服务器上servicebroker发起服务的服务名必须是如下格式:

tcp://起始服务器名(或地址):endpoint端口号/服务名,例如:TCP: //InitiatorServer:4040/ResponseService。

以上都创建好以后,还有非常重要的一点需要执行才能在目标服务器上接收消息,不然仍然是接收不到消息。因为以上只是建立了传输安全,并没有创建会话安全,在目标服务器上,目标服务不能识别发起服务的身分,由起始服务发起的操作会被当成由public角色发起的,所以必须执行以下脚本才能正常接收消息,不然接收不到消息:

/*在两个sqlserver实例间进行servicebroker,无论使用windows或者证书安全,如果
没有建立完全的会话安全,则会使用匿名会话,匿名会话是发起者可以识别目标服务
器的身份,但目服务器不能识别发起者的身份,所以如果想要消息在目标服务器上正
常接收则需要将以下的权限赋给目标服务器的public*/

grant send on service::[testservicet] to public

接下来说会话安全,创建会话安全的脚本如下:
首先是在目标数据库和起始数据库上创建证书,然后进行互换(注意这里跟证书验证不一样的地方在于,证书验证是针对sqlserver实例的为 servicebroker服务的endpoint的,所以创建在master数据库,这个只是针对参与servicebroker的数据库的,使用的是起始和目标数据库不是master库):

/*会话安全总是需要建立传输安全(transport security)的,如果想在两个sqlserver实
例间通信的话,有没有建立会话安全都必须建立传输安全才可以进行通信,如果没有
建立会话安全,则会使用匿名会话,是否使用传输安全和会话安全,分别由create
endpoint和begin dialog中的encryption和with encryption选项来控制。当在传输中
如果会有消息的中转服务器,如果传输过程是安全的话可以关闭传输安全,而只使用
会话安全,这样可以提高效率,否则会在传输的每台中转服务器上进行加解密而降低
传输效率,而会话安全只在发起服务器和目标服器上对消息进行加解密,而不会在中转
服务器上进行加解密操作。传输安全是建立在传输endpoint上的,而会话安全是建立在
会话endpoint上的*/

use test2k8 --target server
go
IF NOT EXISTS(SELECT 1 FROM sys.symmetric_keys where name =
'##MS_DatabaseMasterKey##')
CREATE MASTER KEY ENCRYPTION BY PASSWORD = '123456'
GO
IF NOT EXISTS (select 1 from sys.databases where
[is_master_key_encrypted_by_server] = 1)
ALTER MASTER KEY ADD ENCRYPTION BY SERVICE MASTER KEY
GO
create user testservicetowner without login
go
grant control on service::testservicet to testservicetowner
go
create certificate tdcert authorization testservicetowner
with subject='Target Server Certificate for Dialog Security'
go
backup certificate tdcert to file='e:\a\tdcert.cer'
USE test2k8 --initial server
GO
IF NOT EXISTS(SELECT 1 FROM sys.symmetric_keys where name =
'##MS_DatabaseMasterKey##')
CREATE MASTER KEY ENCRYPTION BY PASSWORD = '234567'
GO
IF NOT EXISTS (select 1 from sys.databases where
[is_master_key_encrypted_by_server] = 1)
ALTER MASTER KEY ADD ENCRYPTION BY SERVICE MASTER KEY
GO
create user testserviceowner without login
go
alter authorization on service::testservice to testserviceowner
go
create certificate idcert AUTHORIZATION testserviceowner
WITH SUBJECT = 'Initiator Server Certificate for Dialog Security'
GO
backup certificate idcert to file='c:\a\idcert.cer'


将证书互换考备,然后创建证书的授权用户
use test2k8 --initial server
create user tsowner without login
go
create certificate tdpubcert authorization tsowner
from file='c:\a\tdcert.cer'
use test2k8 --target server
go
create user isowner without login
go
create certificate idpubcert authorization isowner
from file='e:\a\idcert.cer'
go
grant send on service::testservicet to isowner


最后在起始数据库上创建remotebinding
use test2k8 --initial server
go
create remote service binding totestservicet
to service 'testservicet'
with user=tsowner


以上完成以后就创建了会话安全,当然前面创建的endpoint和route是必须的,这样就可以在两台服务器间进行servicebroker了,有了会话安全,就不需要再grant send on service::[testservicet] to public了。
这里再给个创建转发endpoint的脚本:
CREATE ENDPOINT ForwardSBTestEP
STATE = STARTED
AS TCP(LISTENER_PORT = 7777)
FOR SERVICE_BROKER
(
AUTHENTICATION = WINDOWS
,MESSAGE_FORWARDING = ENABLED
----------------最大有40M内在用来消息转发
,MESSAGE_FORWARD_SIZE = 40
)

接着说说会话优先权设置的问题:
优先权是2008才有的功能,在2005中是先到先得模式,没有优先权,另外优先权只
针对于两个sqlserver实例间,因为优先权应用于sys.transmission_queue中的消息
,而在同一实例数据库间的消息不经过sys.transmission_queue而直接从发送方放入
接收数据库的队列中,所以同一实例的数据库间没有优先权,还有优先权是不跨
sqlserver实例的,发送方定义的优先权只能用于发送方,接收方同样也只能使用自己
定义的优先权,同一消息的优先权在发送和接收方可以不相同,这将使有不同的发送
和接收顺序,如果没有设置优先权,优先权默认是5,其范围是从1到10,数越大,优先
级越高

其设定脚本如下:
/*优先权是2008才有的功能,在2005中是先到先得模式,没有优先权,另外优先权只
针对于两个sqlserver实例间,因为优先权应用于sys.transmission_queue中的消息
,而在同一实例数据库间的消息不经过sys.transmission_queue而直接从发送方放入
接收数据库的队列中,所以同一实例的数据库间没有优先权,还有优先权是不跨
sqlserver实例的,发送方定义的优先权只能用于发送方,接收方同样也只能使用自己
定义的优先权,同一消息的优先权在发送和接收方可以不相同,这将使有不同的发送
和接收顺序,如果没有设置优先权,优先权默认是5,其范围是从1到10,数越大,优先
级越高*/
/*必须执行此设置,数据库才能使用servicebroker的优先权*/

alter database test2k8 set honor_broker_priority on
go


以下是为同样的contract在起始和目标数据库上创建了不同的优先权,这在发送和接收的时候会有不同的处理顺序
------------------------------建立优先权-----------------------------------
use test2k8 --initiator
go
create broker priority lowpriority
for conversation
set(
contract_name=pcontract,
local_service_name=pservice,
remote_service_name='pservicet',
priority_level=2)
go
use test2k8 --target
go
create broker priority highpriority
for conversation
set(
contract_name=pcontract,
local_service_name=pservicet,
remote_service_name='pservice',
priority_level=8)
go

建立好了优先权,其他的设置不需要就可以进行servicebroker了,但是在处理的顺序上会有差别,默认级别是5。
要想查看关于servicebroker的优先级可以使用以下sql语句:

-------------------------------查看优先权的信息----------------------------
USE test2k8 --initiator or target
GO
SELECT scp.name AS priority_name
,ssc.name AS contract_name
,ssvc.name AS local_service_name
,scp.remote_service_name
,scp.priority AS priority_level
FROM sys.conversation_priorities AS scp
JOIN sys.service_contracts AS ssc
ON scp.service_contract_id = ssc.service_contract_id
JOIN sys.services AS ssvc
ON scp.local_service_id = ssvc.service_id
ORDER BY priority_name
,contract_name
,local_service_name
,remote_service_name


接下来说说poinson message的情况,poison message情况是指,当在接收消息被激活的存储过程中,当处理的消息发生了错误导致存储过程(也就是处理消息的事务)发生了回滚,service broker会检测这种重复性的回滚,然后将队列给关闭使其停止运行。
对这种情况可以创建事件通知,将错误消息放到其他的队列,脚本如下:

--可以创建事件,将有问题的消息发送到其他的队列中
use test2k8 --target
create queue dbo.poisonmessagnotification 
go
create service ServicePoisonMessageNotification 
ON QUEUE dbo.poisonmessagnotification
([http://schemas.microsoft.com/SQL/Notifications/PostEventNotification])
go
create event notification poisonevent
on queue dbo.testqueue
with fan_in
for broker_queue_disabled
to service 'ServicePoisonMessageNotification','current database'
go


当testqueue发生了poison message 的时候,出问题的消息就会被转发到poisonmessagnotification队列了。可以使用如下sql语句来查询PoisonMessageNotification队列了解情况。
USE TargetDB
SELECT CAST(message_body as xml), * FROM PoisonMessageNotification


最后再放一些用于service broker管理等相关的查询语句和脚本:

/*查看当前数据库的broker是否启用和它的guid(用来把唯一标志消息数据库)*/
SELECT is_broker_enabled, Service_Broker_Guid
FROM sys.databases
WHERE database_id = DB_ID()

/*开启broker功能,以下两句script都需要单独连接,如果数据库有其他连接执行会被
阻止*/

--会创建全新的broker guid并将之前的会话,消息全部抛弃
alter database test2k8 set new_broker
--只是开启broker,不会创建新的guid
ALTER DATABASE test2k8 SET ENABLE_BROKER
/*If you want to keep the same unique identifier for the database but ignore all the existing conversations
(error them out)*/
ALTER DATABASE test2k8 SET ERROR_BROKER_CONVERSATIONS

/*Service Broker routing relies on a unique identifier in
each database to deliver messages correctly,所以当复制数据库以后要用下面的sql
修改那个数据库的unique identifier,不然会导致servicebroker不能正确运行*/

ALTER DATABASE test2k8 SET NEW_BROKER
/*This query determines which Service Brokers are enabled in the current SQL Server instance*/
SELECT name, is_broker_enabled, service_broker_guid, is_trustworthy_on
FROM sys.databases
/*The following query determines Service Broker endpoint state, port, connection authentication type,
encryption algorithm, and whether forwarding is enabled or not*/
SELECT se.name, te.port, se.protocol, se.state_desc,
se.is_message_forwarding_enabled, se.connection_auth as authentication_method,
se.encryption_algorithm_desc
FROM sys.service_broker_endpoints se
JOIN sys.tcp_endpoints te
ON se.endpoint_id = te.endpoint_id

/*This query provides all the service names, along with their queues, and activation stored procedures and
their status*/

SELECT s.name AS Service, sq.Name AS Queue,
CASE WHEN sq.is_receive_enabled = 1 THEN 'Yes' ELSE 'No' END AS QueueActive,
ISNULL(sq.activation_procedure, 'N/A')
,CASE WHEN sq.is_activation_enabled = 1 THEN 'Yes' ELSE 'No' END AS
Is_Activation_Enabled
FROM sys.services s
JOIN sys.service_queues sq
ON s.service_queue_id = sq.object_id

/*This next query provides the queue and message types associated with that queue. This is helpful when
you write the RECEIVE statement in the queue to ensure that you have covered all the message types for
that queue*/

SELECT sq.name AS Queue, mt.Name AS Message_Type_Name
FROM sys.service_queues sq
JOIN sys.services s
ON s.service_queue_id = sq.object_id
JOIN sys.service_contract_usages scu
ON s.service_id = scu.service_id
JOIN sys.service_contracts sc
ON scu.service_contract_id = sc.service_contract_id
JOIN sys.service_contract_message_usages mtu
ON mtu.service_contract_id = sc.service_contract_id
JOIN sys.service_message_types mt
ON mt.message_type_id = mtu.message_type_id
GROUP BY sq.name, mt.Name

/*This DMV provides how many queue monitors you have
in the current instance of SQL Server.*/

select * from sys.dm_broker_queue_monitors

/*sys.dm_broker_activated_tasks: This DMV returns a row for each stored procedure activated
by Service Broker in the current instance of SQL Server.*/

select * from sys.dm_broker_activated_tasks

/*Whenever Service Broker makes network connections, you will
see rows in this DMV. If the connection is idle for 90 seconds, Service Broker closes the connection;
otherwise, it keeps it open so messages can be sent using the same connection.*/

select * from sys.dm_broker_connections


/*When a SQL Server instance is working as a message forwarder,
you will see a row for each message in the process of being forwarded on a SQL Server
instance.*/

select * from sys.dm_broker_forwarded_messages

/*check queue status*/
SELECT name, CASE WHEN is_receive_enabled = 1 THEN 'Yes' ELSE 'No' END AS
QueueEnabled
FROM sys.service_queues

/*check whether the database is enabled for Service Broker or not*/
SELECT name, CASE WHEN is_broker_enabled = 1 THEN ‘Yes’ ELSE ‘No’ END AS
Service_Broker_Enabled
FROM sys.databases

/*check the stored procedure activation status*/
SELECT name, CASE WHEN is_activation_enabled = 1 THEN ‘Yes’ ELSE ‘No’ END AS
ActivationEnabled
FROM sys.service_queues

/*打开收到消息激活的存储过程*/
ALTER QUEUE Queuename WITH ACTIVATION (STATUS = ON)

/*view the monitor of stored procedure processing msg all the queue monitors configured in a SQL Server instance*/
select * from sys.dm_broker_queue_monitors

/*To find out which activated stored procedures are running in a SQL Server instance*/
SELECT * FROM sys.dm_broker_activated_tasks

详细的请参考相关文献,谢谢!

猜你喜欢

转载自blog.csdn.net/qq_26347097/article/details/81634916
今日推荐