Как настроить обмен данными XML по подписке на проведение документа без зависаний и ошибок?

Программист 1С v8.3 (Обычные формы) 1С:Комплексная автоматизация IT и автоматизация бизнеса
← На главную

В процессе интеграции различных конфигураций «1С:Предприятия» разработчики часто сталкиваются с задачей оперативной отправки данных из одной базы в другую. На первый взгляд, идея кажется простой и логичной: настроить подписку на событие проведения документа в базе-источнике, при его записи или проведении запускать фоновое задание, которое через COM-соединение или XML-выгрузку мгновенно передает данные в базу-приемник. Однако при реализации этой схемы «в лоб» разработчики неизбежно сталкиваются с тем, что обмен либо зависает без каких-либо записей в журнале регистрации, либо приводит к серьезным блокировкам базы данных.

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

Анализ исходной проблемы и кода

Рассмотрим ситуацию, с которой столкнулся автор темы. В базе-источнике (например, «Комплексная автоматизация 1.1») настроены правила обмена через «Конвертацию данных 2». На клиенте интерактивно выгрузка отрабатывает отлично. Но при попытке перенести этот код в общий модуль для выполнения на сервере через фоновое задание в подписке на проведение документа, процесс зависает или не стартует вовсе.

Проанализируем исходный код подписки и фонового вызова:


Процедура ПодпискаНаСобытие1ОбработкаПроведения(Источник, Отказ, РежимПроведения) Экспорт
    ФоновыеЗадания.Выполнить("МодульРегламентныхЗаданий.ОбменятьсяДаннымиСБП");
КонецПроцедуры

И процедуру выгрузки на сервере:


Процедура ОбменятьсяДаннымиСБП() Экспорт
    ОбработкаОбмена = Обработки.УниверсальныйОбменДаннымиXML_v3.Создать();
    ОбработкаОбмена.РежимОбмена = "Выгрузка";
    врФайл = ПолучитьИмяВременногоФайла("xml");
    
    МакетПравил = ПланыОбмена.ОбменСБухгалтерияПредприятия3.ПолучитьМакет("ПравилаВыгрузкиКА_БП");
    МакетПравил.Записать(врФайл);
    
    ОбработкаОбмена.ИмяФайлаПравилОбмена = vrFile;
    ОбработкаОбмена.ЗагружатьДанныеВРежимеОбмена = Истина;
    ОбработкаОбмена.ЗаписыватьРегистрыНаборамиЗаписей = Истина;
    ОбработкаОбмена.ЗапоминатьЗагруженныеОбъекты = Истина;
    
    // Настройки COM-подключения к базе-приемнику
    ОбработкаОбмена.ТипИнформационнойБазыДляПодключения = Ложь; // Файловая/Серверная
    ОбработкаОбмена.ВерсияПлатформыИнформационнойБазыДляПодключения = "V83";
    ОбработкаОбмена.ИмяИнформационнойБазыНаСервереДляПодключения = "db_buh_v3";
    ОбработкаОбмена.ИмяСервераИнформационнойБазыДляПодключения = "s1c2k16";
    ОбработкаОбмена.ПользовательИнформационнойБазыДляПодключения = "ИмяПользователя";
    ОбработкаОбмена.ПарольИнформационнойБазыДляПодключения = "Пароль";
    ОбработкаОбмена.НепосредственноеЧтениеВИБПриемнике = Истина;
    
    ОбработкаОбмена.ЗагрузитьПравилаОбмена();
    
    УстановитьУзелОбменаУСтрокДерева(ОбработкаОбмена.TableПравилВыгрузки.Строки, 
        ПланыОбмена.ОбменСБухгалтерияПредприятия3.НайтиПоКоду("БП"));
        
    ОбработкаОбмена.ВыполнитьВыгрузку();
КонецПроцедуры

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

Причина №1: Проблема транзакционности (грязное чтение и взаимоблокировки)

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

Когда мы вызываем метод ФоновыеЗадания.Выполнить, система мгновенно создает новый рабочий сеанс на сервере приложений 1С. Этот сеанс пытается прочитать данные проводимого документа, чтобы выгрузить их в приемник. Однако, согласно правилам СУБД (уровень изоляции транзакций Read Committed):

  1. Данные еще не зафиксированы: Изменения, внесенные в документ и его движения в текущей транзакции проведения, еще не записаны в базу данных окончательно. Фоновое задание, работая в параллельном сеансе, физически не видит этих изменений. Оно либо выгрузит старую версию документа, либо вообще не найдет его, если документ проводится впервые.
  2. Взаимные блокировки (Deadlocks): Попытка прочитать данные, которые прямо сейчас заблокированы пишущей транзакцией в основном сеансе проведения, заставляет фоновое задание уйти в режим ожидания блокировки. Основной сеанс ждет завершения фонового задания (так как код написан последовательно или сервер перегружен), а фоновое задание ждет освобождения таблиц основным сеансом. Происходит классический Deadlock, система аварийно завершает один из сеансов по таймауту.
  3. Проблема отката транзакции: Если в процессе проведения документа после вызова фонового задания произойдет ошибка (например, не хватило свободных остатков на складе), транзакция проведения откатится. При этом фоновое задание уже запущено и может успешно выгрузить в базу-приемник документ, которого на самом деле в базе-источнике не существует.

Причина №2: Специфика COM-соединения на сервере

На клиенте интерактивная выгрузка работает без проблем, так как она выполняется в 32-разрядном адресном пространстве под учетной записью текущего пользователя Windows. На сервере ситуация кардинально меняется:

  1. Разрядность платформы: Современные серверы 1С практически всегда являются 64-разрядными. При вызове конструктора Новый COMОбъект("V83.COMConnector") сервер пытается загрузить 64-битную библиотеку comcntr.dll. Если на сервере зарегистрирована только 32-битная версия платформы или библиотека вовсе не зарегистрирована в системном реестре, вызов завершится критической ошибкой, которая даже не успеет записаться в лог обмена.
  2. Права системной службы: Фоновые задания выполняются от имени системного пользователя, под которым запущена служба сервера «1С:Предприятия» (например, USR1CV8). У этого пользователя часто отсутствуют права на инициализацию COM-компонентов в Windows (DCOM), нет доступа к сетевым ресурсам и временным папкам сервера, куда метод ПолучитьИмяВременногоФайла пытается сохранить временный файл правил обмена.
  3. Интерактивные окна: Если в коде правил обмена (в обработчиках событий «Перед выгрузкой», «При выгрузке» и т. д.) или внутри самой обработки УниверсальныйОбменДаннымиXML затесался хотя бы один интерактивный вызов (например, функции Предупреждение, Вопрос, Сообщить или открытие формы), серверный процесс мгновенно и навсегда зависнет, ожидая реакции пользователя на несуществующем экране.

Причина №3: Настройка свойств общего модуля

Для того чтобы регламентное или фоновое задание могло корректно запустить процедуру обмена, необходимо соблюдение строгих правил конфигурирования:

Если эти требования не соблюдены, планировщик заданий просто «не увидит» процедуру, что приведет к ошибке вызова метода.

---

Разрабатываем правильную архитектуру обмена

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

Шаг 1. Регистрация изменений в Плане обмена

Вместо непосредственного запуска выгрузки в подписке на проведение, мы будем использовать стандартный механизм 1С — регистрацию изменений на узле плана обмена. Это чрезвычайно быстрая операция, которая занимает доли миллисекунды, не создает блокировок и полностью подчиняется транзакции проведения (если транзакция откатится, то и регистрация изменения отменится).

Создадим или используем существующий План обмена (например, ОбменСБухгалтерияПредприятия3). Настроим подписку на событие ПриЗаписи для нужных документов:


Процедура ПодпискаНаСобытиеРегистрацияИзмененийПриЗаписи(Источник, Отказ, РежимЗаписи, РежимПроведения) Экспорт
    // Проверим, что документ не находится в режиме обмена (чтобы избежать зацикливания)
    Если Источник.ОбменДанными.Загрузка Тогда
        Возврат;
    КонецЕсли;
    
    // Получаем узел-приемник
    УзелПриемник = ПланыОбмена.ОбменСБухгалтерияПредприятия3.НайтиПоКоду("БП");
    Если ЗначениеЗаполнено(УзелПриемник) Тогда
        // Регистрируем изменение только для этого конкретного узла
        ПланыОбмена.ЗарегистрироватьИзменения(УзелПриемник, Источник);
    КонецЕсли;
КонецПроцедуры

Шаг 2. Создание регламентного задания для асинхронного обмена

Теперь создадим регламентное задание, которое будет запускаться независимо от действий пользователей на сервере, например, каждые 2–3 минуты. Оно будет считывать накопленные изменения из плана обмена, порционно выгружать их и очищать регистрацию только после успешного завершения сеанса обмена.

Напишем процедуру для общего модуля РегламентныеПроцедурыСервер (флаг «Сервер» — Истина, «Экспорт» присутствует):


Процедура ВыполнитьОперативныйОбменСБП() Экспорт
    
    УзелПриемник = ПланыОбмена.ОбменСБухгалтерияПредприятия3.НайтиПоКоду("БП");
    Если Не ЗначениеЗаполнено(УзелПриемник) Тогда
        ЗаписьЖурналаРегистрации("Обмен данными", УровеньЖурналаРегистрации.Ошибка, , , 
            "Не найден узел плана обмена с кодом 'БП'!");
        Возврат;
    КонецЕсли;
    
    // Проверим, есть ли зарегистрированные изменения, чтобы не запускать тяжелую обработку вхолостую
    ВыборкаИзменений = ПланыОбмена.ВыбратьИзменения(УзелПриемник, 1); // получаем маркер изменений
    Если Не ВыборкаИзменений.Следующий() Тогда
        // Изменений нет, завершаем работу
        Возврат;
    КонецЕсли;
    
    // Сбрасываем выборку для корректной работы обработки обмена
    ВыборкаИзменений = Неопределено;
    
    // Инициализируем обработку выгрузки XML
    ОбработкаОбмена = Обработки.УниверсальныйОбменДаннымиXML_v3.Создать();
    ОбработкаОбмена.РежимОбмена = "Выгрузка";
    
    // Использование временных файлов на сервере должно быть безопасным
    ИмяФайлаПравил = ПолучитьИмяВременногоФайла("xml");
    МакетПравил = ПланыОбмена.ОбменСБухгалтерияПредприятия3.ПолучитьМакет("ПравилаВыгрузкиКА_БП");
    МакетПравил.Записать(ИмяФайлаПравил);
    
    ОбработкаОбмена.ИмяФайлаПравилОбмена = ИмяФайлаПравил;
    ОбработкаОбмена.ЗагружатьДанныеВРежимеОбмена = Истина;
    ОбработкаОбмена.ЗаписыватьРегистрыНаборамиЗаписей = Истина;
    ОбработкаОбмена.ЗапоминатьЗагруженныеОбъекты = Истина;
    
    // Настройки удаления регистрации изменений после успешной выгрузки
    // Режим 2 означает автоматическое удаление регистрации только для выгруженных объектов
    ОбработкаОбмена.ТипУдаленияРегистрацииИзмененийДляУзловОбменаПослеВыгрузки = 2;
    
    // Настройки COM-соединения
    ОбработкаОбмена.ТипИнформационнойБазыДляПодключения = Ложь; // Серверный вариант базы
    ОбработкаОбмена.ВерсияПлатформыИнформационнойБазыДляПодключения = "V83";
    ОбработкаОбмена.ИмяИнформационнойБазыНаСервереДляПодключения = "db_buh_v3";
    ОбработкаОбмена.ИмяСервераИнформационнойБазыДляПодключения = "s1c2k16";
    ОбработкаОбмена.ПользовательИнформационнойБазыДляПодключения = "АдушкинВ";
    ОбработкаОбмена.ПарольИнформационнойБазыДляПодключения = "135246";
    ОбработкаОбмена.НепосредственноеЧтениеВИБПриемнике = Истина;
    
    Попытка
        ОбработкаОбмена.ЗагрузитьПравилаОбмена();
        
        // Устанавливаем узел обмена для выгружаемых правил
        НастроитьУзлыОбменаВПравилах(ОбработкаОбмена.ТаблицаПравилВыгрузки.Строки, УзелПриемник);
        
        // Выполняем выгрузку
        ОбработкаОбмена.ВыполнитьВыгрузку();
        
        ЗаписьЖурналаРегистрации("Обмен данными", УровеньЖурналаРегистрации.Информация, , , 
            "Обмен данными с БП успешно завершен.");
    Исключение
        ИнформацияОбОшибке = ИнформацияОбОшибке();
        ЗаписьЖурналаРегистрации("Обмен данными", УровеньЖурналаРегистрации.Ошибка, , , 
            "Ошибка при выполнении обмена: " + КраткоеПредставлениеОшибки(ИнформацияОбОшибке));
    КонецПопытки;
    
    // Удаляем временный файл правил обмена с диска сервера
    Попытка
        УдалитьФайлы(ИмяФайлаПравил);
    Исключение
        // Игнорируем ошибку удаления временного файла
    КонецПопытки;
    
КонецПроцедуры

Процедура НастроитьУзлыОбменаВПравилах(СтрокиДерева, УзелОбмена)
    Для Каждого Строка Из СтрокиДерева Цикл
        Если Строка.ЭтоГруппа Тогда
            НастроитьУзлыОбменаВПравилах(Строка.Строки, УзелОбмена);
        Иначе
            Строка.СсылкаНаУзелОбмена = УзелОбмена;
        КонецЕсли;
    КонецЦикла;
КонецПроцедуры

Альтернатива COM-соединениям: Переход на Веб-технологии

Даже при правильной настройке регламентных заданий COM-соединение остается крайне капризной технологией, чувствительной к обновлениям платформы 1С, настройкам DCOM на сервере Windows и разрядности процессов. Современным индустриальным стандартом интеграции систем «1С» является использование HTTP-сервисов.

Рассмотрим, как концептуально перевести обмен на HTTP-протокол, полностью избавившись от проблем с COM-библиотеками:

  1. В базе-приемнике (БП 3.0) создается и публикуется на веб-сервере (IIS или Apache) простой HTTP-сервис, принимающий POST-запросы с XML-пакетом данных.
  2. База-источник формирует XML-файл обмена локально на сервере через регламентное задание и отправляет его по HTTP/HTTPS.

Пример отправки данных из источника по HTTP-протоколу:


Процедура ОтправитьДанныеПоHTTP(ИмяФайлаДанных)
    
    // Настройка соединения
    СерверПриемник = "buh-server.local";
    РесурсСервиса = "/buh_base/hs/Exchange/UploadData";
    
    HTTPСоединение = Новый HTTPСоединение(СерверПриемник, , "ПользовательHTTP", "ПарольHTTP", , 30);
    
    HTTPЗапрос = Новый HTTPЗапрос(РесурсСервиса);
    HTTPЗапрос.Заголовки.Вставить("Content-Type", "application/xml; charset=utf-8");
    HTTPЗапрос.УстановитьТелоИзФайла(ИмяФайлаДанных);
    
    Попытка
        HTTPОтвет = HTTPСоединение.ОтправитьДляОбработки(HTTPЗапрос);
        Если HTTPОтвет.КодСостояния = 200 Тогда
            ЗаписьЖурналаРегистрации("HTTP Обмен", УровеньЖурналаРегистрации.Информация, , , 
                "Данные успешно переданы в приемник.");
        Иначе
            ВызватьИсключение "Сервер вернул ошибку: " + HTTPОтвет.КодСостояния + " " + HTTPОтвет.ПолучитьТелоКакСтроку();
        КонецЕсли;
    Исключение
        ИнформацияОбОшибке = ИнформацияОбОшибке();
        ВызватьИсключение "Ошибка HTTP-соединения: " + КраткоеПредставлениеОшибки(ИнформацияОбОшибке);
    КонецПопытки;
    
КонецПроцедуры

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

Резюме по шагам решения проблемы

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

  1. Никогда не запускайте реальную выгрузку данных внутри транзакции проведения документа. В подписке на событие выполняйте только регистрацию изменений в планах обмена.
  2. Перенесите всю логику обмена в регламентные задания. Настройте их запуск с нужной вам периодичностью (например, раз в 2 минуты). Это полностью изолирует процессы выгрузки от пользовательских транзакций.
  3. Проверьте настройки общего модуля регламентных заданий: модуль должен иметь флаг «Сервер» и процедуру с ключевым словом Экспорт.
  4. Обеспечьте права серверной службе 1С: у учетной записи (например, USR1CV8) должен быть доступ на чтение/запись к системным временным папкам и права на запуск DCOM-компонента V83.COMConnector, если вы продолжаете использовать COM.
  5. Рассмотрите отказ от COM в пользу HTTP-сервисов. Это избавит вас от проблем несовместимости 32- и 64-битных систем и существенно повысит отказоустойчивость всей инфраструктуры.
← На главную