При интеграции нескольких информационных систем часто возникает задача: необходимо оперативно получать данные из одной базы 1С (базы-источника) и обрабатывать их в другой (базе-приемнике) или во внешнем веб-приложении. Ситуация осложняется, когда база-источник является типовой, находится на поддержке, и вносить в неё изменения (даже с помощью расширений) крайне нежелательно или физически невозможно. При этом стандартные механизмы вроде создания COM-соединения «на каждый чих» вызывают серьезные задержки и таймауты при высокой интенсивности запросов.
В этой статье мы подробно разберем, как решить проблему медленной работы межпроцессного взаимодействия, исключить долгое подключение и организовать высокоскоростное выполнение функций на стороне неизменяемой конфигурации-источника.
Основная причина низкой скорости работы через COM-соединение — это не сам процесс передачи данных или выполнение запроса, а длительная инициализация сеанса. При каждом вызове метода Connect платформа выполняет авторизацию пользователя, инициализирует параметры сеанса, запускает модули системы и проверяет лицензии. На это уходит от 1.5 до 5 секунд, что критично при многопоточной работе.
Чтобы избежать этих затрат времени, мы должны использовать встроенный механизм пулирования соединений, предоставляемый объектом V83.COMConnector. Пул позволяет держать несколько авторизованных сеансов в активном (но спящем) состоянии и мгновенно использовать их при поступлении запроса.
Разберем, как настроить пул соединений программно на стороне вызывающей системы:
// Создаем объект коннектора
Коннектор = Новый COMОбъект("V83.COMConnector");
// Настраиваем параметры пула соединений
// Максимальное количество удерживаемых в пуле неактивных соединений
Коннектор.PoolCapacity = 10;
// Время жизни неактивного соединения в пуле (в секундах)
Коннектор.PoolTimeout = 60;
// Максимальное количество одновременно открытых соединений
Коннектор.MaxConnections = 20;
// Выполняем подключение. При первом вызове произойдет полноценная авторизация.
// Все последующие вызовы с аналогичной строкой подключения будут брать сеанс из пула мгновенно!
СтрокаПодключения = "Srvr=""Server1C"";Ref=""BaseSource"";Usr=""IntegrationUser"";Pwd=""Password123"";";
БазаИсточник = Коннектор.Connect(СтрокаПодключения);
Для надежной многопоточной работы (например, под нагрузкой в 20 параллельных потоков из Java-приложения) рекомендуется настроить библиотеку comcntr.dll в операционной системе Windows через консоль «Службы компонентов» (COM+). Создание выделенного серверного приложения COM+ изолирует процесс работы с 1С в отдельном пуле dllhost.exe, предотвращая утечки памяти и зависания основного рабочего процесса.
Что делать, если в базе-источнике нет готовой экспортной функции для получения нужных данных, а менять конфигурацию или добавлять расширения нельзя? Мы можем использовать механизм внешних обработок, вызываемых через COM-соединение.
Этот способ позволяет реализовать сложнейшие запросы, пакетные обработки и расчеты на стороне источника, храня весь код в обычном файле обработки на диске.
Рассмотрим по шагам, как это реализовать:
DataRetriever.epf.
Функция ПолучитьОстаткиИЦеныНоменклатуры(ПараметрыЗапроса) Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Цены.Номенклатура.Код КАК Код,
| Цены.Цена КАК Цена,
| ЕСТЬNULL(Остатки.ВНаличииОстаток, 0) КАК Остаток
|ИЗ
| РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Период, Номенклатура.Код В (&Коды)) КАК Цены
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(&Период, Номенклатура.Код В (&Коды)) КАК Остаток
| ПО Цены.Номенклатура = Остатки.Номенклатура";
Запрос.УстановитьПараметр("Период", ТекущаяДата());
Запрос.УстановитьПараметр("Коды", ПараметрыЗапроса.КодыНоменклатуры);
ТаблицаРезультата = Запрос.Выполнить().Выгрузить();
// Преобразуем таблицу значений в массив структур для простой сериализации по COM
МассивРезультатов = Новый Массив;
Для Каждого Строка Из ТаблицаРезультата Цикл
СтруктураСтроки = Новый Структура("Код, Цена, Остаток");
ЗаполнитьЗначенияСвойств(СтруктураСтроки, Строка);
МассивРезультатов.Добавить(СтруктураСтроки);
КонецЦикла;
Возврат ЗначениеВСтрокуВнутр(МассивРезультатов);
КонецФункции
// Подключаемся к базе-источнику (используя пул соединений из Решения 1)
БазаИсточник = Коннектор.Connect(СтрокаПодключения);
// Динамически подключаем внешнюю обработку по пути к файлу
ПутьКОбработке = "C:\Services\DataRetriever.epf";
ИмяОбработки = БазаИсточник.ВнешниеОбработки.Подключить(ПутьКОбработке);
ОбъектОбработки = БазаИсточник.ВнешниеОбработки.Создать(ИмяОбработки);
// Готовим параметры для передачи
Параметры = Новый Структура;
МассивКодов = Новый Массив;
МассивКодов.Добавить("00000001");
МассивКодов.Добавить("00000002");
Параметры.Вставить("КодыНоменклатуры", МассивКодов);
// Вызываем экспортную функцию обработки и получаем сериализованную строку данных
ДанныеВСтроке = ОбъектОбработки.ПолучитьОстаткиИЦеныНоменклатуры(Параметры);
// Восстанавливаем данные на стороне базы-приемника
МассивДанных = ЗначениеИзСтрокиВнутр(ДанныеВСтроке);
Важное замечание: При подключении внешних обработок в современных версиях платформы может сработать защита от опасных действий. Чтобы избежать ошибок, необходимо в свойствах пользователя СУБД, под которым выполняется COM-подключение, снять флажок "Защита от опасных действий" или отключить её программно при создании профиля безопасности.
Многие разработчики ошибочно полагают, что использование REST API OData требует конфигурирования веб-сервисов и написания кода в базе-источнике. На самом деле стандартный интерфейс OData — это встроенная нативная возможность платформы 1С, которая запускается простым флажком при публикации базы.
Проанализируем процесс настройки стандартного интерфейса OData:
По умолчанию все объекты конфигурации могут быть закрыты для доступа через OData. Мы можем настроить состав доступных сущностей удаленно, не снимая конфигурацию с поддержки. Для этого выполним из внешней системы простой HTTP POST запрос к нашей опубликованной базе:
// Пример настройки доступности объектов OData (выполняется один раз администратором)
// Отправляем POST запрос на адрес: http://Server1C/BaseSource/odata/standard.odata/$metadata
// С телом запроса, содержащим список разрешенных таблиц (например, Справочник.Номенклатура, РегистрСведений.Цены)
После публикации внешнее Java-приложение сможет напрямую обращаться к таблицам 1С по HTTP со скоростью работы веб-сервера. Например, для получения данных о товаре с кодом "00001" достаточно выполнить GET-запрос:
GET http://Server1C/BaseSource/odata/standard.odata/Catalog_Номенклатура?$filter=Code eq '00001'
Если требуется максимальное быстродействие при чтении данных (миллисекунды на запрос) и выполнение сложных выборок без участия сервера приложений 1С, оптимальным решением станет прямое обращение к базе данных MS SQL Server или PostgreSQL.
Однако писать прямые SQL-запросы к физическим таблицам 1С неудобно из-за нечитаемых имен полей (вроде _Fld12345_RRRef) и сложности получения виртуальных таблиц (например, "Срез последних" для периодических регистров сведений).
Рассмотрим, как решить эту проблему с помощью создания SQL-представлений (Views) непосредственно на уровне СУБД. Администратор СУБД создает представление, которое преобразует структуру 1С в удобный плоский вид:
-- Создаем SQL-представление для получения цен номенклатуры (срез последних)
CREATE VIEW v_LastPrices AS
SELECT
Prices.Period AS [ДатаЦены],
Nomen.Code AS [КодТовара],
Nomen.Description AS [Наименование],
Prices.Price AS [Цена]
FROM
_InfoRg12345 AS Prices -- Физическая таблица регистра сведений Цены
INNER JOIN
_Reference67890 AS Nomen -- Физическая таблица справочника Номенклатура
ON Prices._Fld12345_RRRef = Nomen._IDRRef
WHERE
Prices.Period = (
SELECT MAX(SubPrices.Period)
FROM _InfoRg12345 AS SubPrices
WHERE SubPrices._Fld12345_RRRef = Prices._Fld12345_RRRef
) AND Prices._Active = 1;
После создания такого представления в СУБД база-получатель может подключить его с помощью стандартного объекта платформы "Внешние источники данных" или прочитать напрямую с помощью ADO/ODBC соединения:
// Чтение данных через ADO из внешней СУБД
Connection = Новый COMОбъект("ADODB.Connection");
Connection.Open("Provider=SQLOLEDB;Data Source=SQLServerName;Initial Catalog=BaseSource_DB;User ID=sa;Password=DBPassword;");
Command = Новый COMОбъект("ADODB.Command");
Command.ActiveConnection = Connection;
Command.CommandText = "SELECT * FROM v_LastPrices WHERE КодТовара = '00000001'";
RecordSet = Command.Execute();
Пока НЕ RecordSet.EOF() Цикл
Цена = RecordSet.Fields("Цена").Value;
Наименование = RecordSet.Fields("Наименование").Value;
// Обработка данных...
RecordSet.MoveNext();
КонецЦикла;
RecordSet.Close();
Connection.Close();
Преимущества прямого SQL-доступа:
Для минимизации нагрузки на рабочую базу данных рекомендуется настроить чтение SQL-представлений со вторичной реплики СУБД (например, используя технологии AlwaysOn в MS SQL Server или репликацию в PostgreSQL).
Если у вас есть возможность использовать механизм Расширений конфигурации, это идеальный компромисс. Конфигурация-источник остается полностью типовой и находится на поддержке, но при этом расширение позволяет добавить полноценный HTTP-сервис.
Проанализируем этот подход:
/get_data/{Code}).
Функция ПолучитьДанныеGET(Запрос)
КодТовара = Запрос.ПараметрыURL.Получить("Code");
Запрос1С = Новый Запрос;
Запрос1С.Текст =
"ВЫБРАТЬ
| Номенклатура.Наименование КАК Наименование,
| Цены.Цена КАК Цена
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Период, Номенклатура.Код = &Код) КАК Цены
| ПО (ИСТИНА)
|ГДЕ
| Номенклатура.Код = &Код";
Запрос1С.УстановитьПараметр("Код", КодТовара);
Запрос1С.УстановитьПараметр("Период", ТекущаяДата());
Результат = Запрос1С.Выполнить();
Если Результат.Пустой() Тогда
Ответ = Новый HTTPСервисОтвет(404);
Возврат Ответ;
КонецЕсли;
Выборка = Результат.Выбрать();
Выборка.Следующий();
СтруктураДанных = Новый Структура;
СтруктураДанных.Вставить("Name", Выборка.Наименование);
СтруктураДанных.Вставить("Price", Выборка.Цена);
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
ЗаписатьJSON(ЗаписьJSON, СтруктураДанных);
СтрокаJSON = ЗаписьJSON.Закрыть();
Ответ = Новый HTTPСервисОтвет(200);
Ответ.УстановитьТелоИзСтроки(СтрокаJSON, КодировкаТекста.UTF8, ИспользованиеByteOrderMark.НеИспользовать);
Ответ.Заголовки.Вставить("Content-Type", "application/json");
Возврат Ответ;
КонецФункции
После публикации расширения внешнее Java-приложение или вызывающая база 1С сможет запрашивать данные по сверхбыстрому протоколу HTTP, а само расширение никак не помешает последующим обновлениям типовой конфигурации.
Для того чтобы выбрать наиболее эффективное решение, сопоставим варианты: