При работе с высоконагруженными информационными системами на платформе 1С:Предприятие 8.3 часто возникает необходимость масштабирования за счет использования нескольких серверов приложений в одном кластере. Такая архитектура позволяет распределять нагрузку, обеспечивая стабильную работу для большого количества пользователей. Однако, при реализации длительных операций, которые запускаются в фоне, мы можем столкнуться с неочевидной, на первый взгляд, проблемой: результат выполнения фонового задания, помещенный во временное хранилище, становится недоступным для инициировавшего его клиентского сеанса, если фоновое задание отработало на другом сервере кластера. Для тех, кто только начинает погружаться в эту тему, будет полезно изучить основы работы с фоновыми заданиями для новичков, где разбираются базовые принципы и последние нововведения платформы.
Представим типовую ситуацию: пользователь работает на Сервере Приложений 1 (далее - СРВ1), запускает длительную операцию, которая порождает фоновое задание. Кластер серверов 1С может по своему усмотрению запустить это фоновое задание на Сервере Приложений 2 (далее - СРВ2). Если результат операции сохраняется во временное хранилище на СРВ2, а клиентский сеанс ожидает его на СРВ1, возникает проблема недоступности данных. Давайте проанализируем эту ситуацию и найдем решения.
В кластере серверов 1С:Предприятие, предназначенном для работы с одной базой данных SQL, рабочие сервера приложений (процессы rphost) взаимодействуют с кластерным менеджером (rmngr). Кластерный менеджер отвечает за распределение нагрузки, управление сеансами пользователей и фоновыми заданиями. Когда пользователь подключается к кластеру, его сеанс может быть создан на одном из доступных рабочих серверов. Аналогично, при запуске фонового задания, кластерный менеджер выбирает свободный рабочий сервер для его выполнения. При этом стоит учитывать и другие факторы влияния, например, неочевидную особенность свойства Ключ объекта ФоновоеЗадание, которая может влиять на логику исполнения.
Механизм временного хранилища в 1С используется для кратковременного хранения данных на сервере и обмена информацией между клиентом и сервером. По умолчанию, данные, помещенные во временное хранилище, могут быть доступны только в рамках того рабочего процесса rphost, который их создал, или в рамках кластера, если они сериализуемы и правильно привязаны к сеансу. Однако, при запуске фонового задания на другом сервере, чем клиентский сеанс, прямая доступность временного хранилища может нарушаться, особенно если не соблюдены определенные правила.
Для успешной передачи результата из фонового задания в сеанс, инициировавший его, критически важно правильно использовать временное хранилище, привязывая его к уникальному идентификатору формы. Этот механизм призван обеспечить, что результат будет доступен именно тому сеансу, который его ожидает, и, более того, технически обязывает платформу запускать фоновое задание на том же сервере приложений, что и родительский сеанс.
Инициализация временного хранилища в родительском сеансе.
В клиентском сеансе, то есть в форме, которая инициирует длительную операцию, нам необходимо предварительно поместить пустое значение во временное хранилище, обязательно привязав его к уникальному идентификатору текущей формы. Это создает "точку входа" для результата, связанную с конкретным клиентским интерфейсом.
// Переменные для хранения адреса результата и идентификатора формы
Перем АдресРезультатаДлительнойОперации Экспорт;
Перем ИдентификаторТекущейФормы Экспорт;
// В обработчике события формы (например, при создании на сервере или при нажатии кнопки)
// Мы получаем уникальный идентификатор текущей формы
ИдентификаторТекущейФормы = УникальныйИдентификатор;
// Помещаем пустое значение во временное хранилище, привязывая его к идентификатору формы.
// Это гарантирует, что адрес будет связан с нашим клиентским сеансом.
АдресРезультатаДлительнойОперации = ПоместитьВоВременноеХранилище(Неопределено, ИдентификаторТекущейФормы);
Обратите внимание, что мы используем Неопределено как значение, так как на этом этапе мы лишь резервируем место для будущего результата.
Передача адреса в фоновое задание.
Полученный на предыдущем шаге АдресРезультатаДлительнойОперации и ИдентификаторТекущейФормы необходимо передать в качестве параметров в процедуру, которая запускает фоновое задание. Часто для запуска фоновых заданий используются общие функции-обертки. Если вы используете современные конфигурации, обратите внимание на
актуальные алгоритмы использования длительных операций в БСП 3.1.8.
// Пример использования функции-обертки для запуска фонового задания
// Например, функция ВыполнитьВФоне из типовых подсистем 1С
// &НаСервере
// Функция ЗапуститьМоюДлительнуюОперациюВФоне(ПараметрыВыполнения) Экспорт
//
// // Функция ВыполнитьВФоне из БСП часто имеет такой код для работы с адресом результата:
// // АдресРезультата = ?(ПараметрыВыполнения.АдресРезультата <> Неопределено,
// // ПараметрыВыполнения.АдресРезультата,
// // ПоместитьВоВременноеХранилище(Неопределено, ПараметрыВыполнения.ИдентификаторФормы));
// //
// // Если Вы используете стандартные механизмы, они позаботятся об этом.
// // В противном случае убедитесь, что адрес и идентификатор формы передаются правильно.
//
// ОписаниеЗадания = Новый Структура;
// ОписаниеЗадания.Вставить("ИмяМетода", "ОбщийМодуль.МояСервернаяПроцедура.МояДлительнаяОперация");
//
// // Параметры для фонового задания
// ПараметрыМетода = Новый Массив;
// ПараметрыМетода.Добавить(НеобходимыеДанныеДляОперации); // Например, массив обрабатываемых объектов
// ПараметрыМетода.Добавить(ПараметрыВыполнения.АдресРезультата); // Передаем адрес для записи результата
//
// // Запуск фонового задания
// Возврат ФоновыеЗадания.Выполнить(ОписаниеЗадания.ИмяМетода, ПараметрыМетода,
// Истина, ИдентификаторКлиентскогоСеанса,
// ОписаниеЗадания.Имя, АдресРезультата);
// КонецФункции
// Вызов этой функции с клиента:
// &НаКлиенте
// Процедура ЗапуститьОперацию()
// // ...
// ПараметрыДляЗапуска = Новый Структура;
// ПараметрыДляЗапуска.Вставить("АдресРезультата", АдресРезультатаДлительнойОперации);
// ПараметрыДляЗапуска.Вставить("ИдентификаторФормы", ИдентификаторТекущейФормы);
//
// // Вызов серверной функции для запуска фонового задания
// ЗапуститьМоюДлительнуюОперациюВФоне(ПараметрыДляЗапуска);
// // ...
// КонецПроцедуры
Запись результата в фоновом задании.
В самой процедуре, которая выполняется в фоновом задании, мы получаем переданный АдресРезультата. Используя этот адрес в качестве второго параметра метода ПоместитьВоВременноеХранилище(), мы фактически записываем готовый результат в то же самое хранилище, которое было инициализировано в клиентском сеансе. Данные, помещенные таким образом, станут доступны из родительского сеанса только после завершения фонового задания.
// В общем серверном модуле, где находится код фонового задания
// &НаСервереБезКонтекста
// Процедура МояДлительнаяОперация(МассивПараметров, АдресДляРезультата) Экспорт
//
// Попытка
// // ... выполнение длительной операции
// РезультатОперации = Новый ТаблицаЗначений; // или любой другой сериализуемый объект
// РезультатОперации.Колонки.Добавить("Сообщение");
// РезультатОперации.Строки.Добавить().Сообщение = "Операция успешно завершена!";
//
// // Помещаем полученный результат по переданному адресу
// ПоместитьВоВременноеХранилище(РезультатОперации, АдресДляРезультата);
//
// Исключение
// // Обработка ошибок, если операция завершилась с ошибкой
// РезультатОперации = Новый Структура("Ошибка", ОписаниеОшибки());
// ПоместитьВоВременноеХранилище(РезультатОперации, АдресДляРезультата);
// КонецПопытки;
//
// КонецПроцедуры
Таким образом, благодаря привязке к ИдентификаторФормы и передаче адреса, платформа получает информацию о том, что фоновое задание должно взаимодействовать с конкретным клиентским сеансом. Это не только упрощает передачу данных, но и повышает вероятность того, что фоновое задание будет запущено на том же сервере приложений, что и сеанс пользователя.
После реализации первого решения, мы можем столкнуться с тем, что в некоторых конфигурациях (особенно старых, использующих обычные формы, например, УПП 1.3), результат все равно не будет доступен или будет проявляться нестабильно. Оказывается, ключевым моментом здесь является контекст получения данных из временного хранилища. Для тех, кто разрабатывает решения с нуля, может быть полезен пример реализации выполнения длительных операций без БСП, где подробно рассмотрено отслеживание результата и индикация прогресса.
Проблема, выявленная автором темы, заключалась в том, что в обычных формах попытка получить данные из временного хранилища напрямую &НаКлиенте могла не давать желаемого результата. В отличие от управляемых форм, где четко разделены клиентские и серверные вызовы с помощью директив компиляции (&НаКлиенте, &НаСервере, &НаСервереБезКонтекста), в обычных формах этот механизм может работать иначе. Несмотря на то, что сама функция ПолучитьИзВременногоХранилища() может быть доступна в клиентском контексте, ее внутреннее выполнение может требовать серверного обращения, которое в обычном приложении не всегда явно выражено. В управляемых формах клиентский вызов всегда идет на сервер и обратно, что обеспечивает корректную обработку. В обычных же формах, если получение данных не инициировано явно на сервере, оно может не "увидеть" результат, помещенный другим rphost-процессом.
Следовательно, для обеспечения гарантированной доступности результата, особенно в обычных формах, нам следует всегда выполнять получение данных из временного хранилища через серверный вызов. Давайте посмотрим на примеры:
// Неправильно для обычных форм (может работать нестабильно или не работать вовсе):
// &НаКлиенте
// Процедура ПолучитьРезультатКлиент()
// // АдресРезультатаДлительнойОперации - это адрес, полученный в клиентском сеансе.
// Результат = ПолучитьИзВременногоХранилища(АдресРезультатаДлительнойОперации);
// Если Результат <> Неопределено Тогда
// Сообщить("Результат получен на клиенте: " + Результат.Сообщение);
// Иначе
// Сообщить("Результат на клиенте не найден.");
// КонецЕсли;
// КонецПроцедуры
Вместо этого, мы должны явно вызвать серверную функцию, которая уже на сервере обратится к временному хранилищу:
// Правильно для обычных форм (вызов на сервере):
// &НаКлиенте
// Процедура ПолучитьРезультатИПоказать()
// // Адрес, который был передан в фоновое задание и куда был записан результат
// СерверныйАдрес = АдресРезультатаДлительнойОперации;
//
// // Вызываем серверную функцию для получения результата
// РезультатДлительнойОперации = ПолучитьРезультатССервера(СерверныйАдрес);
//
// Если РезультатДлительнойОперации <> Неопределено Тогда
// Если ТипЗнч(РезультатДлительнойОперации) = Тип("Структура") И РезультатДлительнойОперации.Свойство("Ошибка") Тогда
// Сообщить("Ошибка выполнения операции: " + РезультатДлительнойОперации.Ошибка);
// Иначе
// // ... обработка полученного результата
// Сообщить("Результат операции: " + РезультатДлительнойОперации.Сообщение);
// КонецЕсли;
// Иначе
// Сообщить("Результат длительной операции пока не доступен или не найден.");
// КонецЕсли;
// КонецПроцедуры
// &НаСервереБезКонтекста
// Функция ПолучитьРезультатССервера(Адрес) Экспорт
// // На сервере получаем данные из временного хранилища по переданному адресу
// Возврат ПолучитьИзВременногоХранилища(Адрес);
// КонецФункции
Использование директивы &НаСервереБезКонтекста для функции ПолучитьРезультатССервера гарантирует, что эта функция будет выполнена на сервере приложений, без привязки к контексту формы, что является наиболее надежным способом взаимодействия с серверными данными и временным хранилищем в 1С. Таким образом, даже если фоновое задание отработало на другом сервере, централизованный механизм временного хранилища кластера обеспечит доступность данных для любого рабочего процесса, который делает запрос с корректным адресом.
Помимо рассмотренных конкретных решений, важно учитывать общие принципы работы с длительными операциями и временным хранилищем в многосерверной среде 1С. Эти рекомендации помогут избежать многих потенциальных проблем и обеспечить стабильность и производительность системы.
Сериализация данных.
Критически важно, чтобы любые данные, которые мы помещаем во временное хранилище, были сериализуемыми. Это означает, что их можно преобразовать в поток байтов для хранения и последующего восстановления. Если данные не поддерживают сериализацию (например, объекты СправочникОбъект, ДокументОбъект без их предварительной выгрузки в Структура, Массив или Соответствие, а также COM-объекты), они могут быть потеряны или недоступны, особенно при взаимодействии между разными рабочими процессами rphost или при переключении вызова на другой сервер кластера. Всегда преобразуйте ссылочные объекты в их идентификаторы или в простые сериализуемые структуры перед помещением во временное хранилище.
Время жизни данных и очистка.
Данные во временном хранилище, привязанные к уникальному идентификатору формы (как мы рассмотрели выше), будут храниться в течение всего времени жизни этой формы. Если привязка к форме не выполняется, данные удаляются после завершения следующего серверного вызова. Это означает, что для временного хранилища без привязки к форме необходимо получать данные сразу же после их помещения. Также важно помнить о ручной очистке хранилища, когда данные больше не нужны, чтобы избежать его переполнения, особенно при хранении больших объемов информации. Это можно сделать, поместив Неопределено по тому же адресу после того, как результат был успешно обработан.
Масштабирование и кластер 1С.
Использование нескольких серверов 1С в одном кластере – это стандартная и необходимая практика для высоконагруженных систем (например, при количестве активных пользователей более 200). Такая архитектура обеспечивает горизонтальное масштабирование и повышает отказоустойчивость. Однако, для эффективной работы в такой среде требуется внимательное отношение к организации кода, особенно в части работы с данными и фоновыми заданиями. Не стоит путать два сервера в одном кластере с двумя независимыми кластерами, работающими с одной базой SQL – это разные архитектурные подходы с разными правилами взаимодействия.
Предотвращение таймаутов.
Все длительные вызовы с клиента на сервер, которые потенциально могут занимать более 8-10 секунд (в зависимости от настроек веб-сервера и 1С), рекомендуется выполнять асинхронно с помощью фоновых заданий. В качестве основы можно взять пример асинхронного выполнения длительных операций для БСП актуальных версий. Это позволяет избежать блокировки интерфейса пользователя и предотвращает ошибки, такие как таймауты веб-сервера (например, 504 Gateway Timeout), которые возникают, когда клиент слишком долго ожидает ответа от сервера.
Версии платформы и возможные ошибки.
В старых версиях платформы 1С (например, 32-битная платформа 8.2, часто используемая с УПП 1.3) могли возникать ошибки, связанные с ограничением на размер временных файлов, используемых для хранилища. Это могло приводить к ошибкам типа "Runtime Error" при превышении 2 ГБ данных. Поэтому рекомендуется регулярно обновлять платформу и конфигурацию 1С, так как новые версии часто содержат исправления и оптимизации, улучшающие работу с временным хранилищем и фоновыми заданиями.
Таким образом, для надежной работы с длительными операциями и временным хранилищем в многосерверном кластере 1С нам необходимо комплексно подойти к решению задачи: обеспечить правильную привязку данных к клиентскому сеансу через ИдентификаторФормы и всегда выполнять получение результата из хранилища в серверном контексте, особенно если конфигурация использует обычные формы. Эти шаги помогут вам построить устойчивую и эффективную систему, способную обрабатывать большие объемы данных и запросов.