Sql Server Service Broker — подробный пример используемого консольного приложения, активируемого извне.

Мне нужно руководство от любого, кто развернул реальное рабочее приложение, в котором используется механизм внешней активации Sql Server Service Broker (через внешний активатор Service Broker из Feature Pack).

Текущее мышление:

Мои спецификации довольно просты (по крайней мере, я так думаю), поэтому я думаю о следующем основном потоке:

  1. сущность, похожая на заказ, вставляется в Table_Orders со статусом "подтверждено"

  2. SP_BeginOrder выполняется и делает следующее:

    • begins a TRANSACTION
    • запускает ДИАЛОГ из Service_HandleOrderState в Service_PreprocessOrder
    • сохраняет дескриптор диалога (теперь PreprocessingHandle) в определенном столбце таблицы Orders
    • отправляет СООБЩЕНИЕ типа Message_PreprocessOrder, содержащее идентификатор заказа, используя PreprocessingHandle
    • завершает ТРАНЗАКЦИЮ

    Обратите внимание, что я не заканчиваю разговор, я не хочу "выстрелил и забыл"

  3. уведомление о событии в Queue_PreprocessOrder активирует экземпляр PreprocessOrder.exe (максимальное число одновременных действий: 1), который выполняет следующие действия:

    • begins a SqlTransaction
    • получает первое СООБЩЕНИЕ от Queue_PreprocessOrder
    • if message type is Message_PreprocessOrder (format XML):
      • sets the order state to "preprocessing" in Table_Orders using the order id in the message body
      • загружает n наборов данных, из которых вычисляется n-арный декартовский продукт (через Linq, AFAIK это невозможно в T-SQL), чтобы определить коллекцию элементов заказа
      • вставляет строки элементов заказа в Table_OrderItems
      • отправляет СООБЩЕНИЕ типа Message_PreprocessingDone, содержащее тот же идентификатор заказа, используя PreprocessingHandle
      • завершает диалог, относящийся к PreprocessingHandle
    • фиксирует SqlTransaction
    • выходит с Environment.Exit(0)
  4. internal activation on Queue_HandleOrderState executes a SP (max concurrent of 1) that:
    • begins a TRANSACTION
    • получает первое СООБЩЕНИЕ от Queue_InitiatePreprocessOrder
    • if message type is Message_PreprocessingDone:
      • sets the order state to "processing" in Table_Orders using the order id in the message body
      • запускает ДИАЛОГ из Service_HandleOrderState в Service_ProcessOrderItem
      • сохраняет дескриптор диалога (теперь ProcessOrderItemsHandle) в определенном столбце Table_Orders
      • creates a cursor for rows in Table_OrderItems for current order id and for each row:
        • sends a MESSAGE of type Message_ProcessOrderItem, containing the order item id, using ProcessOrderItemsHandle
    • if message type is Message_ProcessingDone:
      • sets the order state to "processed" in Table_Orders using the order id in the message body
    • if message type is http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog (END DIALOG):
      • ends the conversation pertaining to conversation handle of the message
    • завершает ТРАНЗАКЦИЮ
  5. event notification on Queue_ProcessOrderItem activates an instance of ProcessOrderItem.exe (max concurrent of 1) which does the following:
    • begins a SqlTransaction
    • получает первое СООБЩЕНИЕ от Queue_ProcessOrderItem
    • if message type is Message_ProcessOrderItem (format XML):
      • sets the order item state to "processing" in Table_OrdersItems using the order item id in the message body, then:
        • loads a collection of order item parameters
        • делает HttpRequest к URL-адресу, используя параметры
        • сохраняет HttpResponse как PDF в файловой системе
      • если на вышеуказанных подшагах возникли какие-либо ошибки, устанавливает состояние элемента заказа в «ошибка», в противном случае «ОК»
      • выполняет поиск в Table_OrdersItems, чтобы определить, все ли элементы заказа обработаны (состояние "ok" или "error")
      • if all order items are processed:
        • sends a MESSAGE of type Message_ProcessingDone, containing the order id, using ProcessOrderItemsHandle
        • завершает диалог, относящийся к ProcessOrderItemsHandle
    • фиксирует SqlTransaction
    • выходит с Environment.Exit(0)

Примечания:

  • specs specify MSSQL compatibility 2005 through 2012, so:
    • no CONVERSATION GROUPS
    • нет ПРИОРИТЕТА РАЗГОВОРА
    • нет POISON_MESSAGE_HANDLING (STATUS = OFF)
  • Я стремлюсь достичь общей целостности и непрерывности потока, а не скорости
  • учитывая, что таблицы и SP находятся в DB1, а объекты Service Broker (сообщения, контракты, очереди, службы) находятся в DB2, DB2 УСТАНАВЛИВАЕТСЯ НАДЕЖНЫМ

Вопросы:

  1. Есть ли в описанной архитектуре какие-либо серьезные конструктивные недостатки?
  2. Отслеживание состояния выполнения заказа кажется неправильным. Есть ли лучший метод? Может быть, с помощью ОЧЕРЕДИ УДЕРЖАНИЯ?
  3. Моя интуиция подсказывает мне, что ни в коем случае активированный внешний exe не должен заканчиваться с кодом выхода, отличным от 0, поэтому в Main должно быть try{..}catch(Exception e){..} finally{ Environment.Exit(0) }. Верно ли это предположение?
  4. Как бы вы организовали обработку ошибок в коде БД? Достаточно ли таблицы журнала ошибок?
  5. Как бы вы организовали обработку ошибок во внешнем exe-коде C#? Та же таблица регистрации ошибок?
  6. Я видел примеры продуктов SQL Server Service Broker, но интерфейс Service Broker кажется излишним для моего, казалось бы, более простого кейс. Есть ли альтернативы более простой объектной модели Service Broker?
  7. Любой переносной инструмент администрирования для Service Broker с кросс-версией, способный, по крайней мере, сливать ядовитые сообщения?
  8. Есть ли у вас достойные образцы кода для любого из вышеперечисленного?

person Stefan Anghel    schedule 28.11.2012    source источник


Ответы (1)


Вопрос: Есть ли в описанной архитектуре какие-либо существенные недостатки дизайна?
О: Пара небольших преимуществ:
- ожидание завершения HTTP-запроса при сохранении открытой транзакции – это плохо. В любом случае вы не можете добиться согласованности транзакций между базой данных и HTTP, поэтому не рискуйте растянуть транзакцию на несколько минут, когда HTTP работает медленно. Типичный шаблон: {begin tran/receive/начать таймер диалога/ commit} затем выполните HTTP-вызов без какой-либо базы данных xact. Если HTTP-вызов успешен, {begin xact/send response/end talk/commit}. Если HTTP не работает (или клиент дает сбой), позвольте времени разговора снова активировать вас. Вы получите сообщение таймера (без тела), вам нужно выбрать идентификатор элемента, связанный с дескриптором, из вашей таблицы (таблиц).

В: Отслеживание состояния выполнения заказа кажется неправильным. Есть ли лучший метод? Может быть, использовать ОЧЕРЕДИ УДЕРЖАНИЕ?
О: Моя единственная критика отслеживания состояния заключается в зависимости от сканирования элементов заказа, чтобы определить, что текущий обработанный элемент является последним (5.3.4). Например, вы можете добавить информацию о том, что это «последний» элемент, подлежащий обработке, в состоянии элемента, чтобы вы знали, что при его обработке вам нужно сообщить о завершении. RETENTION полезен только при отладке или когда у вас есть логика, требующая запуска «логического отката» и компенсирующих действий при ошибке диалога.

В: Моя интуиция подсказывает мне, что ни в коем случае активированный внешний exe не должен завершаться с кодом выхода, отличным от 0, поэтому должен быть try{..}catch(Exception e){..} finally{ Environment. Выход(0) } в Main. Верно ли это предположение?
О: Самое главное, чтобы активированный процесс выдал оператор RECEIVE в очереди. Если этого не сделать, монитор очереди может войти в состояние уведомления навсегда. Код выхода, если я правильно помню, не имеет значения. Как и в случае с любым фоновым процессом, важно перехватывать и регистрировать исключения, иначе вы даже не узнаете, что у него есть проблема, когда он начнет давать сбой. Помимо дисциплинированных блоков try/catch, Hookup Application.ThreadException для приложений с пользовательским интерфейсом и AppDomain.UnhandledException для обоих Пользовательский интерфейс и приложения без пользовательского интерфейса.

В: Как бы вы организовали обработку ошибок в коде БД? Достаточно ли таблицы журнала ошибок?
О: Я уточню этот вопрос позже. Имхо таблицы журнала ошибок достаточно.

В: Как бы вы организовали обработку ошибок во внешнем exe-коде C#? Та же таблица протоколирования ошибок?
О: Я создал bugcollect.com именно потому, что мне приходилось справляться с такими проблемами. с моими собственными приложениями. Проблема заключается не только в ведении журнала, вам также нужны некоторые агрегация и анализ (по крайней мере, обнаружение повторяющихся отчетов) и подавление потока ошибок из-за какого-либо сбоя конфигурации развертывания «в поле». По правде говоря, в настоящее время есть больше вариантов, например. exceptron.com. И, конечно же, я думаю, что FogBugs также имеет возможности ведения журнала.

В: Я видел примеры продуктов SQL Server Service Broker, но интерфейс Service Broker кажется излишним для моего, казалось бы, более простого случая. Есть ли альтернативы более простой объектной модели Service Broker?
Наконец, простой вопрос: да, это излишество. Нет простой модели.

Вопрос. Любой "переносимый" инструмент администрирования для Service Broker с кросс-версией, способный по крайней мере удалять опасные сообщения?
О. Проблема с подозрительными сообщениями заключается в том, что определение подозрительного сообщения меняется вместе с вашим кодом. : ядовитое сообщение — это любое сообщение, которое нарушает установленные в настоящее время средства защиты для его обнаружения .

В: Есть ли у вас достойные примеры кода для любого из вышеперечисленного?
О: Нет

Еще один момент: старайтесь избегать любой ссылки из DB1 в DB2 (например, 4.3.4 активируется в DB1 и читает таблицу элементов из DB2). Это создает зависимости между базами данных, которые разрываются, когда а) одна БД находится в автономном режиме (например, для обслуживания) или перегружена или б) вы добавляете зеркальное отображение базы данных для высокой доступности/аварийного восстановления, а одна БД отказывает. Попробуйте заставить код работать, даже если DB1 и DB2 находятся на разных машинах (и не связаны между собой серверами). При необходимости добавьте дополнительную информацию в полезные данные сообщений. И если вы спроектируете его таким образом, что DB2 может находиться на другом компьютере, и даже может существовать несколько компьютеров DB2 для масштабирования работы по записи HTTP/PDF.

И наконец: этот дизайн будет очень медленным. Я говорю о медленных десятках сообщений в секунду, с таким количеством вовлеченных диалогов/сообщений и всего с max_queue_readers 1. Это может быть или не быть приемлемым для вас.

person Remus Rusanu    schedule 29.11.2012
comment
Вопрос 1. Значит, когда ProcessOrderItem.exe получает сообщение DialogTimer, он ВСЕГДА должен использовать BEGIN CONVERSATION TIMER, а затем повторять HTTP-запрос? - person Stefan Anghel; 29.11.2012
comment
Вопрос 2. Вы предлагаете установить флаг IsLastItem для элементов заказа. Не будет ли это конфликтовать с сообщениями DialogTimer, так как ProcessOrderItem.exe достигнет последнего элемента заказа и установит состояние заказа на обработанное ДО получения DialogTimer сообщения? - person Stefan Anghel; 29.11.2012
comment
Q1: да, всегда планируйте повторную попытку, зафиксируйте, а затем выполните работу. Это на 100% отказоустойчиво, не оставляет условий гонки, когда систему можно оставить без запланированного таймера повтора. Вы можете рассмотреть причудливые вещи, такие как экспоненциальная отсрочка и общее ограничение по времени (например, сдаться, если каждая повторная попытка не удалась через 1 день), но простота не обязательно означает плохо;) - person Remus Rusanu; 03.12.2012