Чтение RSS
Рефераты:
 
Рефераты бесплатно
 

 

 

 

 

 

     
 
MSSQL 2005 (Yukon) – работа с очередями и асинхронная обработка данных

MSSQL 2005 (Yukon) – работа с очередями и асинхронная обработка данных

Ivan Bodyagin

Несколько общих слов

Этот очерк посвящен трудностям, с которыми сталкивается разработчик при попытке построить полноценное асинхронное приложение, а также той посильной помощи, которую может оказать компания Microsoft в этом нелегком предприятии благодаря следующей версии SQL Server с кодовым именем Yukon и сопутствующих библиотек.

Безусловно, тема асинхронности весьма обширна, и ее невозможно охватить в одной статье, даже если ограничиваться исключительно рамками SQL Server-а, но я и не ставил перед собой задачи охватить все. Здесь будет дан краткий обзор новой функциональности, которая появится в MS SQL Server с выходом новой версии, наиболее важной на мой взгляд, и несколько примеров использования этой функциональности...

Асинхронность

Я несколько раз подкрадывался к своим знакомым и пытался неожиданно спросить, что же они понимают под асинхронностью – выяснилось, что все понимают, что это такое, но никто не может дать четкого определения.

Так что же такое асинхронность? Формальное определение говорит, что это такая характеристика процессов, не совпадающих во времени. Коротко, емко, но непонятно... Если же упростить, то это возможность свалить часть работы на кого-то другого, а за результатом прийти потом, занимаясь в промежутке своими делами. И это относится как к однотипной работе, так и к совершенно разноплановой. Наверное, уместно было бы прибегнуть к аналогии... Допустим, существует два способа отдать автомобиль в сервис – синхронный и асинхронный. В синхронном варианте можно приехать на сервис, пообщаться с механиком, загнать вместе с ним машину в бокс, помочь ему дружеским советом, рассказать пару свежих анекдотов или услышать их от него... Тоже в общем-то с пользой проведенное время. Если же просто отдать ключи механику при встрече и забрать машину, когда она будет готова, проведя промежуток времени между двумя этими событиями по своему усмотрению, то это уже будет асинхронный способ ремонта...

Точно так же обстоит дело и в приложении. Поток можно распараллелить на несколько потоков, как выполняющих одну и ту же работу, разделив ее на части, так и совершенно разную, например, одновременно считывать что-то с диска и решать вычислительные задачи.

Интуитивно все ощущают, что асинхронные приложения во всех отношениях лучше, но почему-то пишут их только в самом крайнем случае.

Асинхронное приложение снижает зависимость между процессами – вы в достаточно широких пределах не зависите от того, как долго механик возится с вашей машиной, если вернуться к нашей аналогии. Оно лучше масштабируется – при увеличении нагрузки достаточно подключить второй процессор или второй сервер. В общем случае оно надежнее – если один сервер вышел из строя, то остальные продолжают выполнять работу. Этот список можно продолжать довольно долго. Но всю эту радужную картину рушит одно – асинхронные приложения чертовски сложно разрабатывать.

Главная проблема асинхронных приложений состоит в сложности коммуникации между независимыми процессами. Вот такой забавный парадокс: выясняется, что асинхронные приложения необходимо уметь правильно синхронизировать, и от качества этой синхронизации зависит очень многое. В идеальном варианте обмен информации между асинхронными потоками сам по себе должен быть асинхронным, обеспечивать транзакционность, гарантию доставки, гарантию очередности, групповую обработку и много других скучных вещей... В одном отдельно взятом приложении может и не потребоваться вся эта функциональность, но практически любой крупный проект с поддержкой асинхронной работы включает в себя разработку некоего фреймфорка для реализации асинхронности, что само по себе задача не тривиальная...

Однако Microsoft с выпуском SQL Server 2005 и сопутствующих клиентских библиотек решил взять часть этой нудной работы на себя.

Асинхронные возможности сервера

Для начала рассмотрим, какие возможности предоставляет новый SQL Server сам по себе, без учета возможностей клиента и ADO.Net 2.0

Начнем, пожалуй, издалека. Фраза о работе с очередями недаром вынесена в название этой статьи, так как механизм очередей является неотъемлемой частью хорошей реализации асинхронности. Как правило, в асинхронном приложении есть, условно говоря, «основной поток», который раздает некоторые задания «служебным потокам» и впоследствии забирает от них результаты. Одним из важных моментов является именно процесс выдачи задания и получения результатов. Дело в том, что служебные потоки не всегда находятся в распоряжении главного. Тому есть множество причин. Число потоков, с которыми можно работать эффективно, ограничено, и свободных потоков, готовых выполнить задание, может просто не быть, или же служебный поток может вовсе находиться на другой машине... Если основной поток при обмене информацией будет взаимодействовать непосредственно со служебными, то ему придется ждать служебные потоки, а это подрывает саму идею асинхронности. И тут на помощь приходят очереди. Они позволяют разорвать зависимость основного потока от служебных. Основному потоку достаточно поместить задания в очередь и идти дальше по своим делам. Служебные потоки, как только у них появится такая возможность, заберут из очереди задание и будут его выполнять, после чего опять-таки поместят результаты в соответствующую очередь, дабы основной поток забрал их, когда у него появится время. И даже если служебный поток находится на другой машине, то при наличии очередей не составит никакого труда инициировать транспортную транзакцию при поступлении задания в очередь, опять-таки не заставляя основной поток ждать

В грядущей версии SQL Server есть готовый механизм очередей (как одна из основных частей Service Broker). Однако если по каким-то причинам разработчику приходится строить очередь самостоятельно, то и для этого появились некоторые новые возможности.

Output или расширения обработки очередей

Посвященная этой функциональности глава в разделе BOL «новые возможности» называется Queue Processing Extensions - расширения обработки очередей. Но на самом деле, это всего лишь одно из самых очевидных применений данного механизма. Суть функциональности заключается в следующем: теперь у ряда операторов, занимающихся манипуляцией с данными, а именно INSERT, UPDATE и DELETE, появилось новое ключевое слово OUTPUT. С помощью этой конструкции можно после выполнения оператора получить результат его работы и перенаправить этот результат в какую-нибудь таблицу или просто вернуть клиентскому приложению. Если говорить проще, появился доступ к триггерным псевдотабличкам inserted и deleted прямо из запроса. Иными словами, теперь есть возможность узнать, что же именно было изменено DML-оператором, не обращаясь лишний раз к серверу.

Основное предназначение данной конструкции, как следует из названия раздела, это работа с очередями, но подробнее об этом будет сказано чуть позже, а пока разберем непосредственно механику.

Простейший пример может выглядеть примерно так:

-- создаем тестовую таблицу:

--

CREATE TABLE OutputTest (

 ID int IDENTITY,

 [Time] datetime default getDate(),

 Limit as Left(Data, 8),

 Data char(50))

-- собственно, проверяем, как оно работает:

--

INSERT INTO OutputTest (Data) OUTPUT INSERTED.* VALUES (NewID())

-- наслаждаемся результатом:

--

ID  Time           Limit   Data

1  2005-05-21 19:40:43.087 5C1D39E9  5C1D39E9-8E28-4ED7-B5E8-938EA84FFE18

Как легко заметить, вся магия заключается в конструкции OUTPUT INSERTED.*, обратите внимание, что в тестовой таблице присутствует колонка identity, колонка со значением по умолчанию и колонка с вычисляемым значением. При этом данные, полученные из inserted-таблички, содержат уже посчитанные значения в этих колонках. То есть табличка inserted содержит фактические значения вставляемых данных уже после внутренних вычислений, однако триггеры не учитываются, то есть отработка output происходит после внутренних вычислений, но перед выполнением триггеров. Например, при наличии триггера INSTEAD OF на таблице, изменяющая эту таблицу операция в output вернет все данные, которые должны там быть, даже если в результате работы триггера никаких изменений не произойдет.

ПРЕДУПРЕЖДЕНИЕ

На самом деле тут есть одно исключение, если на табличке висит триггер INSTEAD OF, то значение IDENTITY в OUTPUT INSERTED вычислено не будет.

К выборке output можно применять различные выражения и подзапросы, в том случае если они возвращают одно значение. Например, если есть необходимость в момент изменения записи узнать, сколько времени прошло с момента последнего обновления, то запрос может выглядеть примерно так:

UPDATE OutputTest SET Data = newID(), [Time] = GetDate()

OUTPUT DateDiff(ss, DELETED.[Time], INSERTED.[Time]) Diff

В результате выполнения такого запроса получится рекордсет с одной записью, в которой будет содержаться количество секунд, прошедшее между изменениями записи.

В предыдущих примерах результат работы output отправлялся прямо в клиентское приложение, но можно перенаправить его и в таблицу – обычную, временную и табличную переменную. Сделать это довольно просто:

DECLARE @tmp_output TABLE (

 ID_t int, Time_t datetime,

 Limit_t nvarchar(8),

 Data_t nvarchar(50))

INSERT INTO OutputTest (Data)

 OUTPUT inserted.* INTO @tmp_output

 VALUES (newid())

В данном случае вывод был перенаправлен в табличную переменную. В то же время, на таблицы, в которые производится вывод, наложено несколько ограничений:

На них не должно быть назначено триггеров. В принципе, триггер может быть назначен, но должен быть в состоянии Disabled.

Они не должны быть связаны внешним ключом с другими таблицами, и на эту таблицу не должны ссылаться внешние ключи.

Не должно быть CHECK-ограничений и правил (rules) в состоянии Enabled.

Вывод не может быть перенаправлен во view или функцию. Очевидно, это связано с запретом триггеров в целевой таблице.

Сложно сказать, с чем эти ограничения связаны, но, скорее всего, это вызвано стремлением максимально облегчить и ускорить вставку данных. Поскольку механизм output работает до триггеров, на довольно низком уровне, то желание избавиться от тяжелой функциональности вполне объяснимо.

И еще несколько общих ограничений механизма output:

Секционированные представления и удаленные таблицы не могут быть источником output.

В случае оператора INSERT источником output не могут быть view.

Порядок записей, выдаваемых output, не гарантируется.

Если вызов output происходит в триггере, и вывод из output не перенаправляется в таблицу, то, очевидно, опция disallow results from triggers не должна быть установлена, в противном случае произойдет исключение.

Также, если не происходит перенаправления вывода output, то изменяемая таблица не должна иметь активных триггеров на данную операцию модификации. Например, если происходит INSERT c output, без перенаправления вывода в таблицу, то триггеров на INSERT быть не должно, хотя UPDATE и DELETE триггеры вполне могут быть.

Как это использовать

Как ясно из названия данной функциональности, Microsoft предлагает использовать ее для работы с очередями. Во-первых, конструкцию DELETE … OUTPUT удобно применять для разгребания очереди, выполняя чтение и удаление прочитанной записи одним движением.

DELETE FROM output_test OUTPUT deleted.*

В комбинации с хинтом READPAST, можно организовать разгребание очереди из нескольких потоков одновременно, если есть такая необходимость. Ранее для этого приходилось использовать как минимум два запроса с явным блокированием нужных записей.

Во-вторых, механизм output можно использовать и для формирования очередей. В данном случае это сильно напоминает работу обычного триггера, в задачу которого входит складывать измененные данные в отдельную таблицу. Но триггер будет срабатывать для всех изменений без исключения, а output можно использовать только в определенных DML-операторах. Иными словами, если таблица может изменяться из двух мест, то триггер будет срабатывать в обоих случаях, а output можно использовать только для одного из них. Да и использовать output, наверное, будет проще, чем триггер.

Применить данный механизм с пользой можно и в отрыве от очередей. Как уже упоминалось ранее, результат output возвращается после подсчета значений по умолчанию, identity и вычисляемых столбцов, и это может оказаться довольно полезным.

Однако основную проблему коммуникации между асинхронными процессами такой подход не решает – это всего лишь небольшой синтаксический сахарок, несколько облегчающий работу с собственноручно написанными очередями, но не более того. В идеале же механизм обмена, как уже говорилось, должен обеспечивать транзакционность, отсутствие дубликатов, автоматическую работу с очередями, обработку групп сообщений, гарантировать очередность и т.д… Все это богатство было реализовано и вошло в следующую версию SQL Server под именем Service Broker.

Service Broker

При первом взгляде на Service Broker возникает вопрос: "А что во что, собственно, встроено, СУБД в подсистему сообщений или наоборот. :)"

С одной стороны, полноценная подсистема работы с сообщениями должна иметь собственное надежное хранилище, без этого невозможно реализовать все богатство, описанное в предыдущем разделе. И ребята из Редмонда с присущим им размахом решили, что раз СУБД у них уже есть, то почему бы ей не послужить в роли хранилища данных для подсистемы рассылки сообщений? С другой же стороны, использовать SQL Server исключительно в роли хранилища для одной подсистемы, пусть и очень мощной, тоже как-то неправильно... Так и родилось то, что мы исследуем сейчас: без SQL Server-а работа подсистемы сообщений невозможна, но при наличии подсистемы сообщений SQL Server перестает быть просто хранилищем данных, как это было в недавнем прошлом.

Вышеупомянутая подсистема (под названием Service Broker) представляет собой очень мощный механизм, способный принести немало пользы. Описание этого сервиса может составить целую книгу. Кстати, вскоре одна такая выйдет, однако здесь будет описана лишь небольшая часть этой функциональности, необходимая для понимания дальнейшего материала.

СОВЕТ

Книга, целиком посвященная Service Broker и выходящая в самое ближайшее время, называется The Rational Guide To SQL Server 2005 Service Broker Beta Preview (http://www.mannpublishing.com/Catalog/BookDetail.aspx?BookID=37). Она написана Роджером Уолтером (Roger Wolter), основная работа которого заключается как раз в руководстве группой, разрабатывающей этот самый Service Broker. Так что лучше него вряд ли кто-нибудь об этом механизме расскажет. :)

Эта книга содержит много интересных и захватывающих подробностей, которые, к сожалению, выходят за рамки статьи, но рассмотрим вкратце, как все работает – процесс создания, посылки и получения сообщения.

Работа с Service Broker-ом реализована через набор объектов, которые управляются посредством обычных DDL операторов CREATE, ALTER, DROP - ничего нового. Команды по работе с этими объектами также являются небольшим DML расширением T-SQL. Например, команда получения сообщения возвращает обычный реляционный набор данных и мало чем отличается от SELECT, так что тут не должно быть никаких сложностей. В посылке и получении сообщения участвуют следующие объекты (здесь приведены далеко не все, лишь необходимый минимум):

QUEUE (очередь): Service Broker использует очереди для того, чтобы не было зависимости между отправителем и получателем сообщения. Отправитель просто помещает сообщение в очередь и идет заниматься своими делами, не дожидаясь получателя, а доставку сообщения возлагает на плечи собственно Service Broker-а, будучи уверенным, что тот справится. Получатель же может забрать и обработать сообщение, когда ему будет удобно, зная, что его деятельность никоим образом не влияет на эффективность работы отправителя, а все сообщения выстроены в должном порядке. При этом есть возможность запустить несколько «получателей» одновременно, добиваясь тем самым параллельной обработки очереди для достижения большей эффективности.

DIALOG (диалог): Одной из частей подсистемы доставки сообщений является диалог. Эту конструкцию можно рассматривать как двунаправленный поток (stream) между двумя конечными точками, по которому курсируют сообщения. Все сообщения в диалоге добираются до получателя ровно в том порядке, в котором были отосланы отправителем. Этот порядок сохраняется, невзирая ни на какие сбои, транзакции, архивирования базы, погодные условия, курс доллара и прочие внезапные факторы.

На самом деле, диалог является частным случаем общения (conversation). Conversation – это не объект, а более низкоуровневое понятие – постоянный, надежный канал связи. В MSSQL 2005 диалог – единственный тип общения, но в следующих версиях обещают добавить monolog (монолог), однонаправленный поток один-ко-многим, и, возможно, что-то еще. Однако в текущей версии диалог и общение можно считать синонимами.

Каждое сообщение включает в себя метку общения, которая уникально определяет диалог (а в будущем и другие типы потоков), ассоциированный с этим сообщением, что позволяет легко определить, откуда пришло сообщение, если поддерживается несколько каналов связи.

MESSAGE TYPE (тип сообщения): Любое сообщение должно быть ассоциировано с определенным типом. Это метка, которая передается вместе с сообщением и позволяет получателю понять, какого типа сообщение к нему приехало. По желанию, если сообщение представляет собой XML, то метка может быть ассоциирована с произвольной XML-схемой. В этом случае при получении производится проверка соответствия сообщения этой схеме, и если сообщение проверку не проходит, то оно отвергается.

CONTRACT (контракт): Контракт – это набор типов сообщений, при этом для каждого типа сообщения в контракте указывается, кому из участников диалога сообщение данного типа может принадлежать: отправителю, получателю или им обоим. Каждый диалог обязательно ассоциируется с контрактом. Иными словами, если тип сообщения не соответствует ни одному типу из контракта, ассоциированного с диалогом, то передать такое сообщение по этому диалогу не получится.

SERVICE (сервис): Сервис связывает несколько контрактов с очередью. Имя сервиса является синонимом для конечной точки диалога. Таким образом, контракт определяет, сообщения каких типов могут быть посланы через очередь посредством диалога, а сервис является конечной точкой, через которую сообщение попадает в очередь.

Схематично, весь этот зоопарк можно изобразить примерно следующим образом:

Отправитель и получатель – достаточно условные понятия, они имеют смысл только на этапе создания канала общения. После того, как канал создан, сообщения могут свободно гулять в обоих направлениях. Иными словами, отправитель – это та сторона, которая создает диалог. При этом само создание диалога не вызывает создания физического канала связи, канал создается лишь в тот момент, когда необходимо доставить первое сообщение, и удерживается до тех пор, пока диалог не завершится.

Через сервис отправителя сообщение попадает в очередь отправителя, если соответствует одному из типов сообщения, упомянутых в контракте. После этого оно передается в очередь получателя, где при получении также проходит проверку, после чего забирается оттуда собственно получателем. Если же получатель желает как-то ответить, то он в свою очередь формирует сообщение и отправляет его через свой сервис, и оно путешествует обратно тем же манером.

Теперь самое время приступить к практическим экспериментам. Для начала создадим все необходимые объекты:

-- тип сообщения, просто текст, для простоты безо всяких проверок и xml

--

CREATE MESSAGE TYPE [TestType] VALIDATION = NONE

-- теперь можно создать контракт, разрешающий сообщения этого типа

-- для любой из сторон

--

CREATE CONTRACT [TestContract] ([TestType] SENT BY ANY)

-- для отправляющей стороны необходимо создать очередь

-- и сервис на основе этой очереди

--

CREATE QUEUE [SourceQueue]

CREATE SERVICE [SourceService] ON QUEUE [SourceQueue]

-- Для принимающей стороны так же нужно создать принимающую очередь

-- и принимающий сервис, причем принимающий сервис обязательно

-- должен иметь контракт, хотя для отправляющего это не обязательно

--

CREATE QUEUE [TargetQueue]

CREATE SERVICE [TargetService] ON QUEUE [TargetQueue] ([TestContract])

Все, объекты готовы. Теперь можно приступать собственно к передаче сообщения. В данном примере рассматривается самый простой вариант: из отправляющего сервиса сообщение попадает в очередь получателя и забирается оттуда.

Сначала займемся получателем. Для получения сообщения служит команда RECEIVE, которая сильно напоминает обычный SELECT, только вместо имени таблицы указывается имя очереди. К слову, и команда SELECT для очереди работает (поскольку с точки зрения базы данных очередь – это обычная таблица), показывая ее содержимое, но ничего из нее не удаляя. Команда же RECEIVE выбирает данные из очереди, удаляя выбранные сообщения. Однако если очередь пуста, RECEIVE отработает вхолостую и вернет пустой набор данных, а хотелось бы, чтобы кто-то караулил очередь, и RECEIVE бы срабатывала, как только в очереди что-то появится. К счастью, в этом нет ничего сложного, достаточно обернуть RECEIVE в WAITFOR. Итак, в отдельном окне выполняем следующую команду для своевременного получения сообщения:

WAITFOR(RECEIVE cast(message_body as nvarchar(MAX)) FROM [TargetQueue])

После выполнения этой команды подключение замрет в ожидании сообщения из очереди. Теперь самое время заняться отправителем. У него задачка посложнее, надо начать диалог и передать сообщение с идентификатором открытого диалога.

DECLARE @convHandler uniqueidentifier

-- начало диалога

--

BEGIN DIALOG  @convHandler

  FROM SERVICE  [SourceService]

  TO SERVICE   'TargetService'

  ON CONTRACT   [TestContract];

-- посылка сообщения

--

SEND ON CONVERSATION @convHandler

  MESSAGE TYPE [TestType] (N'Message!!!')

-- завершение диалога

--

END CONVERSATION @convHandler

Если после отправки сообщения вернуться в окошко, где ожидали его получения, можно увидеть, что сообщение успешно получено.

Стоит заметить, что TargetService при создании диалога взят в кавычки, а SourceService – нет. Дело в том, что TargetService может быть создан на совершенно другом сервере, и просто отсутствовать на сервере, где начинается диалог такого сервиса.

Как можно видеть из примера, в каком именно диалоге отправлять сообщение, определяется некой меткой (handler), которая возвращается при создании диалога, и представляет собой GUID. Если ее в какой-то момент потерять, то завершить диалог можно будет только административными методами, узнав этот GUID из служебных представлений (catalog view). Эта же метка приезжает к получателю вместе с сообщением, и выбрав эту метку из очереди, можно отправить сообщение обратно в том же диалоге.

Асинхронные триггеры

Теперь рассмотрим, как можно использовать коммуникативные возможности Service Broker на сервере. Например, можно использовать его для реализации асинхронных триггеров, причем не только для DML- и DDL-операций, но и для событий, отслеживаемых профайлером (trace events), и если DML-триггеры придется реализовывать отчасти с применением обычных, то для DDL-триггеров и событий профайлера предусмотрен специальный механизм.

Асинхронные DML-триггеры

Начнем с DML, идея которых, в общем-то, должна быть очевидна. Допустим, у нас есть очень большая таблица (Very_Big_Table), для отчетов по которой надо периодически считать некие агрегатные значения. Поскольку таблица очень большая, то агрегаты считаются очень долго. Отчет не всегда должен быть актуальным, но всегда – согласованным, и строиться должен максимально быстро. Это значит, что в идеале агрегаты должны быть посчитаны заранее. Делать пересчет данных в обычном триггере накладно для операций обновления, так как расчет агрегатов происходит долго, как уже было упомянуто. И тут на помощь приходит Service Broker. В обычном триггере на изменение Very_Big_Table создается диалог (строго говоря, мало что мешает создать диалог заранее, разве что проблемы с запоминанием метки при развертывании) и отправляется сообщение, о том что таблица изменилась. Это занимает минимум времени, а изменяющий процесс идет дальше заниматься своими делами. Получатель же начинает не торопясь пересчитывать эти занудные агрегаты, чтобы к моменту, когда понадобится отчет, все уже было готово.

Вот как это может выглядеть. Сначала создадим необходимые тестовые таблички:

CREATE TABLE Very_Big_Table(ID int IDENTITY, Data bigint, [Time] DateTime)

GO

-- заполним таблицу данными

--

INSERT INTO Very_Big_Table(Data, [Time])

SELECT object_id, create_date FROM sys.objects

GO

-- табличка для вычисленного агрегата

--

CREATE TABLE Big_Aggregate(Agg bigint, [Time] DateTime)

GO

-- Ну и проинициализируем ее

--

INSERT INTO Big_Aggregate(Agg, [Time])

SELECT Sum(Data), GetDate() FROM Very_Big_Table

Теперь триггер на изменение очень большой таблички. Здесь мы сильно мудрствовать не будем, воспользуемся уже готовыми метаданными из предыдущего примера:

CREATE TRIGGER AsyncAggregate ON Very_Big_Table

 FOR INSERT, UPDATE, DELETE

AS

 DECLARE @convHandler uniqueidentifier

 BEGIN DIALOG  @convHandler

  FROM SERVICE  [SourceService]

  TO SERVICE   'TargetService'

  ON CONTRACT   [TestContract];

  SEND ON CONVERSATION @convHandler

   MESSAGE TYPE [TestType] (N'The data hase been changed')

 END CONVERSATION @convHandler

GO

Передавать в сообщении никакой ценной информации нам не надо, так как принимающая сторона должна просто узнать, о том, что таблица поменялась, а признаком этого служит сам факт доставки сообщения. Более того, в данной ситуации нет необходимости даже вызывать команду SEND, так как закрытие диалога (END CONVERSATION) вызывает посылку специального сообщения об этом печальном событии на принимающую сторону. Однако в реальной ситуации может понадобиться передать некоторую информацию, и если ее необходимо структурировать, то придется воспользоваться XML.

Теперь займемся принимающей стороной. Для начала создадим процедуру пересчета агрегата:

CREATE PROCEDURE AggRecalculate AS

 -- очистка очереди

 --

 RECEIVE * FROM [TargetQueue]

 -- небольшая задержка для имитации действительно долгого расчета

 --

 WAITFOR DELAY '00:00:02'

 UPDATE Big_Aggregate

  SET Agg = (SELECT SUM(Data) FROM Very_Big_Table),

    [Time] = GetDate()

GO

Процедура готова, но есть одна проблема. Как выполнить эту процедуру при появлении сообщения в очереди? Конечно, можно, как и раньше, обернуть RECEIVE в WAITFOR, но в этом случае кто-то должен запусить процедуру, чтобы она начала ждать сообщений из очереди. И мало того, сообщение-то у нас может быть не одно. Значит, нужно чтобы после получения кто-то активизировал процедуру снова. Другими словами, нужен некий монитор, который следил бы за состоянием очереди и при появлении в ней сообщений вызывал нашу процедуру. К счастью, все уже сделано за нас. Такой монитор имеется в Service Broker, и для его включения достаточно немного изменить параметры очереди, указав, какую процедуру надо вызвать при получении сообщения:

ALTER QUEUE [TargetQueue]

 WITH ACTIVATION(

  STATUS = ON,

  PROCEDURE_NAME = AggRecalculate,

  MAX_QUEUE_READERS = 1,

  EXECUTE AS OWNER)

Ключевое слово здесь, конечно же, ACTIVATION, то есть активация. Однако если параметр STATUS у нее выставлен в OFF, она не сработает. Как несложно догадаться, в параметре PROCEDURE_NAME указывается имя процедуры, которая будет вызвана при активации, а в EXECUTE AS – от имени какого пользователя эта процедура будет вызвана. Параметр MAX_QUEUE_READERS определяет максимальное количество процедур, которое одновременно может быть запущено для разгребания очереди. Если во время работы процедуры поступили новые сообщения, то запускается еще один экземпляр этой процедуры, и так до максимального разрешенного количества или опустошения очереди.

Теперь все готово для эксперимента, можно приступать. Сначала обновим нашу «очень_большую_таблицу», и тут же заберем данные из таблички агрегатов, затем подождем чуть-чуть и снова заберем агрегированные данные, чтобы увидеть, как они изменились после работы процедуры перерасчета, автоматически запущенной Service Broker-ом.

UPDATE Very_Big_Table SET Data = Data + 10 WHERE ID=1

SELECT * FROM Big_Aggregate

WAITFOR DELAY '00:00:05'

SELECT * FROM Big_Aggregate

-- Результат:

--

Agg         Time

-------------------- -----------------------

76577545551     13:44:37.987

76577545561     13:59:24.630

Как можно заметить, все отлично сработало, произошло асинхронное обновление агрегатной таблицы. Вся прелесть в том, что агрегатная таблица может находиться на совершенно другом сервере в другой стране, надо только настроить подключение соответствующим образом, что совсем не сложно.

Асинхронные DDL и SQL-Trace триггеры (Event Notification)

Для реализации асинхронных триггеров на DDL-операции и события профайлера существует специальный механизм, Event Notification (извещение о событии).

ПРИМЕЧАНИЕ

Надо учитывать, что в связи с асинхронностью данного механизма породившие это извещение изменения в базе или на сервере, не отменятся в случае отката извещения, как это было бы в DDL-триггере. Они – уже свершившийся факт. И еще один нюанс: поскольку события профайлера работают вне транзакций, то даже если изменение на сервере, вызвавшее посылку сообщения, не увенчается успехом, то само сообщение все равно будет доставлено до получателя, однако для DDL-событий это не работает, так как DDL-операции работают в рамках транзакции и в случае отмены DDL транзакции сообщение отправлено не будет.

Как не сложно догадаться, этот механизм отслеживает события, на которые есть подписчики, и посылает соответствующее сообщение. Для того чтобы механизм сообщений заработал, достаточно создать очередь и сервис получателя с предопределенным контрактом [http://schemas.microsoft.com/SQL/Notifications/PostEventNotification], все остальное - и контракт, и диалог, и сервис с очередью отправителя, уже реализовано. Затем надо создать объект EventNotification, связывающий нужное событие с сервисом – и готово. На практике, допустим, для асинхронного аудита подключений к серверу и отключений от оного, это может выглядеть следующим образом:

-- сначала создадим очередь получателя, при желании

-- здесь можно назначить процедуру обработки новых сообщений

--

CREATE QUEUE [LoginQueue]

GO

-- затем необходимо создать сервис со специальным контрактом,

-- в котором уже есть необходимые типы сообщений

--

CREATE SERVICE [LoginService] ON QUEUE [LoginQueue](

 [http://schemas.microsoft.com/SQL/Notifications/PostEventNotification])

GO

-- Ну а теперь можно создать и сам Event Notification, связывающий

-- серверные события с сервисом доставки сообщения

--

CREATE Event Notification auditLogin

 ON SERVER FOR Audit_Login, Audit_Logout

 TO SERVICE 'LoginService', 'current database'

Здесь ‘current database’ – это константа, которая говорит о том, что в качестве механизма доставки будет использоваться экземпляр Service Broker-а, установленный в текущей базе. Указание этого экземпляра является необходимым параметром при создании уведомления.

Вот, в общем-то, и все. Теперь осталось только получить красивый XML из очереди после очередного входа/выхода из системы и придумать, что с ним делать. Получить его можно все тем же, уже знакомым способом:

RECEIVE cast(message_body as xml) FROM [LoginQueue]

Сам XML представляет собой результат вызова той же самой функции Eventdata(), что используется и в DDL-триггерах.

Комбинируя рассмотренную функциональность, можно добиться довольно причудливого поведения, например, активируя очередь, по таймеру проверять загрузку процессора и занимаемую сервером память. И если нагрузка на сервер достаточно низка, то отсылать другое сообщение, которое приведет к запуску долгой расчетной процедуры...

Асинхронные возможности клиентских приложений

Теперь самое время рассмотреть подробнее, какие возможности для работы с базой данных в асинхронном режиме есть у клиентского приложения.

Как можно было убедиться из предыдущих примеров, отправить задание на выполнение кому-то другому – это полдела, надо еще вовремя получить извещение о том, что асинхронная операция закончена. При разработке механизмов взаимодействия клиента с сервером этому вопросу было уделено должное внимание.

Асинхронное выполнение запросов

Помимо выполнения асинхронных операций на сервере и работы с очередями, в ADO.Net 2.0 добавлена специальная функциональность по асинхронной работе с БД со стороны клиента. Эта функциональность поддерживается только провайдером SqlClient (OleDB и остальные его не поддерживают). Зато (приятная новость) со стороны сервера жестких ограничений нет, и асинхронные запросы будут работать с SQL Server от Microsoft, начиная с седьмой версии, при условии, что режим работы с ними – не Shared Memory, а операционная система – Windows 2000/XP/2003.

Строго говоря, и в предыдущей версии Framework-а организация асинхронной обработки данных не была такой уж большой проблемой. Однако при этом приходилось выделять дополнительный поток и блокировать его в ожидании выполнения запроса. Для клиентских приложений это не представляет большой проблемы, но для серверных решений, вынужденных обслуживать множество клиентов одновременно, это может послужить источником неприятностей. Вся же прелесть данной реализации заключается в том, что дополнительный поток не создается. Вместо этого для достижения должного эффекта используются возможности асинхронного сетевого ввода/вывода. Вместо того, чтобы создавать новый поток и заставлять его ждать синхронной операции ввода/вывода для отправки запроса в БД и получения ответа, используются асинхронные возможности сетевого протокола Windows 2000/XP/2003 (с этим и связаны ограничения на использование ОС и режима Shared Memory для версий сервера ниже SQL 2005), позволяющие одному потоку отослать запрос и идти дальше по своим делам.

Для выполнения запросов в асинхронном режиме разработчики добавили несколько методов, однако придерживались минималистской политики, и добавили лишь самые необходимые методы. Поэтому далеко не все синхронные варианты Execute* обзавелись асинхронными аналогами, точнее, только три из них. Это ExecuteReader, получивший BeginExecuteReader и EndEsecuteReader, ExecuteNonQuery, получивший BeginExecuteNonQuery и EndExecuteNonQuery, и ExecuteXmlReader, получивший, как не сложно догадаться BeginExecuteXmlReader и EndExecuteXmlReader. Предполагается следующая схема применения этого богатства: метод Begin* получает все входные параметры и передает их для исполнения серверу, оставляя после себя потоку на память лишь некий объект, реализующий специальный интерфейс по имени IAsyncResult. Этот интерфейс может быть использован для отслеживания состояния выполнения операции. Из метода же End* с помощью этого оставленного объекта можно получить обратно результат выполнения запроса, когда тот будет готов.

СОВЕТ

Интерфейс IAsyncResult далеко не нов, как и сопутствующие методы синхронизации. Вся эта механика должна быть хорошо знакома тем, кто сталкивался с асинхронными делегатами, асинхронной работой с файлами и другими многопоточными задачами в .Net.

Одним из ключевых моментов при выполнении асинхронных операций является способ, с помощью которого инициатор операции может узнать о ее завершении. Для столь ответственного мероприятия ADO.Net предоставляет целых три способа:

Функция обратного вызова (callback). Все методы Begin* имеют перегруженный вариант, принимающий на вход делегат вместе с неким пользовательским объектом, в котором, в случае необходимости, можно передать текущее состояние. После того, как результат будет готов, произойдет вызов делегата. При этом необходимо учитывать, что делегат будет вызван из другого потока, поэтому могут потребоваться некоторые дополнительные действия для обеспечения синхронизации.

Объект синхронизации. Объект IAsyncResult, возвращаемый всеми методами Begin*, содержит свойство WaitHandle с событием, и это событие может быть использовано такими примитивами синхронизации, как WaitHandle.WaitAny и WaitHandle.WaitAll. Это позволяет вызывающему потоку дожидаться выполнения нескольких или всех запущенных операций, причем не только запросов к БД, но и, возможно, других асинхронных процедур или вызовов ОС, которые также обслуживаются вышеупомянутыми примитивами.

Опрос (Polling).

 
     
Бесплатные рефераты
 
Банк рефератов
 
Бесплатные рефераты скачать
| Интенсификация изучения иностранного языка с использованием компьютерных технологий | Лыжный спорт | САИД Ахмад | экономическая дипломатия | Влияние экономической войны на глобальную экономику | экономическая война | экономическая война и дипломатия | Экономический шпионаж | АК Моор рефераты | АК Моор реферат | ноосфера ба забони точики | чесменское сражение | Закон всемирного тяготения | рефераты темы | иохан себастиян бах маълумот | Тарых | шерхо дар борат биология | скачать еротик китоб | Семетей | Караш | Influence of English in mass culture дипломная | Количественные отношения в английском языках | 6466 | чистонхои химия | Гунны | Чистон | Кус | кмс купить диплом о language:RU | купить диплом ргсу цена language:RU | куплю копии дипломов для сро language:RU
 
Рефераты Онлайн
 
Скачать реферат
 
 
 
 
  Все права защищены. Бесплатные рефераты и сочинения. Коллекция бесплатных рефератов! Коллекция рефератов!