Параллельные вычисления для http-сервиса

19.11.20

База данных - HighLoad оптимизация

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

Файлы

ВНИМАНИЕ: Файлы из Базы знаний - это исходный код разработки. Это примеры решения задач, шаблоны, заготовки, "строительные материалы" для учетной системы. Файлы ориентированы на специалистов 1С, которые могут разобраться в коде и оптимизировать программу для запуска в базе данных. Гарантии работоспособности нет. Возврата нет. Технической поддержки нет.

Наименование Скачано Купить файл
Параллельные вычисления для http-сервиса:
.cf 78,66Kb ver:1
5 1 850 руб. Купить

Подписка PRO — скачивайте любые файлы со скидкой до 85% из Базы знаний

Оформите подписку на компанию для решения рабочих задач

Оформить подписку и скачать решение со скидкой

Для начала на всякий случай вспомним, что регламентные и фоновые задания - это не совсем про одно и то же. Механизм регламентных заданий - это по сути планировщик, который будет запускать определенное фоновое задание с заданной периодичностью. Фоновое задание - это отдельный процесс (он же поток, он же нить, он же Жора, он же Гоша... не будем вдаваться в детали), исполняющий часть работы приложения. Фоновые задания мы можем создавать/запускать по мере необходимости, чем и воспользуемся для решения задачи ускорения вычислений.

В рассматриваемом примере имеем http-сервис, выполняющий вычисления, которые могут быть абсолютно любыми: от "2*2" до запросов к БД. Наш сервис будет вычислять квадрат для каждого из переданного в массиве числа. Если размерность массива превысит определенный порог, то будет применяться распараллеливание. В реальных задачах к этому лучше отнестись внимательно, т.к. из-за излишних накладных расходов на организацию параллелизма можно получить деградацию времени отклика сервиса. В качестве инструмента организации параллелизма будем использовать фоновые задания.

Зачастую ответом сервиса являются данные, отформатированные в определенной нотации. Никто не ограничивает нас в раздельном формировании частей общего ответа. Ведь мы можем сформировать несколько частей JSON, а затем объединить их в один большой результат? Конечно можем. Так и будем делать.

Ниже представлена основная логика с комментариями

//  
//  точка входа обработки тела запроса
//  тело запроса json-массив числовых значений [1, 2, 3, 4, 5, 6, 7]
//	результатом исполнения будет массив квадратов исходных чисел [1, 4, 9, 16, 25, 36, 49]
Функция Квадрат(ТелоЗапроса) Экспорт
    
    Результат = Новый Массив;    
    
    Попытка
        
        // получаем коллекцию из входных данных
        ВходящийМассив = ИзВеба(ТелоЗапроса);
        
        // служебная структура, которая содержит все необходимые для основного процесса вычислений параметры
        ПараметрыВычисления = СтруктураПараметровВычисления();
        ПараметрыВычисления.ВходящийМассив = ВходящийМассив;
        
        // определяем необходимость распараллеливания работы
        КоличествоНаПроцесс = 2;
        Если ВходящийМассив.Количество() > КоличествоНаПроцесс Тогда
            
            // реализуем параллельную обработку
            
            // разделяем данные на части
            МассивЧастей = РазделитьНаЧастиЗаданнойРазмерности(ВходящийМассив, КоличествоНаПроцесс);
            
            // формируем параметры исполнения для каждого процесса
            НомерПакета = 0;
            МассивПараметровФоновыхЗаданий = Новый Массив;
            Для Каждого Часть из МассивЧастей Цикл
                
                ТекущиеПараметрыВычисления = СтруктураПараметровВычисления();
                ТекущиеПараметрыВычисления.ВходящийМассив = Часть;
                ТекущиеПараметрыВычисления.НомерПакета = НомерПакета;
                
                ПараметрыФоновогоЗадания = Новый Массив;
                ПараметрыФоновогоЗадания.Добавить(ТекущиеПараметрыВычисления);
                МассивПараметровФоновыхЗаданий.Добавить(ПараметрыФоновогоЗадания);
                
                НомерПакета = НомерПакета + 1;
                
            КонецЦикла;
            
            // выполняем вычисления
            // для этого запускаем фоновые задания, ожидаем их завершения и собираем переданные сообщения
            // каждое сообщение - это кусочек вычисления, которые необходимо будет просто собрать вместе в один общий ответ
            МетодОбработки = "Вычислитель.ВычислитьКвадрат";
            СообщенияФоновыхПроцессов = ВыполнитьПроцессы(МетодОбработки, МассивПараметровФоновыхЗаданий);
            
            // получение и сортировка результата
            // сортировка может потребоваться, если результат сервиса должен быть собран в строго определенном порядке
            ТаблицаСообщений = Новый ТаблицаЗначений;
            ТаблицаСообщений.Колонки.Добавить("НомерПакета");
            ТаблицаСообщений.Колонки.Добавить("Результат");
            Для Каждого ТекущееСообщение Из СообщенияФоновыхПроцессов Цикл                
                ЗаполнитьЗначенияСвойств(ТаблицаСообщений.Добавить(), ИзВеба(ТекущееСообщение.Значение));
            КонецЦикла;
            ТаблицаСообщений.Сортировать("НомерПакета");
            
            // компоновка ответа сервиса
            Для Каждого ТекущееСообщение Из ТаблицаСообщений Цикл
                ТекущийРезультат = ТекущееСообщение.Результат;
                Для Каждого ТекущийЭлемент Из ТекущийРезультат Цикл
                    Результат.Добавить(ТекущийЭлемент);
                КонецЦикла;
            КонецЦикла;            
            
        Иначе
            
            // сюда попадаем, если не нужно распараллеливать            
            Результат = Вычислитель.ВычислитьКвадрат(ПараметрыВычисления);
            
        КонецЕсли;
                
    Исключение
        ОписаниеОшибки = ОписаниеОшибки();
        ЗаписьЖурналаРегистрации(ИмяСобытияЖурнала(), УровеньЖурналаРегистрации.Ошибка,,, ОписаниеОшибки);
    КонецПопытки;
    
    
    Возврат ВВеб(Результат);    
    
КонецФункции


А вот здесь можно увидеть, как фоновые задания передают результат исполнения

//
//
Функция ВычислитьКвадрат(ПараметрыВычисления) Экспорт
    
    Результат = Новый Массив;
    
    // выполняем вычисление квадрата для каждого числа
    Для Каждого Элемент Из ПараметрыВычисления.ВходящийМассив Цикл
        
        ТекущееЧисло = КакЧисло(Элемент);
        Результат.Добавить(ТекущееЧисло*ТекущееЧисло);
        
    КонецЦикла;
    
    // определяем необходимость передачи результата
    // если контекст текущего исполнения - это фоновое задание, то выполняется сообщение результата для родительского процесса
    Если ИсполнениеВФоновомПроцессе() Тогда
        
        Сообщение = Новый СообщениеПользователю;
        Сообщение.ИдентификаторНазначения = Новый УникальныйИдентификатор;
        Сообщение.Текст = ЗначениеВСтрокуВнутр(Новый ХранилищеЗначения(ВВеб(Новый Структура("НомерПакета, Результат", ПараметрыВычисления.НомерПакета, Результат)), Новый СжатиеДанных(9)));
        Сообщение.Сообщить();
        
    КонецЕсли;
    
    Возврат Результат;
    
КонецФункции

 

Как мы видим, реализация очень простая и может быть применена для большого количества задач. Отдельно отмечу, что речь идет не только о задачах обработки передаваемых на вход данных, но и о задачах, для реализации которых необходимо обращение к БД. Например, для формирования ответа сервиса нам необходимо осуществить выборку данных из БД за переданный в параметрах сервиса период времени. Можно разбить переданный период на несколько частей, каждую часть обработать с использованием параллельных процессов, а затем собрать результаты в общий ответ.

Чего мы можем добиться, используя описанный подход? В первую очередь быстродействие: параллельная обработка одного и того же набора данных может быть быстрее, чем последовательная. Во-вторых, надежность: уменьшение пакетов обработки может благотворно сказаться на показателях надежности. В-третьих, масштабируемость: можно распределять фоновые задания в кластере между нодами через ТНФ. 

Полная реализация описанного примера приведена во вложении к публикации. Конфигурация с примером (платформа 8.3.17).

Вступайте в нашу телеграмм-группу Инфостарт

http параллелизм фоновые задания параллельные вычисления

См. также

HighLoad оптимизация Программист 1C:ERP Бесплатно (free)

Использование оператора «В» для полей или данных составного типа (например, Регистратор) может приводить к неочевидным проблемам.

10.11.2025    5265    ivanov660    48    

51

HighLoad оптимизация Программист 1С:Предприятие 8 1C:ERP Бесплатно (free)

Приведем примеры использования различных в динамических списках и посмотрим, почему это плохо.

18.02.2025    8109    ivanov660    39    

61

HighLoad оптимизация Технологический журнал Системный администратор Программист Бесплатно (free)

Обсудим поиск и разбор причин длительных серверных вызовов CALL, SCALL.

24.06.2024    10536    ivanov660    13    

64

HighLoad оптимизация Программист 1С:Предприятие 8 Бесплатно (free)

Метод очень медленно работает, когда параметр приемник содержит намного меньше свойств, чем источник.

06.06.2024    16507    Evg-Lylyk    73    

46

HighLoad оптимизация Программист 1С:Предприятие 8 1C:Бухгалтерия Бесплатно (free)

Анализ простого плана запроса. Оптимизация нагрузки на ЦП сервера СУБД используя типовые индексы.

13.03.2024    8109    spyke    29    

54

HighLoad оптимизация Программист 1С:Предприятие 8 Бесплатно (free)

Оказывается, в типовых конфигурациях 1С есть, что улучшить!

13.03.2024    11437    vasilev2015    22    

47
Комментарии
Подписаться на ответы Инфостарт бот Сортировка: Древо развёрнутое
Свернуть все
1. ltfriend 19.11.20 19:04 Сейчас в теме
Не стоит путать понятия "процесс" и "поток".
Процесс - экземпляр программы во время выполнения, независимый объект, которому выделены системные ресурсы (например, процессорное время и память).
Потоки (Thread) существуют внутри процесса. Есть основной поток, в котором выполняется основной код приложения, а также процесс может создавать дополнительные потоки, которые выполняются параллельно. Поток использует то же самое пространства стека, что и процесс.
А "нить" - просто прямой перевод слова Thread.
Другими словами, экземпляр запущенного сервера 1С - это процесс, фоновые задания, которые создаются и выполняются на этом сервере - потоки.
Shmell; VitaliyCeban; qwe322; van_za; +4 Ответить
2. NoRazum 31 03.12.20 11:40 Сейчас в теме
Увидел
"Попытка"
и большой кусок кода.
Дальше читать не стал.

Считаю эта плохим подходам.
(Хотя сама 1с очень любит так делать)
3. Lars Ulrich 635 03.12.20 15:23 Сейчас в теме
(2) Читать не призываю, но соглашусь с высказанной мыслью.
Здесь это сделано для того, чтобы не перегружать код проверкой входных данных. Тут ведь как: будешь смотреть на льва не увидишь золота, будешь смотреть на золото не увидишь льва.
4. NoRazum 31 03.12.20 17:40 Сейчас в теме
(3) Лев то хорош, Из-за этого поставил ПЛЮС.
Lars Ulrich; +1 Ответить
5. artbear 1583 03.12.20 19:02 Сейчас в теме
(0) Интересная публикация, спасибо!

одно но
Никогда так не пишите код обработки исключения
Исключение
        ОписаниеОшибки = ОписаниеОшибки();
        ЗаписьЖурналаРегистрации(ИмяСобытияЖурнала(), УровеньЖурналаРегистрации.Ошибка,,, ОписаниеОшибки);
КонецПопытки;

Так вы маскируете стек вызовов от администраторов, используйте подробное представление ошибки.
Стандарт 1С так и рекомендует.

подобная неточная обработка исключений - одна из самых частых ошибок\неточностей разработчиков.
очень часто наблюдаю такой код.
Lars Ulrich; +1 Ответить
9. efin 06.12.20 04:39 Сейчас в теме
(5) покажите, пожалуйста, пример кода. Потому что вот вроде слова знакомые, а общий смысл ускользает...
10. Lars Ulrich 635 08.12.20 10:51 Сейчас в теме
(9) имеется ввиду, что следует использовать подробное представление исключения.
Пример из синтаксис-помощника:
Попытка
    ...
Исключение
    Инфо = ИнформацияОбОшибке();
    Сообщить(НСтр("ru='Описание=';en='Description='") + Инфо.Описание + "'");
    Сообщить(НСтр("ru='ИмяМодуля=';en='ModuleName='") + Инфо.ИмяМодуля + "'");
    Сообщить(НСтр("ru='НомерСтроки=';en='LineNumber='") + Инфо.НомерСтроки + "'");
    Сообщить(НСтр("ru='ИсходнаяСтрока=';en='SourceLine='") + Инфо.ИсходнаяСтрока + "'");
КонецПопытки;
Показать


Источник для ознакомления: https://its.1c.ru/db/v8std/content/499/hdoc
divmaster; efin; +2 Ответить
11. artbear 1583 10.12.20 13:49 Сейчас в теме
(10) Да, спасибо за ссылку на стандарт.

Но пример кода из СП так себе (
вот как раз такое описание не нужно, а нужно как по ссылке на стандарт
6. artbear 1583 03.12.20 19:08 Сейчас в теме
(0) Ну и вообще при работе с фоновыми заданиями нужно помнить, что они могут быть прекращены в любой момент и могут не доработать до конца.
поэтому приходится использовать спец.обработку состояний фоновых заданий.

я для работы с фоновыми заданиями с гарантированным выполнением и гибкой настройкой много лет использую подсистему Менеджер Заданий моего товарища, Евгения Павлюка.
в ней многие проблемы работы с ФЗ решены.

Исходники есть на гитхабе, есть хорошая статья на habr-е
- https://github.com/wizi4d/TaskManagerFor1C
- https://habr.com/ru/post/255387/

Рекомендую.
Shmell; Lars Ulrich; +2 Ответить
7. artbear 1583 03.12.20 19:13 Сейчас в теме
(0) А почему юзаете возврат значений из ФЗ через СообщенияПользователю?
почему не штатный механизм 1С через временное хранилище, как написано в документации https://its.1c.ru/db/v8312doc#bookmark:dev:TI000000819

все-таки ненужная\излишня сериализация\десереализация
8. Lars Ulrich 635 04.12.20 12:06 Сейчас в теме
(7) можно и через механизм временного хранилища, будет побыстрее работать. соглашусь, что следовало бы в примере его использовать. Через СообщенияПользователю в текущей практике часто используется трансляция состояния исполнения фоновых заданий, как-то сходу его и задействовал.
user1503726; +1 Ответить
Для отправки сообщения требуется регистрация/авторизация