Использование событий данных SQL Server для целей обмена сообщениями

В нашей организации у нас есть база данных SQL Server 2005 и множество клиентов баз данных: веб-сайты (php, zope, asp.net), богатые клиенты (legacy fox pro). Теперь нам нужно передать определенные события из базовой базы данных в другие системы (MongoDb, LDAP и другие). Парадигма сообщений кажется довольно способной решить эту проблему. Поэтому мы решили использовать брокер RabbitMQ в качестве промежуточного программного обеспечения.

Проблема потребления событий из базы данных сначала, казалось, имела только два возможных решения:

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

Мне не понравилась первая идея из-за проблем с задержкой, возникающих при периодическом выполнении sql.

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

  • Строка вставляется в таблицу.
  • Триггер запускает и отправляет сообщение (используя хранимую процедуру CLR, написанную на С#)

Все нормально, если транзакция, которая записывает данные, откатывается. В этом случае данные будут согласованы, но сообщение уже отправлено и не может быть отброшено, поскольку триггер срабатывает в момент записи в журнал базы данных, а не во время транзакционного фиксации (что является правильным поведением СУБД).

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

Итак, мои вопросы:

  • Кто-нибудь смог извлечь данные с помощью триггеров?
  • Какие другие способы использования данных могут быть вам полезны?
  • Является ли уведомление о запросе (построенном поверх Service Broker) подходящим в моей ситуации?

Спасибо заранее!

Ответ 1

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

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

Так что же работает? Рассмотрим это: BizTalk Server. Другими словами, вокруг этого проблемного пространства существует целый бизнес, и решения далеки от тривиальных (иначе никто не купил бы такие продукты).

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

  • расцепления. Триггеры, основанные на событиях, в порядке, но не отправляйте сообщение с триггера. Помимо проблемы согласованности при откате, у вас также есть проблема с задержкой в ​​том, что каждая операция DML теперь ожидает внешнего вызова API (RabbitMQ send) и проблемы доступности внешней ошибки вызова API (если RabbitMQ недоступен, ваша БД недоступна). Решение состоит в том, чтобы триггер использовал обычные таблицы в качестве очередей, триггер будет выставлять сообщение в локальной очереди db (т.е. Будет вставляться в эту таблицу) и процесс будет обслуживать эту очередь, выгружая сообщения (т.е. удаляя из таблицы) и перенаправляя их в RabbitMQ. Это отделяет транзакцию от операции RabbitMQ (внешний процесс может видеть сообщение только в том случае, если первоначальный xact совершает), но стоимость - это очевидная добавленная латентность (есть дополнительный переход, локальная таблица выступает в качестве очереди).
  • идемпотентность. Так как RabbitMQ не может регистрироваться в распределенных транзакциях с базой данных, вы не можете гарантировать атомарность операции БД (dequeue из локальной таблицы, действующей как очередь) и операцию RabbitMQ (отправка). Либо один может преуспеть, если другой не удался, и просто нет возможности обойтись без явной поддержки регистрации заявок на участие в транзакциях. Это означает, что приложение будет отправлять повторяющиеся сообщения каждый раз в то время (обычно, когда по какой-то причине ситуация уже идет плохо). И быстрый хэдз-ап: включение в действие явных сообщений "подтверждения" и отправка порядковых номеров - это битва, так как вы быстро обнаружите, что вы изобретаете TCP поверх сообщений, эта дорога вымощена телами.
  • Допуск. По тем же причинам, что и выше, каждый из них теперь передается, и сообщение, которое, как вы считаете, было отправлено, никогда не будет выполнено. Опять же, какой ущерб это причиняет, полностью зависит от бизнеса. Проблема заключается не в том, как предотвратить эту ситуацию (это почти невозможно...), но как обнаружить эту ситуацию и что с ней делать. Никакой серебряной пули, я боюсь.

Вы упомянули об этом Service Broker (тот факт, что Powering Query Notification является наименее интересным аспектом этого...). Будучи платформой для обмена сообщениями, встроенной в SQL Server, которая предлагает гарантию доставки точно в порядке и полностью транслируется, она решит все вышеупомянутые болевые точки (вы можете SEND от триггеров с безнаказанностью вы можете использовать Активацию для решения проблемы с задержкой, вы никогда не увидите дубликат или недостающее сообщение, есть четкая семантика ошибок) и некоторые другие болевые точки Раньше я не упоминал (согласованность резервного копирования/восстановления, так как данные и сообщения находятся на одной единице хранения - база данных, cosnsitnecy переадресации HA/DR, поскольку SSB поддерживает зеркалирование и кластеризацию базы данных и т.д.). Однако обратная связь заключается в том, что SSB способен разговаривать только с другой службой SSB, другими словами, ее можно использовать только для обмена сообщениями между двумя (или более) экземплярами SQL Server. Любое другое использование требует от сторон использования SQL Server для обмена сообщениями. Но если ваши конечные точки - это все SQL Server, то подумайте, что есть несколько широкомасштабных развертываний с использованием Service Broker. Обратите внимание, что конечные точки, такие как php или asp.net, могут считаться конечными точками SQL Server, они просто программируют уровни поверх API БД, другая конечная точка, скажем, должна посылать сообщения с карманных устройств (телефонов) непосредственно в базу данных (и только те 99% времени проходят через веб-службу, что означает, что они могут в конечном итоге достичь SQL Server). Другое соображение заключается в том, что SSB ориентирован на пропускную способностьи надежная доставка, а не низкая латентность. Определенно, это не технология, которую можно использовать для возврата ответа в веб-запросе HTTP, например. Является технологией, которую нужно использовать для отправки для обработки чего-либо, вызванного веб-запросом.

Ответ 2

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

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

Первый компонент - это триггер.

  • Создайте триггер CLR, который подготавливает то, что необходимо сделать при совершении транзакции.
  • Создайте System.Transactions.IEnlistmentNotification, который всегда соглашается быть готовым, и метод void Commit(System.Transactions.Enlistment) выполняет подготовленное действие.
  • В триггере вызовите System.Transactions.Transaction.Current.EnlistVolatile(enlistmentNotification, System.Transactions.EnlistmentOptions.None)

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

Вам также понадобится

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

Второй компонент считывает состояние, которое обновляется триггером. Создайте отдельный компонент CLR, который читает из вашей очереди или состояния, и делает все, что вам нужно (например, отправить сообщение idempotent в систему обмена сообщениями, записать, что оно было отправлено, что угодно). Если этот компонент может выйти из строя (подсказка: он может), вам понадобится форма допуска, которая может принадлежать другой системе. Вы можете достичь низкой латентности, имея триггерный сигнал второго компонента, когда доступно новое состояние.

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