При проектировании высоконагруженных систем на платформе 1С:Предприятие 8 часто возникает необходимость параллельной обработки данных. Многопоточность позволяет существенно сократить время выполнения тяжелых регламентных операций, распределяя нагрузку между фоновыми заданиями. Однако при этом разработчики и системные администраторы сталкиваются с рядом сложных вопросов: как гарантировать, что все потоки будут выполняться на определенном сервере кластера, как организовать бесконфликтное логирование и как избежать взаимоблокировок в СУБД. Рассмотрим подробно решение этих задач.
По умолчанию кластер серверов 1С распределяет нагрузку динамически. Фоновые задания запускаются на тех рабочих серверах, которые в данный момент наименее нагружены. Если нам необходимо жестко привязать выполнение определенных фоновых заданий к конкретному физическому или виртуальному серверу (например, к выделенному серверу для тяжелых расчетов с именем SRV_JOBS), мы должны использовать механизм требований назначения функциональности.
Разберем по шагам процесс настройки в консоли администрирования кластера серверов:
SRV_JOBS).BackgroundJob.CommonModule (это правило привяжет фоновые задания, запущенные программно из общих модулей).BackgroundJob.ScheduledJob.Важное архитектурное исключение: Платформа 1С использует так называемые системные фоновые задания (например, для компиляции модулей или формирования контекста базы данных при запуске новых процессов rphost). Они имеют внутренний идентификатор SystemBackgroundJob. На них созданные нами требования назначения функциональности не распространяются, и они могут запускаться на любом сервере кластера, где активен рабочий процесс.
Проанализируем работу фоновых заданий в файловых базах данных. Начиная с версии платформы 8.3.3, механизм выполнения фоновых задач в файловом варианте был изменен. Теперь для этого не требуется запускать выделенный сеанс-планировщик. Платформа автоматически выделяет отдельный поток операционной системы под выполнение фоновых задач при наличии активного клиентского или веб-соединения.
Тем не менее, важно понимать ограничение файлового режима: настоящая параллельность в нем отсутствует. Если ваше приложение инициирует запуск нескольких фоновых заданий одновременно, они будут помещены в очередь и выполнены строго последовательно. Настоящая многопоточность доступна исключительно в клиент-серверном варианте работы под управлением СУБД (MS SQL Server, PostgreSQL, IBM DB2, Oracle Database).
Когда одновременно выполняется несколько десятков фоновых заданий, и каждое из них должно фиксировать ход своей работы, стандартные подходы к записи логов могут вызвать серьезные проблемы с производительностью.
Выясним особенности работы с различными механизмами логирования:
Вариант А: Использование Журнала регистрации (ЖР)
Метод ЗаписьЖурналаРегистрации() является потокобезопасным на уровне платформы. Однако производительность напрямую зависит от формата вашего ЖР:
.lgf) интенсивная запись из параллельных потоков гарантированно приведет к жестким блокировкам файлов операционной системы и существенно замедлит выполнение фоновых заданий..lgd) справляется с параллельной записью гораздо лучше, но при экстремально высоких нагрузках СУБД SQLite может возвращать ошибку "database is locked".Вариант Б: Запись в текстовые файлы логов
Попытка прямой записи из разных потоков в один и тот же текстовый файл на диске приведет к ошибке совместного доступа (файл будет заблокирован первым открывшим его потоком). Для обхода этой проблемы применим эффективную стратегию индивидуальных логов:
log_e9a12b3c...txt.Вариант В: Логирование через независимый регистр сведений
Этот способ является наиболее гибким. Мы создаем независимый регистр сведений (например, ЛогФоновыхЗаданий). Важно спроектировать структуру его ключевых полей так, чтобы исключить блокировки при одновременной записи. Ключевыми полями должны выступать ИдентификаторЗадачи (уникальный GUID потока) и Период (или НомерСтрокиЛога).
Основная сложность при многопоточной модификации данных в 1С — это транзакционные блокировки и взаимоблокировки (deadlocks) в СУБД. Если два параллельных потока попытаются обновить пересекающиеся наборы данных, один из них будет ожидать освобождения ресурсов, а при худшем сценарии СУБД аварийно завершит одну из транзакций.
Чтобы избежать этих проблем, применим следующие архитектурные подходы:
Принцип строгого секционирования данных:
Перед запуском потоков разделим весь объем обрабатываемых данных на непересекающиеся порции. Например, по диапазонам уникальных идентификаторов, по кодам филиалов или с помощью деления числовых ID записей по модулю (остаток от деления на количество потоков). Каждый поток должен работать исключительно со своим сегментом данных и не обращаться к чужим.
Использование управляемых блокировок:
В коде фонового задания перед выполнением запросов и записью объектов необходимо накладывать явную управляемую блокировку данных с помощью объекта БлокировкаДанных. Блокировать нужно строго те элементы, которые принадлежат текущему потоку. Это предотвратит эскалацию блокировок СУБД (когда блокируется вся таблица целиком вместо конкретных строк).
Рассмотрим пример кода установки управляемой блокировки в обработчике фонового задания:
Процедура ВыполнитьОбработкуПорции(ПараметрыПотока) Экспорт
ИдентификаторПотока = ПараметрыПотока.ИдентификаторПотока;
СписокОбъектов = ПараметрыПотока.СписокОбъектов;
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.ОчередьОбработки");
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
ЭлементБлокировки.ИсточникДанных = СписокОбъектов;
ЭлементБлокировки.ИспользоватьИзИсточникаДанных("Ссылка", "Ссылка");
НачатьТранзакцию();
Попытка
Блокировка.Заблокировать();
// Здесь выполняется чтение и модификация выделенной порции данных
// ...
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ЗаписатьЛогОшибки(ИнформацияОбОшибке());
ВызватьИсключение;
КонецПопытки;
КонецПроцедуры
Разделение итогов регистров:
Если ваши фоновые задания записывают движения по регистрам накопления или регистрам бухгалтерии, обязательно включите свойство "Разделение итогов" для этих регистров в конфигураторе. Данная опция позволяет разным транзакциям одновременно проводить документы и делать записи по одним и тем же аналитическим разрезам (измерениям), физически записывая промежуточные итоги в разные строки СУБД. Платформа автоматически объединит их при получении остатков.
Вместо использования низкоуровневого метода платформы ФоновыеЗадания.Выполнить() крайне рекомендуется задействовать программный интерфейс Библиотеки стандартных подсистем (БСП), а именно общий модуль ДлительныеОперации. Он предоставляет разработчику готовый, проверенный временем каркас для организации параллельных вычислений.
Использование БСП дает следующие преимущества:
Посмотрим на пример организации многопоточного запуска с использованием БСП:
Процедура ЗапуститьПараллельнуюОбработку(МассивПорцийДанных)
МассивЗаданий = Новый Массив;
// Запуск потоков фоновых заданий
Для Каждого Порция Из МассивПорцийДанных Цикл
ПараметрыВыполнения = Новый Структура;
ПараметрыВыполнения.Вставить("ПорцияДанных", Порция);
НаименованиеЗадания = "Параллельный поток обработки данных";
Задание = ДлительныеОперации.ЗапуститьВыполнениеВФоне(
Неопределено,
"ОбщийМодульСервер.ВыполнитьОбработкуПорции",
ПараметрыВыполнения,
НаименованиеЗадания
);
МассивЗаданий.Добавить(Задание);
КонецЦикла;
// Ожидание завершения всех запущенных фоновых заданий
ПараметрыОжидания = ДлительныеОперации.ПараметрыОжиданияВыполнения();
ПараметрыОжидания.ИнтервалПовтора = 2; // Опрашивать раз в 2 секунды
ПараметрыОжидания.ВызыватьИсключение = Ложь;
РезультатыВыполнения = ДлительныеОперации.ОжидатьВыполнение(МассивЗаданий, ПараметрыОжидания);
// Анализ результатов выполнения
Для Каждого Результат Из РезультатыВыполнения Цикл
Если Результат.Статус = "Выполнено" Тогда
// Обработка успешного завершения потока
ИначеЕсли Результат.Статус = "Ошибка" Тогда
// Запись информации об ошибке конкретного потока
ЗаписьЖурналаРегистрации("МногопоточнаяОбработка.ОшибкаПотока",,,, Результат.КраткоеПредставлениеОшибки);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Применение рассмотренных архитектурных шаблонов настройки кластера, логирования и управления блокировками позволяет построить высокоэффективную, отказоустойчивую и масштабируемую систему параллельной обработки данных в среде 1С:Предприятие 8.