Для лучшего понимания того, о чём пойдёт речь в этой статье, необходимо ознакомиться с моей предыдущей публикацией "Планы обмена. Управляемый режим блокировок".
Рассмотрим работу плана обмена на небольшом примере. Зарегистрируем изменение. Таблицы плана обмена и регистрации изменений будут выглядеть следующим образом:
Таблица плана обмена | Таблица регистрации изменений | |||||||||||
|
|
Теперь выполним выгрузку этого изменения. Вид этих таблиц изменится следующим образом:
Таблица плана обмена | Таблица регистрации изменений | |||||||||||
|
|
Номер сообщения изменился. Это означает, что наше изменение было успешно отправлено в сообщении номер 1. Теперь зарегистрируем изменение другого объекта того же типа.
Таблица плана обмена | Таблица регистрации изменений | ||||||||||||||
|
|
Если теперь снова попытаться выполнить выгрузку, то в выборку изменений попадут оба изменения. Это означает, что изменение, отправленное ранее в сообщении номер 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. Короткие транзакции - хорошие транзакции.