При интеграции 1С со сторонними системами (загрузка заказов, обработка входящих файлов, обмен через FTP или сетевой каталог) разработчики часто сталкиваются с проблемой накопления очередей. Если один файл обрабатывается несколько минут, а новые данные поступают чаще, возникает задержка исполнения. Попытка решить проблему изменением режима запуска регламентного задания на «не фоновый» основана на ошибочном предположении о механизмах работы платформы. Разберем детально, как устроены регламентные задания в 1С, почему фоновый режим является максимально производительным и как правильно архитектурно устранить узкие места при обработке очередей.
Рассмотрим, как платформа 1С выполняет запланированные процедуры на уровне сервера приложений. В клиент-серверном варианте информационной базы существует строгое разделение понятий:
РегламентноеЗадание) — это объект метаданных и расписание в базе данных. Само по себе оно не выполняет код, а лишь указывает платформе, какую процедуру общего модуля и с какой периодичностью необходимо вызвать.ФоновоеЗадание) — это непосредственный исполняемый процесс на стороне кластера серверов 1С. Когда наступает время по расписанию, менеджер кластера запускает процедуру регламентного задания исключительно в виде отдельного фонового задания внутри рабочего процесса rphost.Таким образом, запустить регламентное задание на сервере 1С «не в фоновом режиме» технически невозможно. В файловом варианте информационной базы фоновые потоки эмулируются внутри сеанса клиентского приложения, однако и там они выполняются последовательно и не предоставляют интерактивного интерфейса.
Проанализируем распространенное заблуждение о том, что фоновое задание выполняется медленнее интерактивного. На самом деле фоновый серверный поток — это наиболее быстрый способ исполнения встроенного языка 1С, так как:
Если фоновая обработка одного заказа занимает 4 минуты, причина кроется не в способе запуска, а в структуре алгоритма, блокировках данных или неоптимальной архитектуре чтения.
Разберем ситуацию, когда входящие файлы накапливаются быстрее, чем текущий алгоритм успевает их обрабатывать. Разделим тяжелый монолитный процесс на два независимых этапа: быструю фиксацию входящих сообщений и их асинхронную обработку.
Создадим регистр сведений ОчередьВходящихПакетов со следующими реквизитами:
ИдентификаторПакета (УникальныйИдентификатор / Строка) — измерение.ДанныеФайла (ХранилищеЗначения) — ресурс (бинарные данные заказа).Статус (ПеречислениеСсылка.СтатусыОбработки: Новый, ВРаботе, Ошибка, Обработан) — реквизит.ДатаСоздания (Дата) — реквизит.Посмотрим на код регламентного задания-диспетчера, который за доли секунды считывает файлы из каталога, регистрирует их в очереди и удаляет с диска, исключая повторное чтение:
Процедура ЗагрузитьФайлыВОчередь() Экспорт
КаталогОбмена = "\\Server\Exchange\Incoming\";
МассивФайлов = НайтиФайлы(КаталогОбмена, "*.xml");
Для Каждого НайденныйФайл Из МассивФайлов Цикл
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.ОчередьВходящихПакетов");
ЭлементБлокировки.УстановитьЗначение("ИдентификаторПакета", НайденныйФайл.ИмяБезРасширения);
НачатьТранзакцию();
Попытка
Блокировка.Заблокировать();
ДвоичныеДанные = Новый ДвоичныеДанные(НайденныйФайл.ПолноеИмя);
МенеджерЗаписи = РегистрыСведений.ОчередьВходящихПакетов.СоздатьМенеджерЗаписи();
МенеджерЗаписи.ИдентификаторПакета = НайденныйФайл.ИмяБезРасширения;
МенеджерЗаписи.ДанныеФайла = Новый ХранилищеЗначения(ДвоичныеДанные, Новый СжатиеДанных(9));
МенеджерЗаписи.Статус = Перечисления.СтатусыОбработки.Новый;
МенеджерЗаписи.ДатаСоздания = ТекущаяДатаСеанса();
МенеджерЗаписи.Записать(Истина);
УдалитьФайлы(НайденныйФайл.ПолноеИмя);
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ЗаписьЖурналаРегистрации("Интеграция.Загрузка", УровеньЖурналаРегистрации.Ошибка,,, ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;
КонецЦикла;
КонецПроцедуры
Выясним, как ускорить разбор очереди. Вместо последовательной обработки в один поток запустим параллельные фоновые задания с помощью менеджера ФоновыеЗадания. Каждое фоновое задание будет брать свой отдельный пакет из очереди и обрабатывать его независимо от других.
Посмотрим на реализацию управляющего обработчика, который контролирует число одновременно работающих потоков:
Процедура ОбработатьОчередьПараллельно() Экспорт
МаксимальноеЧислоПотоков = 4;
// Получаем список активных фоновых заданий нашей обработки
Фильтр = Новый Структура;
Фильтр.Вставить("Ключ", "ОбработкаПакетаЗаказов");
Фильтр.Вставить("Состояние", СостояниеФоновогоЗадания.Активно);
АктивныеЗадания = ФоновыеЗадания.ПолучитьФоновыеЗадания(Фильтр);
ДоступноПотоков = МаксимальноеЧислоПотоков - АктивныеЗадания.Количество();
Если ДоступноПотоков <= 0 Тогда
Возврат; // Все рабочие потоки заняты
КонецЕсли;
// Выбираем порцию новых пакетов из регистра сведений
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ " + ДоступноПотоков + "
| Очередь.ИдентификаторПакета КАК ИдентификаторПакета
|ИЗ
| РегистрСведений.ОчередьВходящихПакетов КАК Очередь
|ГДЕ
| Очередь.Статус = ЗНАЧЕНИЕ(Перечисление.СтатусыОбработки.Новый)
|УПОРЯДОЧИТЬ ПО
| Очередь.ДатаСоздания ВОЗР";
Результат = Запрос.Выполнить();
Если Результат.Пустой() Тогда
Возврат;
КонецЕсли;
Выборка = Результат.Выбрать();
Пока Выборка.Следующий() Цикл
// Переводим пакет в статус "ВРаботе"
Если НЕ ЗахватитьПакетДляОбработки(Выборка.ИдентификаторПакета) Тогда
Продолжить;
КонецЕсли;
// Запускаем независимый фоновый поток
ПараметрыПроцедуры = Новый Массив;
ПараметрыПроцедуры.Добавить(Выборка.ИдентификаторПакета);
ПараметрыФонового = Новый Структура;
ПараметрыФонового.Вставить("Наименование", "Обработка пакета " + Выборка.ИдентификаторПакета);
ФоновыеЗадания.Выполнить(
"ИнтеграцияСервер.ВыполнитьОбработкуПакета",
ПараметрыПроцедуры,
"ОбработкаПакетаЗаказов",
ПараметрыФонового.Наименование
);
КонецЦикла;
КонецПроцедуры
Функция ЗахватитьПакетДляОбработки(ИдентификаторПакета)
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.ОчередьВходящихПакетов");
ЭлементБлокировки.УстановитьЗначение("ИдентификаторПакета", ИдентификаторПакета);
НачатьТранзакцию();
Попытка
Блокировка.Заблокировать();
МенеджерЗаписи = РегистрыСведений.ОчередьВходящихПакетов.СоздатьМенеджерЗаписи();
МенеджерЗаписи.ИдентификаторПакета = ИдентификаторПакета;
МенеджерЗаписи.Прочитать();
Если МенеджерЗаписи.Выбран() И МенеджерЗаписи.Статус = Перечисления.СтатусыОбработки.Новый Тогда
МенеджерЗаписи.Статус = Перечисления.СтатусыОбработки.ВРаботе;
МенеджерЗаписи.Записать(Истина);
ЗафиксироватьТранзакцию();
Возврат Истина;
Иначе
ОтменитьТранзакцию();
Возврат Ложь;
КонецЕсли;
Исключение
ОтменитьТранзакцию();
Возврат Ложь;
КонецПопытки;
КонецФункции
Проанализируем, почему создание и проведение одного документа может занимать до 4 минут. Для устранения задержек проверим следующие узкие места в коде процедуры ВыполнитьОбработкуПакета:
НайтиПоНаименованию() / НайтиПоРеквизиту() внутри цикла обхода строк файла. Все сопоставления необходимо загружать одним пакетным запросом с помещением входных данных во временную таблицу (МенеджерВременныхТаблиц).РежимЗаписиДокумента.Запись, а проведение выполняйте отдельным общим регламентным заданием в наименее загруженные часы либо пакетом.Сравним различные подходы к организации пакетной загрузки данных:
Применяя пул параллельных фоновых потоков и разделяя прием данных с их непосредственной бизнес-обработкой, мы устраняем отставание очереди независимо от объема входящих заказов.