При работе с внешними API, парсинге данных или интеграции с государственными информационными системами (например, закупки или маркировка) часто возникает необходимость отправить тысячи запросов за короткий промежуток времени. Стандартная последовательная отправка 5000 запросов в один поток может занимать более часа, что недопустимо для бизнес-процессов. В этой статье мы разберем эффективные методы ускорения обмена данными, проанализируем возможности параллельной обработки и выясним, как правильно настроить систему для максимальной производительности.
Прежде чем переходить к коду, давайте выясним причину медленной работы. При последовательном выполнении кода 1С каждый следующий запрос не начинается до тех пор, пока не будет полностью получен ответ на предыдущий. Основное время тратится не на выполнение кода внутри 1С, а на:
Рассмотрим несколько способов, как обойти эти ограничения.
Наиболее «родной» для 1С способ — использование объекта ФоновыеЗадания. Идея заключается в разделении общего массива запросов на порции и запуске каждой порции в отдельном потоке. Проанализируем пример реализации такого подхода:
// Определяем количество потоков
ЧислоПотоков = 10;
ВсегоЗапросов = СсылкиДляОбработки.Количество();
РазмерПорции = Цел(ВсегоЗапросов / ЧислоПотоков);
МассивЗаданий = Новый Массив;
Для НомерПотока = 1 По ЧислоПотоков Цикл
ИндексНачала = (НомерПотока - 1) * РазмерПорции;
// Корректировка для последнего потока, чтобы захватить остаток
Если НомерПотока = ЧислоПотоков Тогда
РазмерПорции = ВсегоЗапросов - ИндексНачала;
КонецЕсли;
ПараметрыЗапуска = Новый Массив;
ПараметрыЗапуска.Добавить(ИндексНачала);
ПараметрыЗапуска.Добавить(РазмерПорции);
ПараметрыЗапуска.Добавить(СсылкиДляОбработки); // Передаем данные
// Выполнение экспортного метода в фоновом задании
Задание = ФоновыеЗадания.Выполнить("МодульИнтеграции.ОтправитьПорциюЗапросов", ПараметрыЗапуска);
МассивЗаданий.Добавить(Задание);
КонецЦикла;
// Ожидание завершения всех потоков
Если МассивЗаданий.Количество() > 0 Тогда
Попытка
ФоновыеЗадания.ОжидатьЗавершения(МассивЗаданий);
Исключение
ЗаписьЖурналаРегистрации("Ошибка выполнения потоков", УровеньЖурналаРегистрации.Ошибка);
КонецПопытки;
КонецЕсли;
Важный нюанс: при использовании этого метода нельзя создавать слишком много потоков. Каждый поток — это отдельный сеанс на сервере 1С, который потребляет лицензию и оперативную память. Оптимальное число потоков обычно составляет от 5 до 20 в зависимости от мощности сервера.
Если фоновые задания кажутся слишком тяжеловесными (например, из-за долгого старта нового сеанса), можно рассмотреть использование системного объекта Windows. Этот метод позволяет реализовать асинхронную отправку в рамках одного потока 1С, используя внутренние механизмы операционной системы. Рассмотрим, как это работает на практике:
Объекты = Новый Массив;
КоличествоОбъектов = СсылкиДляОбработки.Количество();
// Создание пула асинхронных запросов
Для ин = 0 По КоличествоОбъектов - 1 Цикл
ОбъектHTTP = Новый COMObject("WinHttp.WinHttpRequest.5.1");
АдресЗапроса = "https://api.site.ru/data/" + СсылкиДляОбработки[ин].Идентификатор;
// Параметр "1" в методе Open включает асинхронный режим
ОбъектHTTP.Open("GET", АдресЗапроса, 1);
ОбъектHTTP.SetRequestHeader("Connection", "Keep-Alive");
ОбъектHTTP.Send();
Данные = Новый Структура("Объект, Выполнено, Ошибка", ОбъектHTTP, Ложь, Ложь);
Объекты.Добавить(Данные);
КонецЦикла;
// Цикл ожидания ответов
Пока Истина Цикл
ОбработаноОбъектов = 0;
Для ин = 0 По КоличествоОбъектов - 1 Цикл
Если Объекты[ин].Выполнено = Ложь И Объекты[ин].Ошибка = Ложь Тогда
Попытка
// WaitForResponse возвращает Истина, если ответ получен
Рез = Объекты[ин].Объект.WaitForResponse(0.01);
Если Рез Тогда
Объекты[ин].Выполнено = Истина;
ТекстОтвета = Объекты[ин].Объект.ResponseText;
// Обработка результата...
КонецЕсли;
Исключение
Объекты[ин].Ошибка = Истина;
КонецПопытки;
Иначе
ОбработаноОбъектов = ОбработаноОбъектов + 1;
КонецЕсли;
КонецЦикла;
Если ОбработаноОбъектов = КоличествоОбъектов Тогда
Прервать; // Все запросы завершены
КонецЕсли;
КонецЦикла;
В этом примере мы используем метод WaitForResponse. Это позволяет нам «пульнуть» сразу сотни запросов и в цикле проверять их готовность. Это значительно быстрее последовательного выполнения, так как сетевые задержки для всех запросов накладываются друг на друга во времени.
Многие разработчики совершают ошибку, создавая новый объект HTTPСоединение внутри цикла. Это заставляет систему каждый раз заново устанавливать TCP/TLS соединение. Если мы работаем с одним и тем же хостом, 1С умеет поддерживать постоянное соединение (механизм Keep-Alive).
Посмотрим на правильную структуру кода:
// Создаем соединение один раз вне цикла
Соединение = Новый HTTPСоединение("api.zakupki.gov.ru",,,,, 30, Новый ЗащищенноеСоединениеOpenSSL());
Для Каждого Ссылка Из МассивСсылок Цикл
Запрос = Новый HTTPЗапрос("/orders/" + Ссылка.ID);
// Платформа будет использовать уже открытый канал
Ответ = Соединение.Получить(Запрос);
Если Ответ.КодСостояния = 200 Тогда
// Обработка данных
КонецЕсли;
КонецЦикла;
Такой подход может ускорить обработку в 2–3 раза даже в один поток, так как исключаются затраты на TLS-рукопожатие для каждого запроса.
В современных версиях платформы (начиная с 8.3.18) разработчикам стали доступны встроенные асинхронные методы работы с HTTP. Например, ВызватьМетодАсинх. Это позволяет писать код, который не блокирует основной интерфейс (в случае клиентского приложения) и более эффективно распределяет ресурсы системы.
Хотя асинхронные методы в 1С не являются «честной» многопоточностью в классическом понимании, они позволяют операционной системе эффективнее управлять сетевым стеком. Рекомендуется использовать именно их, если ваша версия платформы это позволяет, вместо использования устаревших COMObject.
При ускорении запросов мы неизбежно сталкиваемся с двумя проблемами:
WinHttp, необходимо в реестре Windows изменить параметры MaxConnectionsPerServer.User-Agent, имитирующие реальный браузер.HTTPСоединение.Подводя итог, можно составить следующий алгоритм действий для ускорения запросов:
HTTPСоединение вне цикла для активации Keep-Alive.Accept-Encoding: gzip, это сократит объем передаваемого трафика.