При интеграции 1С с внешними системами (например, сайтами или сторонними базами данных) разработчики часто сталкиваются с необходимостью выгрузки больших массивов информации. Представьте ситуацию: у вас есть стек из 60 000 документов, каждый из которых весит всего от 1 до 6 килобайт. Однако при попытке отправить их через стандартный встроенный механизм FTPСоединение вы обнаруживаете, что один файл загружается в среднем от 1.5 до 2 секунд. В итоге простая задача растягивается на 25–30 часов непрерывной работы процессора!
В этой статье мы подробно проанализируем, почему встроенный FTP-клиент 1С работает медленно при отправке множества мелких файлов, и разберем по шагам несколько эффективных способов кардинального ускорения этого процесса: от многопоточности до использования внешних утилит и изменения архитектуры обмена.
По замеру производительности в 1С основное время (до 99%) уходит на выполнение метода Записать() объекта FTPСоединение. Передача файла размером в пару килобайт длится целую секунду или больше по следующим причинам:
Записать() для каждого нового файла платформа на уровне сокетов инициирует новое подключение к каналу данных (особенно в пассивном режиме ПассивныйРежим = Истина). Для файлов размером 1–6 КБ время, затрачиваемое на установку TCP-соединения, согласование портов и авторизацию, в сотни раз превышает время передачи самого тела файла.TIME_WAIT (обычно на 2–4 минуты). Из-за этого ОС начинает искусственно задерживать открытие новых соединений, что и приводит к задержкам на ровном месте.Рассмотрим подробнее четыре практических решения, которые помогут решить эту проблему.
Если бизнес-логика требует отправки каждого файла в отдельности и именно на FTP, то самым очевидным и быстрым решением в рамках платформы «1С:Предприятие» является распараллеливание процесса выгрузки с помощью фоновых заданий.
Вместо того чтобы последовательно отправлять 60 000 файлов в одном сеансе, мы можем разбить этот массив данных на порции (например, по 5 000 или 6 000 файлов) и запустить их обработку параллельно в 10 или более потоков. Автор темы на практике подтвердил, что запуск выгрузки в 10+ потоков полностью решает проблему скорости.
Проанализируем пример кода для реализации многопоточной выгрузки. Сначала создадим управляющую процедуру, которая разбивает исходную таблицу выгрузки на пакеты и запускает фоновые задания:
Процедура РаспределитьВыгрузкуПоПотокам(ТаблицаВыгрузки) Экспорт
КоличествоПотоков = 10;
ВсегоЗаписей = ТаблицаВыгрузки.Количество();
РазмерПорции = Цел(ВсегоЗаписей / КоличествоПотоков) + 1;
МассивЗаданий = Новый Массив;
Для НомерПотока = 1 По КоличествоПотоков Цикл
НачальныйИндекс = (НомерПотока - 1) * РазмерПорции;
Если НачальныйИндекс >= ВсегоЗаписей Тогда
Прервать;
КонецЕсли;
КонечныйИндекс = Мин(НачальныйИндекс + РазмерПорции - 1, ВсегоЗаписей - 1);
// Формируем порцию данных для текущего потока
ПорцияДанных = ТаблицаВыгрузки.СкопироватьКолонки();
Для Индекс = НачальныйИндекс По КонечныйИндекс Цикл
ЗаполнитьЗначенияСвойств(ПорцияДанных.Добавить(), ТаблицаВыгрузки[Индекс]);
КонецЦикла;
ПараметрыЗапуска = Новый Массив;
ПараметрыЗапуска.Добавить(ПорцияДанных);
// Запуск фонового задания
ИмяПотока = "Выгрузка_Файлов_Поток_" + НомерПотока;
ФоновоеЗадание = ФоновыеЗадания.Выполнить("МодульОбменаFTP.ВыгрузитьПорциюНаФТП", ПараметрыЗапуска, , ИмяПотока);
МассивЗаданий.Добавить(ФоновоеЗадание);
КонецЦикла;
// Ожидаем завершения работы всех запущенных потоков
Попытка
ФоновыеЗадания.ОжидатьЗавершения(МассивЗаданий);
ЗаписьЖурналаРегистрации("FTP_Обмен", УровеньЖурналаРегистрации.Информация, , , "Все потоки успешно завершили выгрузку.");
Исключение
ЗаписьЖурналаРегистрации("FTP_Обмен", УровеньЖурналаРегистрации.Ошибка, , , "Ошибка выполнения потоков: " + ОписаниеОшибки());
КонецПопытки;
КонецПроцедуры
Процедура, выполняемая в фоновом задании, будет обрабатывать свою часть данных независимо от других потоков:
Процедура ВыгрузитьПорциюНаФТП(ПорцияДанных) Экспорт
// Инициализируем FTP-соединение внутри потока
ФТП = Новый FTPСоединение("х.х.х.х", 21, "логин", "пароль", , Истина);
ПредыдущийКаталог = "";
Для Каждого Строка Из ПорцияДанных Цикл
// Оптимизируем смену каталога, чтобы не выполнять лишние запросы к FTP
Если Строка.ТекущийКаталогФТП <> ПредыдущийКаталог Тогда
м = ФТП.НайтиФайлы(Строка.ТекущийКаталогФТП);
Если м.Количество() = 0 Тогда
ФТП.СоздатьКаталог(Строка.ТекущийКаталогФТП);
КонецЕсли;
ФТП.УстановитьТекущийКаталог(Строка.ТекущийКаталогФТП);
КонецЕсли;
Попытка
ФТП.Записать(Строка.ПутьКФайлу + "\" + Строка.ИмяФайла, Строка.ИмяФайла);
Исключение
ЗаписьЖурналаРегистрации("FTP_Обмен_Поток", УровеньЖурналаРегистрации.Предупреждение, , ,
"Не удалось выгрузить файл " + Строка.ИмяФайла + ": " + ОписаниеОшибки());
КонецПопытки;
ПредыдущийКаталог = Строка.ТекущийКаталогФТП;
КонецЦикла;
ФТП = Неопределено;
КонецПроцедуры
Передача мелких файлов по одному — это заведомо неэффективный способ работы с сетью. Гораздо надежнее применить буферизацию и пакетную отправку. Вместо моментальной отправки каждого отдельного изменения мы можем настроить регламентное задание, которое будет собирать накопившиеся файлы, упаковывать их в один ZIP-архив и отправлять его на сервер.
Передача одного архива размером 10 МБ займет долю секунды, тогда как последовательная передача 10 000 файлов по 1 КБ может занять несколько часов. На стороне получателя архив распаковывается автоматическим скриптом или принимающим веб-сервером.
Разберем пример создания и отправки ZIP-архива средствами 1С:
Процедура СформироватьИОтправитьАрхив(ТаблицаВыгрузки, ИмяФайлаАрхива) Экспорт
ПутьКАрхиву = КаталогВременныхФайлов() + ИмяФайлаАрхива;
Архив = Новый ЗаписьZipФайла(ПутьКАрхиву, , , МетодСжатияZIP.Сжатие, УровеньСжатияZIP.Оптимальный);
Для Каждого Строка Из ТаблицаВыгрузки Цикл
ПолныйПутьКФайлу = Строка.ПутьКФайлу + "\" + Строка.ИмяФайла;
ФайлНаДиске = Новый Файл(ПолныйПутьКФайлу);
Если ФайлНаДиске.Существует() Тогда
// Сохраняем файлы в архиве, сохраняя при необходимости структуру путей
Архив.Добавить(ПолныйПутьКФайлу, РежимСохраненияПутейZIP.СохранятьОтносительныеПути);
КонецЕсли;
КонецЦикла;
Архив.Записать();
// Отправляем один ZIP-файл вместо тысяч мелких файлов
ФТП = Новый FTPСоединение("х.х.х.х", 21, "логин", "пароль", , Истина);
ФТП.Записать(ПутьКАрхиву, ИмяФайлаАрхива);
ФТП = Неопределено;
// Удаляем временный файл архива
Попытка
УдалитьФайлы(ПутьКАрхиву);
Исключение
// Логируем возможные проблемы очистки временной папки
КонецПопытки;
КонецПроцедуры
Если многопоточность внутри 1С по каким-то причинам нежелательна или перегружает рабочий процесс сервера, лучшим решением становится использование специализированного стороннего софта, оптимизированного для скоростной передачи данных.
Использование утилиты cURL:
Консольный клиент curl.exe умеет держать TCP-соединение открытым в рамках одной сессии и передавать файлы пачкой без постоянной авторизации. Вы можете программно сформировать список файлов и запустить выполнение curl одной командой операционной системы через метод ЗапуститьПриложение().
Использование библиотеки WinSCP через COM:
WinSCP предоставляет мощный программный интерфейс для работы с файловыми серверами. Подключив .NET сборку WinSCP как COMОбъект("WinSCP.Session"), мы получаем возможность использовать высокооптимизированные методы, например, PutFiles или SynchronizeDirectories. WinSCP автоматически обрабатывает сетевые сбои, поддерживает режим дозаписи и работает в разы быстрее штатного механизма 1С.
Рассмотрим пример синхронизации каталога через WinSCP:
Процедура СинхронизироватьПапкуЧерезWinSCP(ЛокальныйКаталог, УдаленныйКаталог) Экспорт
Попытка
// Настройка параметров подключения
ПараметрыСессии = Новый COMОбъект("WinSCP.SessionOptions");
ПараметрыСессии.Protocol = 1; // Указываем протокол FTP
ПараметрыСессии.HostName = "х.х.х.х";
ПараметрыСессии.UserName = "логин";
ПараметрыСессии.Password = "пароль";
ПараметрыСессии.PortNumber = 21;
Сессия = Новый COMОбъект("WinSCP.Session");
Сессия.Open(ПараметрыСессии);
ПараметрыПередачи = Новый COMОбъект("WinSCP.TransferOptions");
ПараметрыПередачи.TransferMode = 0; // Двоичный режим передачи (Binary)
// Передаем все новые и изменившиеся файлы из локальной папки в удаленную
РезультатПередачи = Сессия.PutFiles(ЛокальныйКаталог + "\*", УдаленныйКаталог, Ложь, ПараметрыПередачи);
// Проверяем наличие ошибок в процессе передачи
РезультатПередачи.Check();
// Закрываем и освобождаем сессию
Сессия.Dispose();
Исключение
ЗаписьЖурналаРегистрации("WinSCP_Обмен", УровеньЖурналаРегистрации.Ошибка, , ,
"Ошибка обмена через WinSCP: " + ОписаниеОшибки());
КонецПопытки;
КонецПроцедуры
Сетевые архитекторы рекомендуют уходить от классического протокола FTP при передаче больших массивов данных в силу его технической устарелости.
HTTPСоединение посредством POST-запросов (например, в формате JSON). Протоколы HTTP/1.1 и HTTP/2 поддерживают технологию постоянного соединения (Keep-Alive), что позволяет передавать тысячи мелких пакетов данных вообще без накладных расходов на повторную авторизацию и открытие портов.Для того чтобы выбрать наиболее подходящий метод оптимизации, проанализируем ваши технические условия:
FTPСоединение в 1С ведет себя нестабильно при пиковых нагрузках — делегируйте эту задачу специализированной утилите WinSCP (Решение 3).