При интеграции систем 1С с внешними веб-сервисами разработчики часто сталкиваются с проблемой нестабильности каналов связи или временной недоступности сторонних ресурсов. Стандартный сценарий: при изменении объекта в 1С мы должны немедленно отправить данные во внешнюю систему. Если сервис вернул ошибку, данные необходимо поместить в буфер (например, в регистр сведений) и пытаться отправить повторно через определенные промежутки времени.
Естественным решением кажется использование регламентного задания, которое будет с заданной периодичностью обрабатывать накопившиеся ошибки. Однако возникает дилемма: стоит ли гонять регламентное задание «вхолостую» каждые 15 минут (или даже чаще), если ошибок нет? В этой статье мы подробно разберем, как правильно организовать такой процесс, проанализируем популярные подходы и выявим критические антипаттерны, которые могут навредить производительности вашей базы данных.
Большинство опытных разработчиков сходятся во мнении, что самый простой и при этом самый надежный способ — это постоянно работающее регламентное задание, которое первым делом проверяет наличие записей в «очереди ошибок» (в специальном регистре сведений).
Рассмотрим, как выглядит типичная схема реализации этого подхода:
ОшибкиОбмена.Проанализируем пример кода для метода регламентного задания:
Процедура ВыполнитьПовторнуюОтправкуДанных() Экспорт
// Выполняем максимально легкий запрос для проверки наличия записей
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
| ОшибкиОбмена.ОбъектОбмена КАК ОбъектОбмена
|ИЗ
| РегистрСведений.ОшибкиОбмена КАК ОшибкиОбмена";
РезультатЗапроса = Запрос.Выполнить();
// Если очередь пуста, мгновенно завершаем работу
Если РезультатЗапроса.Пустой() Тогда
Возврат;
КонецЕсли;
// Если записи обнаружены, начинаем их обработку
Выборка = РезультатЗапроса.Выбрать();
// ... здесь располагается логика повторного обращения к веб-сервису ...
КонецПроцедуры
Почему этот способ хорош: «Холостой» запуск такого задания на современном сервере СУБД занимает считанные миллисекунды. Запрос к индексированной таблице регистра сведений выполняется мгновенно и не создает никакой избыточной нагрузки на сервер. Механизм работает предсказуемо, прозрачно и полностью контролируется стандартным планировщиком 1С.
У многих начинающих разработчиков возникает идея оптимизировать процесс: включать регламентное задание только тогда, когда в регистре появляется первая ошибка, и выключать его (делать неактивным), когда все ошибки успешно обработаны.
Для реализации этой идеи предлагается использовать следующий код управления метаданными:
// Включение регламентного задания при возникновении ошибки
РЗадание = РегламентныеЗадания.НайтиПредопределенное(Метаданные.РегламентныеЗадания.ПовторныйОбменДанными);
РЗадание.Использование = Истина;
РЗадание.Записать();
Внимание! Разберем, почему данный подход является опасным антипаттерном:
Записать() для объекта РегламентноеЗадание приводит к физической записи изменений в системные таблицы базы данных. В высоконагруженных многопользовательских системах частая перезапись настроек регламентных заданий вызывает жесткие взаимоблокировки (deadlocks) и замедляет общую работу системы.Использование у одного и того же регламентного задания.Если вы ведете разработку в современной конфигурации на базе БСП (Библиотека стандартных подсистем), то для решения подобных задач существует специализированный и наиболее правильный инструмент — подсистема «Очередь заданий» (программный интерфейс модуля ОчередьЗаданий).
Этот механизм позволяет полностью отказаться от создания собственных велосипедов в виде выделенных регистров сведений и жестко прописанных регламентных заданий. Вы можете добавлять задания на выполнение динамически.
Рассмотрим, как это работает на практике:
Пример программного добавления задания в очередь БСП:
ПараметрыЗадания = Новый Структура;
ПараметрыЗадания.Вставить("ИмяМетода", "ОбщийМодульИнтеграцияССервисом.ВыполнитьПовторнуюОтправку");
ПараметрыЗадания.Вставить("Параметры", Новый Массив); // Сюда можно передать массив с нужными ссылками
ПараметрыЗадания.Вставить("НачалоВыполнения", ТекущаяДата() + 900); // Запуск ровно через 15 минут (900 секунд)
// Добавляем задание в общую очередь БСП
ОчередьЗаданий.Добавить(ПараметрыЗадания);
При таком подходе, если в системе нет ошибок интеграции, очередь заданий пуста, и никакие фоновые процессы вообще не запускаются, экономя системные ресурсы сервера на 100%.
Если в вашей конфигурации не используется БСП, но вы все равно категорически хотите избежать постоянных «холостых» запусков, хорошей альтернативой станет использование непредопределенных регламентных заданий.
Разберем этот алгоритм по шагам:
Предопределенное.Посмотрим на пример кода создания динамического задания:
// Создание уникального экземпляра задания при появлении ошибки
ИмяЗадания = "ПовторныйОбмен_Группа_1";
Отбор = Новый Структура("Наименование", ИмяЗадания);
НайденныеЗадания = РегламентныеЗадания.ПолучитьРегламентныеЗадания(Отбор);
Если НайденныеЗадания.Количество() = 0 Тогда
НовоеЗадание = РегламентныеЗадания.СоздатьРегламентноеЗадание(Метаданные.РегламентныеЗадания.ШаблонПовторногоОбмена);
НовоеЗадание.Наименование = ИмяЗадания;
НовоеЗадание.Использование = Истина;
// Настраиваем расписание (например, каждые 15 минут)
Расписание = Новый РасписаниеРегламентногоЗадания;
Расписание.ПериодПовтораВТечениеДня = 900; // 15 минут
НовоеЗадание.Расписание = Расписание;
НовоеЗадание.Записать();
КонецЕсли;
В конце успешного выполнения процедуры фонового процесса мы можем просто удалить это задание:
// Удаление задания после успешного завершения всех обменов
ОтобранныеЗадания = РегламентныеЗадания.ПолучитьРегламентныеЗадания(Новый Структура("Наименование", "ПовторныйОбмен_Группа_1"));
Для Каждого ТекущееЗадание Из ОтобранныеЗадания Цикл
ТекущееЗадание.Удалить();
КонецЦикла;
Иногда требуется запустить обработку немедленно при записи определенного события, не дожидаясь расписания. Для этого можно использовать программный запуск метода через механизм фоновых заданий (ФоновыеЗадания.Выполнить). Чтобы избежать параллельного запуска дублирующих процессов, обязательно нужно выполнять проверку на то, не запущено ли уже аналогичное задание в данный момент.
Разберем пример реализации безопасного ручного старта:
&НаСервере
Процедура ЗапуститьПовторныйОбменВручную()
ИмяМетода = Метаданные.РегламентныеЗадания.ШаблонПовторногоОбмена.ИмяМетода;
// Проверяем, выполняется ли уже аналогичное фоновое задание в данный момент
Отбор = Новый Структура;
Отбор.Вставить("ИмяМетода", ИмяМетода);
Отбор.Вставить("Состояние", СостояниеФоновогоЗадания.Активно);
АктивныеЗадания = ФоновыеЗадания.ПолучитьФоновыеЗадания(Отбор);
// Если активных заданий нет, запускаем новое
Если АктивныеЗадания.Количество() = 0 Тогда
НаименованиеЗадания = "Ручной запуск обмена данными";
ФоновыеЗадания.Выполнить(ИмяМетода, , , НаименованиеЗадания);
КонецЕсли;
КонецПроцедуры
В процессе обсуждения подобных задач у некоторых разработчиков возникает соблазн запустить одно фоновое задание, которое будет работать бесконечно, проверяя регистр в цикле и засыпая на 15 минут с помощью функции Приостановить() (или аналогичных методов ожидания).
Почему это категорически запрещено делать:
rphost и удерживает сеансовую лицензию 1С. При накоплении нескольких таких процессов ваш сервер быстро исчерпает свободные ресурсы и начнет зависать.При проектировании систем повтора отправки данных во внешние веб-сервисы лучшим тоном разработки считается уход от фиксированного интервала (например, жестко каждые 15 минут). Вместо этого рекомендуется применять стратегию экспоненциальной задержки.
Если внешний сервис упал, глупо отправлять ему запросы с одинаковой частотой — это может лишь усугубить ситуацию (вы устроите ему своеобразную DDoS-атаку после его поднятия), либо просто впустую потратит ресурсы вашего сервера. Разумнее увеличивать интервал между попытками:
Для реализации этой стратегии добавьте в ваш регистр сведений ОшибкиОбмена два реквизита: КоличествоПопыток (число) и ДатаСледующегоЗапуска (Дата/Время). При каждой неудачной попытке увеличивайте счетчик и рассчитывайте новую дату запуска. Регламентное задание при старте должно выбирать только те записи, у которых ТекущаяДата() >= ДатаСледующегоЗапуска. Это существенно снизит нагрузку на систему и повысит надежность интеграции.