Краткое введение в сервис-брокер
В 1997 г. в составе NT4 Option Pack был выпущен MSMQ 1.0 - см. https://www.emanual.ru/download/www.eManual.ru_4364.html.
Восемь лет спустя механизм асинхронной работы на основе очередей под названием Service Broker появился в составе SQL Server 2005. Мы его уже затрагивали в посте про Event Notification . Здесь придется коснуться его чуть более подробно, т.к. далее он понадобится в развитии репликации с помощью Change Tracking на распределенный сценарий. Подобно MSMQ сервис-брокер предназначен для асинхронного взаимодействия, что позволяет реализовать отсоединенные сценарии, когда SQL Serverное приложение отправляет сообщение другому SQL Serverному приложению и может заниматься дальше своими делами, не подвисая в ожидании ответа от удаленного участника. В MSMQ, он же Queued Components в СОМ+, размер сообщения ограничен 4 МБ, в сервис-брокере сообщение представляет собой, вообще говоря, varbinary(max) - 2 ГБ. Поддерживается (в пределах беседы) порядок и корреляция сообщений, нет нужды прибегать к дополнительному программированию для поддержки транзакций, синхронизации и блокировкам при одновременной обработке очереди с нескольких потоков, резервного копирования и пр., т.к. все это является штатной функциональностью SQL Server, а очереди в сервис-брокере построены на основе таблиц. То есть данные сообщения и транзакционные бизнес-данные в случае SQL Server имеют одинаковую природу и хранятся вместе, что разом снимает массу головной боли, присущей архитектуре MSMQ, вообще всем очередникам общего назначения. Однако это накладывает свою специфику - сервис-брокер есть составная часть SQL Server и работает только с ним. Это не есть излишне строгое ограничение, поскольку SQL Server является нынче неотъемлемым атрибутом каждого уважающего себя разработчика. Кстати, все уже поставили себе 2010 СТР2 (https://technet.microsoft.com/en-us/evalcenter/ee315247.aspx)? Сравнение сервис-брокера с MSMQ можно посмотреть, например, здесь - https://www.devx.com/dbzone/Article/34110.
Берем два сервера, которые хотят иметь промеж собой сношение посредством сообщений. Мне доводилось немного рассказывать и показывать веселые картинки про сервис-брокер, поэтому первый сервер я традиционно назову Маша. Второй та часть аудитории, чье воспитание испорчено MySQL или Oracle, не менее традиционно предлагает назвать Вовочка или в том же духе. У нас он будет называться Дубровский. Это будет культурный сервер, т.е. на нем тоже будет стоять SQL Server. Маша и Дубровский будут взаимодействовать при помощи сервис-брокера. Сервис-брокер имеется в SQL Server 2005 и выше и входит во все редакции, включая бесплатный Express. Единственно, Express может общаться только со взрослой редакцией. Между собой экспрессы взаимодействовать через сервис-брокер не могут. Теорию на тему сервис-брокера можно почитать в официальной документации . Режет слух "образцы" вместо "примеры", а так ничего, читать можно. В конце концов, ведь не пробы, не выборки, не шаблоны и не сэмплы. Желающие могут обратиться к классике - « A first look at SQL Server 2005 for developers », гл. 15, либо посмотреть сравнительно недавнюю книжицу про сервис-брокер в версии 2008. Она так и называется - « Pro SQL Server 2008 Service Broker ». Читать обе смысла нет, т.к. по сравнению с 2005 сервис-брокер радикальных изменений не претерпел, и автор последней сильно позаимствовал.
В моем случае по бедности роль Маши и Дубровского будет попеременно выполнять один извращенный сервер. Маша хочет послать Дубровскому сообщение <Preved/>. Как водится, выберем для экспериментов отдельную базу. На ней должен быть включен сервис-брокер. Сервис-брокер может быть включен в том числе на системных базах, кроме master и mssqlsystemresource. Включение брокера выполняется при помощи команды
alter database tempdb set enable_broker with rollback immediate
Эта операция требует exclusive access на базу, поэтому, возможно, понадобится указать with rollback immediate, иначе она будет выполняться бесконечно. Выключение, соответственно, делается set disable_broker. При выключении база теряет возможность отправлять и принимать сообщения, однако все ранее созданные сервисы, очереди, контракты, типы сообщений, диалоги остаются. Проверить, включен ли сервис-брокер над базой можно при помощи
select is_broker_enabled from sys.databases where name = 'tempdb'
Также сильно рекомендуется иметь на базе мастер-ключ. Иначе она, возможно, будет работать, но до поры до времени . В один прекрасный момент сообщения перестанут уходить, а будут навечно оседать в transmission queue (служебная очередь на отправителе, куда сваливаются сообщения перед отправкой). В колонке transmission_status можно узреть причину:
select cast(message_body as xml) as [Мое неотправленное сообщение], transmission_status from sys.transmission_queue
Мое неотправленное сообщение
The session keys for this conversation could not be created or accessed. The database master key is required for this operation.
Поэтому от греха лучше сделать
create master key encryption by password = 'AbraCadabra'
На случай повторного воспроизведения демки добавляю предварительную зачистку местности. Я-то все равно при написании поста гонял ее неоднократно, и для чистоты эксперимента приходилось каждый раз удалять все, что насоздавалось в процессе. Тип сообщения нельзя убить, покуда имеется хотя бы один использующий его контракт, а контракт - покуда есть хотя бы один использующий его сервис. Имена, начинающиеся с https://, в данном случае пижонство. Можно было начинать их с //Adventure-Works.com/ля-ля-ля, можно было просто назвать Sample_Contract.
if exists(select 1 from sys.services where name = 'Masha') drop service Masha
if exists(select 1 from sys.services where name = 'Dubrovsky') drop service Dubrovsky
if exists(select 1 from sys.service_contracts where name = 'https://blogs.msdn.com/alexejs/ContractMashi&Dubrovskogo')
drop contract [https://blogs.msdn.com/alexejs/ContractMashi&Dubrovskogo]
Создаем новый тип сообщения. Тип сообщения - это именованная декларация нашего намерения, что сообщение должно соответствовать определенному формату. Например, такой-то XSD. Тогда после VALIDATION = надо написать VALID_XML WITH SCHEMA COLLECTION и указать ее. Это то, что создается при помощи оператора CREATE XML SCHEMA COLLECTION . Можно просто сказать, что это будет любой правильный XML (WELL_FORMED_XML). Можно вообще сказать, что в этом сообщении ничего не будет (EMPTY), потому что уже название типа сообщения говорит получателю, что он должен сделать по его получении. Например, пример из BOL. Там создается тип сообщения что-то типа ShowMeYourSysprocesses, по получении которого получатель лезет в соответствующий DMV и отсылает результаты в ответ на это сообщение. Есть еще тип NONE. Означает, что написано. Никакой валидации, сообщение может нести в себе что угодно. Используется, когда в теле лежит не XML, а просто varbinary(max).
if exists(select 1 from sys.service_message_types where name = 'https://blogs.msdn.com/alexejs/MashinMessageType')
drop message type [https://blogs.msdn.com/alexejs/MashinMessageType]
create message type [https://blogs.msdn.com/alexejs/MashinMessageType] validation = well_formed_xml
Взаимодействие на основе сервис-брокера построено по принципу двухстороннего общения. Сторона, начинающая общение, называется инициатором, а тот, к кому она обращается, - TARGET. Можно оговорить, что любая сторона вправе начать общение (ANY). В контракте прописываются определенные ранее типы сообщений, которыми будут обмениваться стороны. Например, в ответ на сообщение типа ShowMeYourSysprocesses, посланное инициатором, target отвечает сообщением типа HereMyCurrentSysprocesses. На самом деле, кроме того, какое сообщение будет первым, контракт не задает никакого дальнейшего порядка обмена сообщениями или workflow. Сервис-брокер вообще не регулирует эти вещи. В ответ на просьбу показать системные процессы target может показать их, например, два раза.
create contract [https://blogs.msdn.com/alexejs/ContractMashi&Dubrovskogo]
[https://blogs.msdn.com/alexejs/MashinMessageType] sent by initiator
Очередь - это место для хранения сообщений, которые ждут своей обработки. В очереди отправки находятся сообщения, ожидающие отправки, в очереди приема - пока с ними сделают что-нибудь осмысленное с точки зрения бизнес-смысла. Чтобы обеспечить гарантированную доставку, сервис-брокер отправителя помещает отправленное сообщение в служебную очередь transmission_queue, пока не получит подтверждения от получателя. В очередь приема на получателе сообщение попадает, когда сервис-брокер получателя удостоверится, что его доставка на уровне транспорта успешно завершена и тип валидный (отвечает типу, прописанному в контракте). Тогда он автоматически высылает отправителю подтверждение, и тот удаляет сообщение из transmission_queue. Читать из очереди можно оператором SELECT, как из таблицы, а читать и забирать - оператором RECEIVE. Опция RETENTION все равно позволяет сохранить все сообщения в очереди до завершения общения. Это делается обычно для нужд отладки или аудита. Опция ACTIVATION позволяет назначить обработчика, который делает над прочитанным из очереди сообщением что-либо осмысленное с точки зрения бизнес-смысла. Обработчиком может быть хранимая процедура на T-SQL или CLR или вообще какая-то внешняя программа (как в случае Event Notification). Обработчик нужен для того, чтобы срабатывать, как триггер, по приходу в очередь нового сообщения.
if exists(select 1 from sys.service_queues where name = 'QueueMashi') drop queue QueueMashi
create queue QueueMashi
if exists(select 1 from sys.service_queues where name = 'QueueDubrovskogo') drop queue QueueDubrovskogo
create queue QueueDubrovskogo
Под очереди на самом деле заводятся внутренние таблицы, которые можно видеть в sys.internal_tables. Например,
select * from sys.internal_tables where name like 'queue_messages_%'
покажет сейчас 5 табличек, соответствующих очередям: 2 пользовательские (QueueMashi и QueueDubrovskogo) и 3 служебные (QueryNotificationErrorsQueue, EventNotificationErrorsQueue, ServiceBrokerQueue).
Сервис-брокер построен по принципам SOA (сервис-ориентированной архитектуры), поэтому понятие очередей сообщений в нем отделено от понятия конечных точек, которые порождают эти сообщения и которым они, в конечном счете, предназначены. Эти конечные точки называются сервисами. Сервис садится на очередь и оговаривает контракт, по которому он готов общаться. На одной очереди может сидеть несколько сервисов.
create service Masha on queue QueueMashi ([https://blogs.msdn.com/alexejs/ContractMashi&Dubrovskogo])
create service Dubrovsky on queue QueueDubrovskogo ([https://blogs.msdn.com/alexejs/ContractMashi&Dubrovskogo])
Практически, все. На этом Маша готова отправлять сообщение Дубровскому. Абстракция, в рамках которой происходит общение (обмен сообщениями), называется беседой. Все сообщения отправляются в рамках какой-либо беседы. Как отмечалось выше, в настоящий момент беседа является синонимом диалога, т.е. двустороннего общения точка-точка. В теории, наверно, можно представить себе трилоги, когда между собой общаются три SQL Servera, вообще, n-логи, а также монологи, когда SQL Server вещает, ни к кому конкретно не обращаясь. Это уже после того, как они пообщались на троих. В книжках про 2005-й на эту тему писали "A monologue is a one-way conversation between a single publisher service and several subscriber services. Currently, monologues are not supported in Service Broker, but they may be included in a future version of Service Broker", откуда она перекочевала в книжки про 2008-й и перекочует в 2008 R2.
declare @ch uniqueidentifier
begin dialog conversation @ch from service Masha to service 'Dubrovsky' on contract [https://blogs.msdn.com/alexejs/ContractMashi&Dubrovskogo] with encryption = off
declare @msg xml = '<Preved/>'
;send on conversation @ch message type [https://blogs.msdn.com/alexejs/MashinMessageType] (@msg)
--end conversation @ch
Смотрим, что-нибудь появилось в очереди получения?
select * from QueueDubrovskogo
Да, там сейчас одна строка, которая есть отправленное Машей сообщение. Колонка conversation_handle содержит гуид беседы, которая была открыта Машей в операторе begin dialog conversation, однако он не равен @ch, т.к. это гуид беседы, автоматически созданной в соответствие @ch на стороне таргета (Дубровского). Можно отправить ответ на сообщение в рамках той же беседы. Колонка message_sequence_number есть identity, которое будет монотонно возрастать с каждым новым сообщением, чтобы обеспечить тот же порядок поступления сообщений, что и при их отправке, в пределах данной беседы. Далее поля можно не пояснять: service_name, service_contract_name, message_type_name, - они знакомы нам по Скриптам 8, 6, 5. Мessage_body - это отправленный XML в двоичном виде:
select cast(message_body as xml) from QueueDubrovskogo
(1 row(s) affected)
Receive отличается от select тем, что не только читает, но и убирает прочитанное сообщение из очереди:
;receive top (1) @x = cast(message_body as xml) from QueueDubrovskogo
После этого очередь QueueDubrovskogo пустеет.
Получить список существующих диалогов, какие сервисы в них общаются и по каким очередям, можно при помощи запроса: