Очень часто можно столкнуться с ситуацией, когда требуется реплицировать данные из одной базы данных MS SQL в другую. Лучше всего для этого использовать компонент Service Broker, так как его работа гораздо быстрее запросов через связанные сервера или обычной репликации. И сам компонент очень гибкий. При настройке объектов брокера зачастую владелец обеих баз данных меняется на sa и в базах данных включается свойство trustworthy. Это несколько снижает безопасность, поскольку можно создавать unsafe-сборки, потенциально позволяя злоумышленникам обращаться со стороны сервера к любым внешним ресурсам. Я хочу показать что всего этого можно избежать, если воспользоваться сертификатами. И именно на основе сертификатов настроить безопасность брокера. В этой статье разобран такой пример, когда используется Service Broker для создания асинхронного триггера, который логирует изменения таблицы в другой базе данных. Для этой задачи использование Service Broker в триггере таблицы чрезвычайно удобно, так как изменения реплицируются асинхронно, не заставляя пользователя ждать, когда произойдет запись в таблицу с логами. Для начала создадим две базы данных.
Базы созданы с отключенным свойством trustworthy. Теперь включим в обеих базах Service Broker:
Теперь создадим таблицу, изменения которой будут логироваться:
Теперь приступим к созданию объектов брокера в базе источнике. Понадобятся такие стандартные объекты как типы сообщений, контракт, очередь, служба. Также понадобится привязка удаленной службы, которая нужна для того чтобы указать пользователя в удаленной базе данных, владеющего сертификатом, с помощью которого осуществляется проверка подлинности.
Также понадобится пользователь, который будет владеть сертификатом для возможности проверки подлинности при обратных сообщениях из второй базы данных. Дополнительно сделаем заготовку для процедуры рассылки сообщений и предоставим пользователю необходимые права для работы со службой брокера:
Теперь требуется сделать первый шаг к настройке проверки подлинности при обмене данными между двумя базами данных. Требуется создать сертификат. Чтобы это было возможно, в базе данных первоначально создается главный ключ.
Создается резервная копия сертификата, которая понадобится в базе данных назначения. Теперь создадим объекты брокера и сертификат во второй базе данных, TrgData. Этот процесс примерно такой же как и для SrcData, только здесь не требуется создавать привязку удаленной службы. Имена типов сообщений и контракта должны точно такими же как и в базе данных источнике. Также потребуется пользователь, для простоты назовем его также ReplOper. Создадим и заготовку для процедуры, разбирающей очередь сообщений.
В каждой базе данных надо восстановить резервную копию сертификата созданного в другой базе данных. Причем владельцами этих сертификатов должны обязательно быть созданные пользователи.
Приступим наконец к программированию процедуры для рассылки сообщений:
При создании диалога, указываем не только имя удаленной службы но и идентификатор типа uniqueidentifier для экземпляра службы Service Broker в базе данных TrgData. Это требуется на случай если на сервере существуют другие базы данных с такими же именами служб. Узнать этот идентификатор можно, сделав такой запрос:
Код ниже создает триггер на таблицу dbo.Data, который будет вызывать процедуру dbo.SendData.
В этом триггере мы отправляем не только информацию об измененных данных, но также о времени изменения и о пользователе, который инициировал обновление строк таблицы. В базе данных TrgData требуется создать таблицы для хранения этой инфорации.
Последний шаг состоит в подготовке процедуры активации очереди dbo.Trg. Процедура будет читать сообщения очереди в формате XML и сохранять их в реляционных таблицах.
Подобную асинхронную передачу данных можно настраивать и в том случае, если базы данных находятся на разных экземплярах sql-сервера. В этом случае потребуется дополнительно на каждом из серверов создать конечные точки, используя синтаксис create endpoint ... Конечные точки должны иметь тип Service Broker и быть привязаны к определенным портам. Учетным записям необходимо предоставить право на подключение к этим конечным точкам (grant connect on endpoint::...). При настройке безопасности на основе сертификатов данные о сертификате необходимо также прописать в параметрах конечной точки. Также в каждой базе данных потребуется создать маршрут, используя синтаксис create route ... В параметрах маршрута прописывается служба локального сервера и сетевой адрес удаленного сервера, на который требуется передавать данные.
Настройка безопасности с использованием сертификатов при передаче данных с помощью компонента Service Broker между различными серверами позволяет не только не заботиться о свойстве trustworthy, но и о том, под какими учетными записями запускаются сервера: локальными или доменными, административными или нет. Более того сами сервера могут находиться в недружественных доменах или вообще вне доменов.
create
database SrcData
with trustworthy
off
create database TrgData with trustworthy offБазы созданы с отключенным свойством trustworthy. Теперь включим в обеих базах Service Broker:
alter
database SrcData
set enable_broker
with rollback immediate
alter database TrgData set enable_broker with rollback immediateТеперь создадим таблицу, изменения которой будут логироваться:
use
SrcData
go
if
object_id ( N'dbo.Data', N'U' ) is null
begin
create table dbo.Data
(
iRowId
int identity ( 1, 1 ) not null,
vcName
varchar ( 100 ) not null,
constraint
PK_Data_iRowId primary
key clustered ( iRowId asc ) on [PRIMARY],
constraint
AK_Data_vcName unique
nonclustered ( vcName asc ) on [PRIMARY]
) on [PRIMARY]
end
goТеперь приступим к созданию объектов брокера в базе источнике. Понадобятся такие стандартные объекты как типы сообщений, контракт, очередь, служба. Также понадобится привязка удаленной службы, которая нужна для того чтобы указать пользователя в удаленной базе данных, владеющего сертификатом, с помощью которого осуществляется проверка подлинности.
use
SrcData
go
if
not exists
(
select *
from sys.service_message_types
where name = 'Src'
)
begin
create message type Src
authorization
dbo
validation
= well_formed_xml
end
go
if
not exists
(
select *
from sys.service_message_types
where name = 'Trg'
)
begin
create message type Trg
authorization
dbo
validation
= well_formed_xml
end
go
if
not exists
(
select *
from sys.service_contracts
where name = 'Repl'
)
begin
create contract Repl
authorization
dbo
(
Src
sent by initiator,
Trg
sent by target
)
end
go
if
object_id ( 'dbo.Src', 'SQ' ) is null
begin
create queue dbo.Src
with
status
= on,
retention
= off
on [DEFAULT]
end
go
if
not exists
(
select *
from sys.services
where name = 'Src'
)
begin
create service Src
authorization
dbo
on queue dbo.Src ( Repl )
end
go
if
not exists
(
select *
from sys.remote_service_bindings
where name = 'Repl'
)
begin
create remote service binding Repl
authorization
dbo
to service 'Trg'
with
user = ReplOper,
anonymous = off;
end
goТакже понадобится пользователь, который будет владеть сертификатом для возможности проверки подлинности при обратных сообщениях из второй базы данных. Дополнительно сделаем заготовку для процедуры рассылки сообщений и предоставим пользователю необходимые права для работы со службой брокера:
use
SrcData
go
if
object_id ( 'dbo.SendData', 'P' ) is null
exec ( 'create proc dbo.SendData
as return' )
go
if
database_principal_id ( 'ReplOper'
) is null
begin
create user ReplOper without login
end
go
if
database_principal_id ( 'Repl'
) is null
begin
create role Repl
authorization
dbo
end
go
alter
role Repl add member ReplOper
go
grant
exec on dbo.SendData to Repl
go
grant
receive on dbo.Src to Repl
go
grant
send on service::Src to Repl
goТеперь требуется сделать первый шаг к настройке проверки подлинности при обмене данными между двумя базами данных. Требуется создать сертификат. Чтобы это было возможно, в базе данных первоначально создается главный ключ.
use
SrcData
go
if
not exists
(
select *
from sys.symmetric_keys
where name = '##MS_DatabaseMasterKey##'
)
begin
create master key encryption by password = 'ReplSend@1'
end
go
set
dateformat ymd
go
if
cert_id ( 'Src' ) is null
begin
create certificate Src
authorization
dbo
with
subject
= 'Передача
данных',
start_date= '2015-09-05',
expiry_date
= '2050-12-31'
active
for begin_dialog
= on
end
go
backup
certificate Src
to file = 'C:\Users\Src.cer'
goСоздается резервная копия сертификата, которая понадобится в базе данных назначения. Теперь создадим объекты брокера и сертификат во второй базе данных, TrgData. Этот процесс примерно такой же как и для SrcData, только здесь не требуется создавать привязку удаленной службы. Имена типов сообщений и контракта должны точно такими же как и в базе данных источнике. Также потребуется пользователь, для простоты назовем его также ReplOper. Создадим и заготовку для процедуры, разбирающей очередь сообщений.
use
TrgData
go
if
not exists
(
select *
from sys.service_message_types
where name = 'Src'
)
begin
create message type Src
authorization
dbo
validation
= well_formed_xml
end
go
if
not exists
(
select *
from sys.service_message_types
where name = 'Trg'
)
begin
create message type Trg
authorization
dbo
validation
= well_formed_xml
end
go
if
not exists
(
select *
from sys.service_contracts
where name = 'Repl'
)
begin
create contract Repl
authorization
dbo
(
Src
sent by initiator,
Trg
sent by target
)
end
go
if
object_id ( 'dbo.OperData', 'P' ) is null
exec ( N'create proc dbo.OperData
as return' )
go
if
database_principal_id ( 'ReplOper'
) is null
begin
create user ReplOper without login
end
go
if
object_id ( 'dbo.Trg', 'SQ' ) is null
begin
create queue dbo.Trg
with
status
= on,
retention
= off,
activation
(
status =
on,
procedure_name = dbo.OperData,
max_queue_readers = 4,
exec as 'ReplOper'
)
on [DEFAULT]
end
go
if
not exists
(
select *
from sys.services
where name = 'Src'
)
begin
create service Trg
authorization
dbo
on queue dbo.Trg ( Repl )
end
go
if
database_principal_id ( 'Repl'
) is null
begin
create role Repl
authorization
dbo
end
go
alter
role Repl add member ReplOper
go
grant
exec on dbo.OperData to Repl
go
grant
receive on dbo.Trg to Repl
go
grant
send on service::Trg to Repl
go
if
not exists
(
select *
from sys.symmetric_keys
where name = '##MS_DatabaseMasterKey##'
)
begin
create master key encryption by password = 'ReplOper@1'
end
go
set
dateformat ymd
go
if
cert_id ( 'Trg' ) is null
begin
create certificate dbo
authorization
dbo
with
subject
= 'Прием
данных',
start_date= '2015-09-05',
expiry_date
= '2050-12-31'
active
for begin_dialog
= on
end
go
backup
certificate Trg
to file = 'C:\Users\Trg.cer'
goВ каждой базе данных надо восстановить резервную копию сертификата созданного в другой базе данных. Причем владельцами этих сертификатов должны обязательно быть созданные пользователи.
use
SrcData
go
if
cert_id ( 'Trg' ) is null
begin
create certificate Trg
authorization
ReplOper
from
file = 'C:\Users\Trg.cer'
end
go
use
TrgData
go
if
cert_id ( 'Src' ) is null
begin
create certificate Src
authorization
ReplOper
from
file = 'C:\Users\Src.cer'
end
goПриступим наконец к программированию процедуры для рассылки сообщений:
use SrcData
go
alter
proc dbo.SendData
(
@xData xml
)
with exec as 'ReplOper'
as
begin
begin try
declare
@uidDialog uniqueidentifier, @vcErr nvarchar ( max )
begin
dialog conversation
@uidDialog
from
service Src to service 'Trg', '57B73C90-4F89-4AE7-9702-A4B37D83E46A'
on
contract Repl
with
encryption = off
;
send
on conversation
@uidDialog
message
type Src ( @xData )
end try
begin catch
set
@vcErr = concat ( error_message (), ' Строка: ', error_line (), '. Процедура: ', error_procedure (), '. Номер: ', error_number () )
; throw 60000, @vcErr, 1
end catch
end
goПри создании диалога, указываем не только имя удаленной службы но и идентификатор типа uniqueidentifier для экземпляра службы Service Broker в базе данных TrgData. Это требуется на случай если на сервере существуют другие базы данных с такими же именами служб. Узнать этот идентификатор можно, сделав такой запрос:
select
service_broker_guid
from
sys.databases
where name = 'TrgData'Код ниже создает триггер на таблицу dbo.Data, который будет вызывать процедуру dbo.SendData.
if
object_id ( 'dbo.TR_Data_UID', 'TR' ) is null
exec ( 'create trigger
dbo.TR_Data_UID on dbo.Data for insert, update, delete as return' )
go
alter
trigger dbo.TR_Data_UID on dbo.Data for insert, update, delete
as
begin
if @@rowcount = 0
begin
return
end
declare @xData xml
set @xData = (
select
iOperType = 1, ins.iRowId, ins.vcName, dtDate = getdate (), vcUser = user_name ()
from
inserted
ins
left outer join
deleted
del on ins.iRowId = del.iRowId
where
del.iRowId is null
for
xml path ( 'data' ), root ( 'store' )
)
if @xData is not null
begin
exec
dbo.SendData @xData
end
set @xData = (
select
iOperType = 2, ins.iRowId, ins.vcName, dtDate = getdate (), vcUser = user_name ()
from
inserted
ins
inner join
deleted
del on ins.iRowId = del.iRowId
for
xml path ( 'data' ), root ( 'store' )
)
if @xData is not null
begin
exec
dbo.SendData @xData
end
set @xData = (
select
iOperType = 3, del.iRowId, del.vcName, dtDate = getdate (), vcUser = user_name ()
from
inserted
ins
right outer join
deleted
del on ins.iRowId = del.iRowId
where
ins.iRowId is null
for
xml path ( 'data' ), root ( 'store' )
)
if @xData is not null
begin
exec
dbo.SendData @xData
end
end
goВ этом триггере мы отправляем не только информацию об измененных данных, но также о времени изменения и о пользователе, который инициировал обновление строк таблицы. В базе данных TrgData требуется создать таблицы для хранения этой инфорации.
use
TrgData
go
if
object_id ( N'dbo.Users', N'U' ) is null
begin
create table dbo.Users
(
iUserId int
identity ( 1, 1 ) not null,
vcUser varchar ( 100 ) not
null,
constraint
PK_Users_iUserId primary
key clustered ( iUserId asc ) on [PRIMARY],
constraint
AK_Users_vcUser unique
nonclustered ( vcUser asc ) on [PRIMARY]
) on [PRIMARY]
end
go
if
object_id ( N'dbo.DataLog', N'U' ) is null
begin
create table dbo.DataLog
(
iItemId int
identity ( 1, 1 ) not null,
iRowId int not null,
vcName varchar ( 100 ) not
null,
iOperType tinyint not null,
dtDate datetime not null,
iUserId int not null,
constraint
PK_Data_iRowId_iItemId primary
key clustered ( iRowId asc, iItemId asc ) on [PRIMARY],
constraint
FK_Data_iUserId foreign
key ( iUserId ) references dbo.Users ( iUserId ) on update cascade on delete no action
) on [PRIMARY]
end
goПоследний шаг состоит в подготовке процедуры активации очереди dbo.Trg. Процедура будет читать сообщения очереди в формате XML и сохранять их в реляционных таблицах.
alter
proc dbo.OperData
with
exec as 'ReplOper'
as
begin
declare @uidDialog uniqueidentifier, @xMess xml, @iXml int, @vcErr nvarchar ( max )
begin try
;
receive
top ( 1 ) @uidDialog = conversation_handle, @xMess = message_body
from
dbo.Trg
if @@rowcount = 0
begin
return
end
exec
sp_xml_preparedocument @iXml out, @xMess
if object_id ( 'tempdb..#Data', 'U' ) is not null
drop
table #Data
create
table #Data
(
iItemId int
identity ( 1, 1 ) not null,
iOperType tinyint not null,
iRowId int not null,
vcName varchar ( 100 ) not
null,
dtDate datetime not null,
vcUser varchar ( 200 ) not
null,
primary
key clustered ( vcUser asc, iItemId asc ) on [PRIMARY]
) on [PRIMARY]
insert
into #Data
(
iOperType,
iRowId,
vcName,
dtDate,
vcUser
)
select
iOperType,
iRowId,
vcName,
dtDate,
vcUser
from
openxml ( @iXml, '/store/data', 1 )
with
(
iOperType tinyint 'iOperType',
iRowId int 'iRowId',
vcName varchar ( 100 ) 'vcName',
dtDate datetime 'dtDate',
vcUser varchar ( 200 ) 'vcUser'
)
insert
into dbo.Users ( vcUser )
select
distinct data.vcUser
from
#Data data
left outer join
dbo.Users us on data.vcUser = us.vcUser
where
us.iUserId is null
insert
into dbo.DataLog ( iRowId, vcName, iOperType, dtDate, iUserId )
select
data.iRowId, data.vcName, data.iOperType, data.dtDate, us.iUserId
from
#Data data
inner join
dbo.Users us on data.vcUser = us.vcUser
end
conversation @uidDialog
end try
begin catch
set
@vcErr = concat ( error_message (), ' Номер: ', error_number (), '. Строка: ', error_line (), '. Процедура: ', error_procedure () )
end catch
if isnull ( @iXml, 0 ) <> 0
begin
exec
sp_xml_removedocument
@iXml
end
if isnull ( @vcErr, '' ) <> ''
begin
; throw 60000, @vcErr, 1
end
end
goПодобную асинхронную передачу данных можно настраивать и в том случае, если базы данных находятся на разных экземплярах sql-сервера. В этом случае потребуется дополнительно на каждом из серверов создать конечные точки, используя синтаксис create endpoint ... Конечные точки должны иметь тип Service Broker и быть привязаны к определенным портам. Учетным записям необходимо предоставить право на подключение к этим конечным точкам (grant connect on endpoint::...). При настройке безопасности на основе сертификатов данные о сертификате необходимо также прописать в параметрах конечной точки. Также в каждой базе данных потребуется создать маршрут, используя синтаксис create route ... В параметрах маршрута прописывается служба локального сервера и сетевой адрес удаленного сервера, на который требуется передавать данные.
Настройка безопасности с использованием сертификатов при передаче данных с помощью компонента Service Broker между различными серверами позволяет не только не заботиться о свойстве trustworthy, но и о том, под какими учетными записями запускаются сервера: локальными или доменными, административными или нет. Более того сами сервера могут находиться в недружественных доменах или вообще вне доменов.
Комментариев нет:
Отправить комментарий