SQLServer实战经验分享--ServiceBroker安全配置和使用示例

      在平常的项目设计中,我们经常会用到各种各样的队列来实现分布式系统的异步调用逻辑和数据消息的传递,从而来达到增强应用程序的性能和可伸缩性,通过事务性队列的离线消息处理机制更可以大大提高系统的可靠性。SQLServer自2005以后版本便增加了一个新的内置队列处理应用模块ServiceBroker,此功能模块大大简化了队列的使用操作,更方便的是能和原先的SQLServer系统在事务处理上完美的结合在一起。可是唯一的缺陷是增加了设计的耦合性。

     接下来我们就如何使用ServiceBroker来做一个场景描述。我们需要设计一个Web系统,系统由一个主Web系统和多个子Web系统组成。有一种情况下,在主模块中会产生一类行为数据,这类行为数据需要传播或者记录到各个子模块的数据库中处理。要如何实现这样的功能呢,当然我们可以选用多种现有技术来实现,如(WebService,链接服务器技术,WCF等技术)。在这里为了说明ServiceBroker的简单易用性,我们为此做一简单示例。

前提:各个应用数据库要允许ServiceBroker和设置数据库主密钥

代码部署划分:1公共部分(初始方和目标方共同使用),2.初始方,3.目标方

示例具体实现步骤主要分为(具体参数详细配置请参考MSDN文档):

1.实现ServiceBroker消息、队列和服务

公共部分

定义消息类型架构集合 

CREATE XML SCHEMA COLLECTION
[http://Samples/SQL/ServiceBroker/msgOperationSchema]
AS N'<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="msgOperation">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="msgId" type="xs:int" />
        <xs:element name="msgContent" type="xs:string" />
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>
';

定义消息类型

CREATE MESSAGE TYPE [http://Samples/SQL/ServiceBroker/msgOperation]
VALIDATION 
= VALID_XML WITH SCHEMA COLLECTION
[http://Samples/SQL/ServiceBroker/msgOperationSchema];

 

定义消息契约 

CREATE CONTRACT [http://Samples/SQL/ServiceBroker/msgOperationContract]
(
    
[http://Samples/SQL/ServiceBroker/msgOperation]
    SENT 
BY INITIATOR
);

 

初始方

定义队列

CREATE QUEUE msgOperationInitQueue
WITH
STATUS 
= ON,
RETENTION 
= OFF
GO

 

定义初始服务

CREATE SERVICE [http://Samples/SQL/ServiceBroker/msgOperationInitService]
ON QUEUE msgOperationInitQueue
(
[http://Samples/SQL/ServiceBroker/msgOperationContract]);
GO

 

定义初始存储过程

CREATE PROCEDURE dbo.usp_msgOperation_SET
    
@msgId int,
    
@msgContent nvarchar(2000)
AS
    
declare @message_body as xml([http://Samples/SQL/ServiceBroker/msgOperationSchema]);
    
declare @dialog as uniqueidentifier;
    
    
--填充消息体
    SET @message_body ='<msgOperation>
    <msgId>
'+cast(@msgId as varchar)+'</msgId>
    <msgContent>
'+@msgContent+'</msgContent>
    </msgOperation>
';
    
    
BEGIN DIALOG @dialog
        
FROM SERVICE [http://Samples/SQL/ServiceBroker/msgOperationInitService]
        
TO SERVICE 'http://Samples/SQL/ServiceBroker/msgOperationProcessService'
        
ON CONTRACT [http://Samples/SQL/ServiceBroker/msgOperationContract];
        
--WITH ENCRYPTION = OFF , LIFETIME = 3600;

    
--发送消息
    SEND ON CONVERSATION @dialog
        MESSAGE TYPE 
[http://Samples/SQL/ServiceBroker/msgOperation] (@message_body);

    
END CONVERSATION @dialog;
GO

 

目标方

定义队列处理存储过程

CREATE PROCEDURE dbo.usp_msgOperation_CMD AS
    
RETURN 0
GO

 

定义队列

CREATE QUEUE msgOperationProcessQueue
WITH
STATUS 
= ON,
RETENTION 
= OFF,
ACTIVATION
(
    STATUS 
= ON,
    PROCEDURE_NAME 
= dbo.usp_msgOperation_CMD,
    MAX_QUEUE_READERS 
= 1,
    
EXECUTE AS SELF
);

 

实现队列处理存储过程

ALTER PROCEDURE dbo.usp_msgOperation_CMD
AS

declare @message_body as xml;
declare @message_type as sysname;
declare @dialog as uniqueidentifier;
WHILE(1=1)
    
BEGIN
        
BEGIN TRANSACTION 
        
--接收下一条可用的消息
        WAITFOR(
            RECEIVE 
TOP(1--一次只处理一条消息
            @message_type = message_type_name,
            
@message_body = message_body,
            
@dialog = [conversation_handle]
            
FROM dbo.msgOperationProcessQueue
        ), TIMEOUT 
2000

        
--如果没收到任何消息则跳出循环
        IF (@@ROWCOUNT = 0)
        
BEGIN
            
ROLLBACK TRANSACTION
            
BREAK;
        
END    

        
--根据接收的消息类型执行不同的消息处理逻辑
        IF (@message_type = 'http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog')
        
BEGIN
            
END CONVERSATION @dialog;
        
END
        
ELSE IF(@message_type = 'http://schemas.microsoft.com/SQL/ServiceBroker/Error')
        
BEGIN
            
END CONVERSATION @dialog;
        
END
        
ELSE IF(@message_type = 'http://Samples/SQL/ServiceBroker/msgOperation')
        
BEGIN
            
declare @msgId int
            
declare @msgContent nvarchar(2000)

            
BEGIN TRY
                
SET @msgId = @message_body.value('data(//msgId)[1]''int');
                
SET @msgContent = @message_body.value('data(//msgContent)[1]''nvarchar(2000)');

                
--此处可以处理自定义业务逻辑
                
                
END CONVERSATION @dialog;
            
END TRY
            
BEGIN CATCH
                
ROLLBACK TRANSACTION
                
CONTINUE
            
END CATCH 

            
        
END

        
COMMIT TRANSACTION
    
END
GO

 

实现目标处理服务

CREATE SERVICE [http://Samples/SQL/ServiceBroker/msgOperationProcessService]
ON QUEUE dbo.msgOperationProcessQueue
(
[http://Samples/SQL/ServiceBroker/msgOperationContract])
GO

 

2.实现ServiceBroker安全配置

在一台数据库服务器上的不同数据库之间的安全配置比较简单,默认情况下数据库之间是没有外部访问权限的,要实现你就需要在本地服务器上开启模拟上下文的数据库模块,即在数据库中设置 ALTER DATABASE database_name SET TRUSTWORTHY ON 来实现互相访问的目的。

这里我们需要实现一种更灵活,更安全的配置方式,那就是基于证书的安全配置。

初始方

创建拥有服务的用户

CREATE USER msgOperationInitServiceUser WITHOUT LOGIN;
ALTER AUTHORIZATION ON 
SERVICE::
[http://Samples/SQL/ServiceBroker/msgOperationInitService] 
TO 
msgOperationInitServiceUser;

创建与该用户关联的私钥证书

CREATE CERTIFICATE msgOperactionInitServiceCertPriv AUTHORIZATION msgOperationInitServiceUser
WITH SUBJECT = 'ForMsgOperactionInitService',
START_DATE 
= '01/01/2009',
EXPIRY_DATE 
= '01/01/2100';

 

将公钥证书备份到文件以供目标方服务使用

BACKUP CERTIFICATE msgOperactionInitServiceCertPriv
    
TO FILE = 'X:\**\msgOperactionInitServiceCertPub.cer';

创建调用目标服务的用户

CREATE USER msgOperationProcessServiceUser WITHOUT LOGIN;


导入目标服务的证书并把刚才创建的用户设为所有者

CREATE CERTIFICATE msgOperactionProcessServiceCertPub AUTHORIZATION msgOperationProcessServiceUser
FROM FILE = 'X:\**\msgOperactionProcessServiceCertPub.cer';

 

建立目标服务远程服务绑定

CREATE REMOTE SERVICE BINDING ToMsgOperactionProcessService
TO SERVICE 'http://Samples/SQL/ServiceBroker/msgOperationProcessService'
WITH USER = msgOperationProcessServiceUser;

 

目标方

创建拥有服务的用户

CREATE USER msgOperationProcessServiceUser WITHOUT LOGIN;
ALTER AUTHORIZATION ON SERVICE::[http://Samples/SQL/ServiceBroker/msgOperationProcessService] TO msgOperationProcessServiceUser;

 

创建与该用户关联的私钥证书

CREATE CERTIFICATE msgOperactionProcessServiceCertPriv AUTHORIZATION msgOperationProcessServiceUser
WITH SUBJECT = 'ForMsgOperactionProcessService',
START_DATE 
= '01/01/2009',
EXPIRY_DATE 
= '01/01/2100';

将公钥证书备份到文件以供初始方服务使用

BACKUP CERTIFICATE msgOperactionProcessServiceCertPriv
    
TO FILE = 'X:\**\msgOperactionProcessServiceCertPub.cer';

创建调用初始服务的用户

CREATE USER msgOperationInitServiceUser WITHOUT LOGIN;

导入初始服务的证书并把刚才创建的用户设为所有者

CREATE CERTIFICATE msgOperactionInitServiceCertPub AUTHORIZATION msgOperationInitServiceUser
FROM FILE = 'X:\**\msgOperactionInitServiceCertPub.cer';

 

授予用户发送服务的权限

GRANT SEND ON SERVICE::[http://Samples/SQL/ServiceBroker/msgOperationInitService] TO msgOperationInitServiceUser;

 

3.实现ServiceBroker通讯设置(不同服务器之间通讯)

要把ServiceBroker部署到不同服务器的数据库实例,需要在Master数据库和应用数据库中同时做相应的设置。

Master数据库同样要允许ServiceBroker和设置数据主密钥。

初始方Master数据库

创建初始服务器通讯证书

CREATE CERTIFICATE [Server1_CertPriv]
  
WITH SUBJECT = 'ForServer1Auth',
  START_DATE 
= '01/01/2009',
  EXPIRY_DATE 
= '01/01/2100'
ACTIVE 
FOR BEGIN_DIALOG = ON;

 

将公钥证书备份到文件以供目标服务器使用

BACKUP CERTIFICATE [Server1_CertPriv]
  
TO FILE = 'X:\**\Server1_CertPub.cer';
GO

 

创建初始服务器通讯终结点,这里我们假设使用33333端口监听

CREATE ENDPOINT [Server1_Endpoint]
STATE 
= STARTED
AS TCP ( LISTENER_PORT = 33333 )
FOR SERVICE_BROKER 

    AUTHENTICATION 
= CERTIFICATE [Server1_CertPriv]  
);

创建目标服务器的用户和登录

CREATE LOGIN [Server2_UserProxy] WITH PASSWORD = '123456';
CREATE USER [Server2_UserProxy];

导入由目标服务器导出的证书

CREATE CERTIFICATE [Server2_CertPub] AUTHORIZATION [Server2_UserProxy]
FROM FILE = 'X:\**\Server2_CertPub.cer';

 

为表示目标服务器用户的登录授予CONNECT权限

GRANT CONNECT ON ENDPOINT::[Server1_Endpoint] TO [Server2_UserProxy];

 

初始方应用数据库

服务路由设置

CREATE ROUTE msgOperationProcessServiceRoute WITH
SERVICE_NAME 
= 'http://Samples/SQL/ServiceBroker/msgOperationProcessService',
--BROKER_INSTANCE = 'CFDF4485-FAEF-47F9-B1F6-40DFD65685B7',
ADDRESS = 'TCP://[IP]:33333';
GO

 

目标方Master数据库

创建目标服务器通讯证书

CREATE CERTIFICATE [Server2_CertPriv]
  
WITH SUBJECT = 'ForServer2Auth',
  START_DATE 
= '01/01/2009',
  EXPIRY_DATE 
= '01/01/2100'
ACTIVE 
FOR BEGIN_DIALOG = ON;

 

将公钥证书备份到文件以供初始服务器使用

BACKUP CERTIFICATE [Server2_CertPriv]
  
TO FILE = 'X:\**\Server2_CertPub.cer';
GO

 

创建目标服务器通讯终结点,这里我们假设使用33333端口监听

CREATE ENDPOINT [Server2_Endpoint] 
STATE 
= STARTED
AS TCP ( LISTENER_PORT = 33333 )
FOR SERVICE_BROKER 

    AUTHENTICATION 
= CERTIFICATE [Server2_CertPriv]  
);

 

创建初始服务器的用户和登录

CREATE LOGIN [Server1_UserProxy] WITH PASSWORD = '123456';
CREATE USER [Server1_UserProxy];

 

导入由初始服务器导出的证书

CREATE CERTIFICATE [Server1_CertPub] AUTHORIZATION [Server1_UserProxy]
FROM FILE = 'X:\**\Server1_CertPub.cer';

为表示初始服务器的登录授予CONNECT权限

GRANT CONNECT ON ENDPOINT::[Server2_Endpoint] TO [Server1_UserProxy];

 

目标方应用数据库

服务路由设置

CREATE ROUTE msgOperationInitServiceRoute WITH
SERVICE_NAME 
= 'http://Samples/SQL/ServiceBroker/msgOperationInitService',
--BROKER_INSTANCE = '52CAD803-6951-4FD3-A16A-6995C50024B1',
ADDRESS = 'TCP://[IP]:33333';
GO

 

最后总结下SQLSVR2005 ServiceBroker部署在生产环境中的一些心得

1.安全策略,防火墙策略一定要配置正确
2.从备份还原的数据库可能要重新开启ServiceBroker开关
3.需要数据库带外访问的一定要设置数据库带外访问权限
4.每个服务器的主密钥都是不同的,部署的新服务器上的每个要使用ServiceBroker的数据库一定要重新创建数据库主密钥

posted on 2009-02-12 11:20  冰绿茶  阅读(2089)  评论(4编辑  收藏  举报

导航