bdd2

Планы обмена. Квитировать или гарантировать?

Опубликовал Дмитрий Жичкин (zhichkin) в раздел Программирование - Практика программирования

Планы обмена предлагают использовать две стратегии удаления обработанных изменений: квитирование и гарантированная доставка сообщений. Как сделать правильный выбор?

Для лучшего понимания того, о чём пойдёт речь в этой статье, необходимо ознакомиться с моей предыдущей публикацией "Планы обмена. Управляемый режим блокировок".

Рассмотрим работу плана обмена на небольшом примере. Зарегистрируем изменение. Таблицы плана обмена и регистрации изменений будут выглядеть следующим образом:

Таблица плана обмена Таблица регистрации изменений
Узел Номер отправленного
Мой узел NULL
Узел Ссылка Номер сообщения
Мой узел Моя ссылка 1 NULL

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

Таблица плана обмена Таблица регистрации изменений
Узел Номер отправленного
Мой узел 1
Узел Ссылка Номер сообщения
Мой узел Моя ссылка 1 1

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

Таблица плана обмена Таблица регистрации изменений
Узел Номер отправленного
Мой узел 1
Узел Ссылка Номер сообщения
Мой узел Моя ссылка 1 1
Мой узел Моя ссылка 2 NULL

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

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

Квитирование.

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

Запрос = Новый Запрос();
Запрос.Текст = "
|ВЫБРАТЬ
|	Т1.Ссылка КАК Узел,
|	СУММА(Т1.НомерОтправленного - Т2.НомерСообщения) КАК Количество
|ИЗ
|	ПланОбмена.Тестовый КАК Т1
|	ЛЕВОЕ СОЕДИНЕНИЕ
|		Справочник.Номенклатура.Изменения КАК Т2
|	ПО Т1.Ссылка = Т2.Узел
|	 И Т1.НомерОтправленного <= Т2.НомерСообщения
|	 И НЕ Т2.НомерСообщения ЕСТЬ NULL
|СГРУППИРОВАТЬ ПО
|	Т1.Ссылка
|";

Гарантированная доставка сообщений.

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

Однако у этой стратегии есть один нюанс: как понять, что изменение успешно попало в канал гарантированной доставки? Оказывается, что для этого планы обмена реализуют своеобразный проткол двухфазной фиксации транзакции. По сути своей это распределённая транзакция. Давайте посмотрим как это работает:

1-ая фаза помечает новые изменения для выгрузки при помощи метода ПланыОбмена.ВыбратьИзменения(), который записывает очередной номер сообщения для новых изменений. Блок № 4 на диаграмме ниже.

НомерСообщения > НомерОтправленного (мы находимся в открытой транзакции)

2-ая фаза фиксирует успешное завершение выгрузки при помощи метода ЗаписьСообщенияОбмена.ЗакончитьЗапись(), который записывает номер отправленного сообщения для узла плана обмена. Блок № 7 на диаграмме ниже.

НомерСообщения = НомерОтправленного (транзакция завершилась успешно)

Схематично весь этот процесс я постарался сделать наглядным при помощи следующей диаграммы:

На этой диаграмме состояние одного изменения показано при помощи блоков 2, 5 и 8. Блоки 3 - 7 являются одним сеансом обмена данными. Независимо от того какую стратегию удаления обработанных изменений мы используем, диаграмма будет одной и той же. Разница заключается только в методике удаления изменений.

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

Обратите внимание, что, в случае использования стратегии гарантированной доставки сообщений, в контексте очередного сеанса обмена данными, нас интересуют только новые изменения (блок 2) и изменения, которые возникли в результате сбоев прошлых сеансов обмена (блок 5). Это является существенным отличием от стратегии квитирования.

Заключение.

На основании анализа работы планов обмена я позволю себе дать следующие рекомендации по их использованию в высоко нагруженных системах:

1. Используйте стратегию гарантированной доставки сообщений, а не квитирование.

2. Для того, чтобы доставка была действительно гарантированной, не изобретайте велосипедов - используйте проверенные MQ-решения, например, MSMQ, ActiveMQ или RabbitMQ.

Основные конфликты (см. диаграмму выше) возникают между блоками 1 и 4 за изменение состояния блока 2. Отсюда следующая рекомендация:

3. Постарайтесь сделать транзакции блока 4 как можно короче. Помечайте на выгрузку не все изменения сразу. Разделите их на какие-то адекватные вашей ситуации порции. В качестве одной из идей реализации этой задачи можно посмотреть мою публикацию "Многопоточная выгрузка одного сообщения обмена".

В особо тяжёлых случаях вместо "ВыбратьИзменения" вполне можно использовать две отдельные команды.

Первая - код SQL вида (псевдокод, как идея)

UPDATE TOP 1000 SET _MessageNo = _SentNo + 1 WHERE _MessageNo IS NULL,

а вторая - выборка изменений кодом 1С с отбором вида

ГДЕ НомерСообщения > НомерОтправленного ИЛИ НомерСообщения ЕСТЬ NULL.

Почему код SQL? Дело в том, что "ВыбратьИзменения" помимо обновления номера сообщения возвращает выборку, которая может быть сразу и не нужна ... Ну и потом одна хорошая команда SQL никогда не помешает =)

Вторым по значимости конфликтом (см. диаграмму выше) является конфликт между блоками 1 и 9 за изменение состояния блоков 8. Здесь рекомендация такая же, как и в пункте 3. Короткие транзакции - хорошие транзакции.

См. также

Добавить вознаграждение
Комментарии
1. Сан Саныч (herfis) 52 12.12.16 11:07 Сейчас в теме
ИМХО, гораздо интереснее была бы статья, описывающая "живой" опыт построения РИБ с гарантированной доставкой на сервисах очередей и описанием проблем, с которыми пришлось столкнуться.
А так, 90% статьи - невнятное описание стандартных механизмов и потрясающее заключение: лучше быть богатым и здоровым, оказывается.
2. Антон Чарушкин (hulio) 21 13.12.16 10:53 Сейчас в теме
Обычно вместо использования сторонних сервисов (RabbitMQ, MSMQ и т.д.) изобретаю свой велосипед решаю задачу средствами самой же 1С: делаю очередь сообщений, например, в виде регистра сведений: туда пишу сообщение обмена и сразу удаляю регистрацию изменений.
WellMaster; +1 Ответить 1
3. bulpi bulpi (bulpi) 100 15.12.16 15:08 Сейчас в теме
"ГДЕ НомерСообщения > НомерОтправленного И НомерСообщения ЕСТЬ NULL"

В результате такого условия выберется ровно НИЧЕГО.

Эта статья в целом написана неряшливо и непонятно.
4. WellMaster (WellMaster) 97 22.12.16 11:17 Сейчас в теме
Вопрос к знатокам, берем модификацию имеющегося примера:

"Номер сообщения изменился. Это означает, что наше изменение было успешно отправлено в сообщении номер 1. Теперь зарегистрируем изменение другого объекта того же типа.
Если теперь снова попытаться выполнить выгрузку, то в выборку изменений попадут оба изменения. Это означает, что изменение, отправленное ранее в сообщении номер 1, будет отправлено ещё раз."

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

Как я полагаю, когда квитанция вернется, регистрация к обмену снимется, и объект останется в этом узле измененный, а в новый контур обмена не полетит, и в другой базе он останется в старой версии.

Поправьте меня, если я не прав.
5. Дмитрий Жичкин (zhichkin) 183 22.12.16 12:19 Сейчас в теме
(3) Спасибо. Логическую ошибку в условии отбора исправил.
6. Дмитрий Жичкин (zhichkin) 183 22.12.16 12:26 Сейчас в теме
(4) Если Вы измените тот же самый объект после его выгрузки в пакет, то получите новую регистрацию того же самого объекта со значением реквизита "НомерСообщения" равным NULL. Когда квитанция "прилетит" она будет искать кого бы снять с регистрации по номеру, а он будет равен NULL, то есть квитанция никого не найдёт и новые изменения попадут в следующий пакет обмена.
Подробнее здесь: http://infostart.ru/public/561460/
7. Дмитрий Жичкин (zhichkin) 183 22.12.16 13:02 Сейчас в теме
(2) Приведу пример из своей практики.
Для одного очень тяжёлого обмена, тестовый пакет XML которого "весил" 100 Мб, мы делали так :
1. Своя регистрация изменений в регистре сведений в хронологическом порядке (по сути очередь).
2. Свой компактный формат XML, имеющий только те данные, которые изменились (гранулярность регистрации изменений - поле таблицы, реквизит объекта).
3. Использование для доставки SQL Server Service Broker (транспорт).
4. Загрузка пакетов XML в 1С средствами C# с генерацией T-SQL кода (для работы с XML на уровне SQL Server писались свои CLR хранимые процедуры).
Целевым временем загрузки XML пакета "весом" 1 Гб считалось 10 минут. Это без учёта выгрузки и транспорта. Мы укладывались в это время. Даже с запасом.
В результате была полностью решена проблема блокировок при регистрации и выгрузке изменений. Загрузка в общем-то тоже не доставляла каких-то особенных проблем. Удаление регистрации изменений никому не мешало, так как удалялись уже обработанные сообщения из очереди (регистра сведений) по дате последней выгрузки. Все традиционные проблемы РИБ были полностью решены.
Проблемы, с которыми столкнулись: сложность программирования всего этого. Три человека в течение одного-полутора месяцев всё это делали: программист 1С, программист C# и программист SQL. В процессе эксплуатации проблем не было вообще. Мы даже иногда забывали, что обмен работает =) Вспоминали только когда сеть "ложилась" и какие-то данные не долетали до нужных баз - пользователи напоминали. Админы "шевелили" сеть и всё снова работало.
В обмене участвовало 4 базы: оперативная (торговля), web-портал B2B, база для отчётов и диспетчерская база для маршрутизации пакетов при помощи Service Broker. Как-то так.
8. WellMaster (WellMaster) 97 22.12.16 15:11 Сейчас в теме
(6)
Да, статью по ссылке я читал, причем еще тогда, когда она впервые вышла. Довольно познавательно.
Но нужен был именно ответ на такой вопрос, за что спасибо.
В той статье этот момент не очевиден (или я плохо читал?), хотя читал ее неоднократно.