Передача файлов и больших массивов данных между клиентом и сервером — одна из наиболее ресурсоемких операций в клиент-серверной архитектуре 1С:Предприятия. Разработчики нередко сталкиваются с ситуацией, когда кэширование адреса временного хранилища в параметрах сеанса не дает ожидаемого прироста скорости, а клиент-серверные вызовы продолжают выполняться с ощутимыми задержками.
Разберем по шагам причины медленной работы при передаче двоичных данных, выясним особенности сериализации платформы и рассмотрим проверенные способы оптимизации клиент-серверного взаимодействия.
Проанализируем ситуацию: разработчик помещает коллекцию (например, Соответствие или Структура), содержащую объекты ДвоичныеДанные, во временное хранилище с привязкой к сеансу и сохраняет полученный адрес. Однако вызовы все равно занимают много времени. Выделим основные технические причины такого поведения:
ПоместитьВоВременноеХранилище передается сложный объект (соответствие с десятками файлов), платформа тратит значительные ресурсы процессора на упаковку и распаковку каждого бинарного объекта.&НаСервере на сервер неявно передаются все реквизиты текущей формы, а при возврате — отправляются обратно на клиент. Если данные формы раздуты, контекст замедляет каждый вызов независимо от хранилища.Знач, платформа сериализует их обратно на клиент после завершения вызова, даже если они не изменялись.ПолучитьИзВременногоХранилища, вызванный на клиенте, инициирует физическую загрузку всех двоичных данных по сети с сервера на клиентскую машину.Новый УникальныйИдентификатор привязывает данные к жизни всего сеанса пользователя. В результате процесс rphost держит бинарные данные в оперативной памяти вплоть до закрытия программы, что создает дополнительную нагрузку на кластер серверов.Рассмотрим один из самых эффективных способов снижения объема передаваемых по сети данных — предварительное сжатие файлов на стороне клиента с помощью объекта ЗаписьZipФайла.
Текстовые файлы, документы XML, JSON и выгрузки CSV отлично сжимаются, что позволяет уменьшить сетевой трафик в 5–10 раз. Посмотрим на пример кода подготовки архива на клиенте и его распаковки на сервере:
&НаКлиенте
Процедура ОтправитьСжатыеФайлыНаСервер(МассивПутейКФайлам)
ИмяАрхива = ПолучитьИмяВременногоФайла("zip");
Архиватор = Новый ЗаписьZipФайла(ИмяАрхива);
Для Каждого ПутьКФайлу Из МассивПутейКФайлам Цикл
Архиватор.Добавить(ПутьКФайлу, РежимСохраненияПутейZIP.НеСохранятьПути);
КонецЦикла;
Архиватор.Записать();
ДвоичныеДанныеАрхива = Новый ДвоичныеДанные(ИмяАрхива);
УдалитьФайлы(ИмяАрхива);
АдресАрхива = ПоместитьВоВременноеХранилище(ДвоичныеДанныеАрхива, УникальныйИдентификатор);
ОбработатьАрхивНаСервереБезКонтекста(АдресАрхива);
УдалитьИзВременногоХранилища(АдресАрхива);
КонецПроцедуры
На стороне сервера выполняем чтение архива и извлечение необходимых файлов:
&НаСервереБезКонтекста
Процедура ОбработатьАрхивНаСервереБезКонтекста(Знач АдресАрхива)
ДвоичныеДанныеАрхива = ПолучитьИзВременногоХранилища(АдресАрхива);
ИмяВременногоАрхива = ПолучитьИмяВременногоФайла("zip");
ДвоичныеДанныеАрхива.Записать(ИмяВременногоАрхива);
КаталогРаспаковки = КаталогВременныхФайлов() + "Unpack_" + СтрЗаменить(Строка(Новый УникальныйИдентификатор), "-", "");
СоздатьКаталог(КаталогРаспаковки);
ЧтениеАрхива = Новый ЧтениеZipФайла(ИмяВременногоАрхива);
ЧтениеАрхива.ИзвлечьВсе(КаталогРаспаковки, РежимВосстановленияПутейФайловZIP.НеВосстанавливать);
ЧтениеАрхива.Закрыть();
// Обрабатываем извлеченные файлы из КаталогРаспаковки
// Обязательно очищаем временные файлы на сервере
УдалитьФайлы(ИмяВременногоАрхива);
УдалитьФайлы(КаталогРаспаковки);
КонецПроцедуры
Разберем правила оформления серверных вызовов, которые исключают неявную передачу лишних данных между клиентом и сервером:
&НаСервереБезКонтекста: если для обработки файла не требуются элементы и реквизиты текущей формы, всегда используйте вызовы без контекста. Это предотвращает сериализацию данных формы.Знач: чтобы платформа не возвращала переданную строку адреса хранилища или структуры обратно на клиент, указывайте Знач ИмяПараметра в сигнатуре серверной процедуры или функции.Новый УникальныйИдентификатор передавайте УникальныйИдентификатор формы. В этом случае данные автоматически удалятся из оперативной памяти сервера при закрытии окна пользователем.УдалитьИзВременногоХранилища: сразу после того, как двоичные данные прочитаны на сервере, очищайте хранилище вручную.Когда требуется работать с файлами большого объема (например, базами SQLite, большими логами или тяжелыми таблицами), хранить их в оперативной памяти процессов rphost в виде объектов ДвоичныеДанные не рекомендуется. Оптимальным решением является сохранение на диск сервера во временный файл.
Посмотрим на пример безопасной работы с временным файлом:
&НаСервереБезКонтекста
Процедура ЗагрузитьДанныеИзХранилищаНаСервере(Знач АдресВХранилище)
ПутьВременногоФайла = ПолучитьИмяВременногоФайла();
Попытка
ДвоичныеДанные = ПолучитьИзВременногоХранилища(АдресВХранилище);
ДвоичныеДанные.Записать(ПутьВременногоФайла);
// Выполняем чтение и обработку файла на сервере
Исключение
УдалитьФайлы(ПутьВременногоФайла);
ВызватьИсключение;
КонецПопытки;
УдалитьФайлы(ПутьВременногоФайла);
КонецПроцедуры
Для управляемого интерфейса платформа 1С:Предприятие 8.3 предоставляет специализированные асинхронные методы: НачатьПомещениеФайловНаСервер и НачатьПолучениеФайловССервера. Они оптимизированы для передачи потоковых данных, работают без блокировки пользовательского интерфейса и поддерживают отображение прогресса загрузки.
Использование встроенных асинхронных методов исключает ручное создание объектов ДвоичныеДанные в коде клиента и снижает пиковую нагрузку на оперативную память.
Если специфика задачи предполагает регулярный обмен тяжелыми файлами между рабочими местами и сервером 1С, рассмотрим альтернативные варианты архитектуры:
\\server\share\data.xml). Сервер 1С обращается к файлу напрямую по сетевому пути, полностью минуя транспортный протокол платформы 1С.