Передача данных между сеансами и Повторное использование значений между сеансами

09.06.18

Разработка - Механизмы платформы 1С

Вам никогда не хотелось в одном сеансе повторно использовать значения, однажды уже вычисленные в другом сеансе?

Про повторное использование значений в рамках одного сеанса сказано уже достаточно. Давайте подумаем, как сделать то же самое, но глобально для ИБ в рамках всех сеансов. Спойлер: без вэб-сервера ничего не получится.

Upd: Внимание! Приведенный метод не является универсальным инструментом на все случаи жизни и имеет существенные ограничения. Прежде чем пилить код - обязательно читайте главу "Update: Потокобезопасность"

В чем проблема

Банальная инициализация параметров какого-то длительного фонового действия может занимать по нескольку секунд. А если это действие распараллелить - то с ростом производительности за счет многопоточной обработки - мы получим рост издержек на инициализацию параметров. Даже в пользовательских сеансах встречаются операции а-ля получения каких-то значений по-умолчанию, продолжающихся достаточно длительное время. Особой проблемы в рамках одного сеанса это как правило не представляет, но если 200 сеансов "долбят" базу данных одними и теми-же вопросами - это уже становится ощутимо.

Согласитесь, было бы здорово прочитать, например, параметры обмена данными один раз и использовать их дальше из оперативной памяти. В общем то такое уже встречается сплошь и рядом: в типовой УТ наверно сотня модулей с повторным использованием возвращаемых значений. Но есть проблема: все это работает только в рамках одного сеанса и у нас нет средства для передачи данных между сеансами (кроме разве что базы данных, но ее использование в этом качестве само по себе является анти-паттерном)

Надо сказать, что в части случаев разделение возвращаемых значений по сеансам действительно оправдано: никто не даст гарантии, что одному пользователю доступны, например, те же склады, что и второму. Или организация по умолчанию у них одинаковая. Но есть и другие ситуации: например, ответственные лица организации от сеанса к сеансу никак не изменятся (сама организация может быть недоступна, но если уж у тебя спрашивают список ответственных лиц по этой организации - двух ответов тут быть не может). Как и правила регистрации объектов, часто используемые классификаторы и т.д.

Решение

С версии 8.3.9 в платформе появилась возможность "завесить" один сеанс и обращаться к нему со всякими дурацкими вопросами с HTTP запросами. Как и в любом другом сеансе, результаты вызова функций можно кэшировать с помощью платформенной функциональности повторного использования возвращаемых значений. Сам механизм предназначен конечно несколько для другого, но для нашей цели тоже вполне применим.

Схематично все это можно изобразить так:

Таким образом, мы как-бы "заворачиваем" вызовы из разных сеансов в один посредством REST сервиса, а в нем уже используем штатную функциональность модулей с повторным использованием возвращаемых значений.

Детали

Для того, чтобы все это взлетело - необходимо создать HTTP сервис и установить в нем параметр Повторное использование сеансов в значение Использовать автоматически. Время жизни сеанса устанавливайте по своему усмотрению, у меня стоит 2.592.000 (месяц). Будьте внимательны: это не время жизни сеанса, это время бездействия сеанса, после которого он будет завершен. Дополнительно, в файле default.vrd (файл описания сервиса, лежит в папке публикации) необходимо установить параметр poolSize="1". Все это вполне легальные действия, никакой партизанщины.

Далее, варианты могут быть разными, вы можете писать какие-то специфичные функции установки и чтения параметров, но в случае написания универсальной процедуры, "оборачивающей" любую процедуру в повторно-используемую оболочку - получится следующее.

Программный интерфейс:

Функция ВыполнитьСПовторнымИспользованием(ИмяМетода, МассивПараметровМетода) Экспорт
    
    ПараметрыСоединения = ПараметрыСоединенияСЭтойБазой();
    
    HTTPСоединение = Новый HTTPСоединение(ПараметрыСоединения.Сервер, ПараметрыСоединения.порт, ПараметрыСоединения.Пользователь, ПараметрыСоединения.Пароль);
    
    HTTPЗапрос = Новый HTTPЗапрос(ПараметрыСоединения.АдресРесурса);
    
    ПараметрыВызова = Новый Структура();
    ПараметрыВызова.Вставить("ИмяМетода", ИмяМетода);
    ПараметрыВызова.Вставить("МассивПараметров", МассивПараметровМетода);
    HTTPЗапрос.УстановитьТелоИзСтроки(XMLСтрока(Новый ХранилищеЗначения(ПараметрыВызова)));
    
    HTTPОтвет = HTTPСоединение.ОтправитьДляОбработки(HTTPЗапрос);
    
    Если HTTPОтвет.КодСостояния = 200 Тогда
        ТекстОтвета = HTTPОтвет.ПолучитьТелоКакСтроку();
        Возврат XMLЗначение(Тип("ХранилищеЗначения"), ТекстОтвета).Получить();
    Иначе
        ВызватьИсключение HTTPОтвет.ПолучитьТелоКакСтроку();
    КонецЕсли;
    
КонецФункции

Функция ПараметрыСоединенияСЭтойБазой()
    
    ПараметрыСоединения = Новый Структура();
    
    ПараметрыСоединения.Вставить("Сервер", "127.0.0.1");
    ПараметрыСоединения.Вставить("Порт", 8008);
    ПараметрыСоединения.Вставить("Пользователь", "Справочная");
    ПараметрыСоединения.Вставить("Пароль", "123");
    ПараметрыСоединения.Вставить("АдресРесурса", "/TM/hs/Reference/GetReference");
    
    Возврат ПараметрыСоединения;
    
КонецФункции

Здесь параметры соединение введены хардкодом, что как бы не очень хорошо, но если мы будем делать все по-правильному и читать их каждый раз из БД - эффект от повторного использования возвращаемых значений может быть существенно снижен.

Метод сервиса:

Функция GetReference(Запрос)
    
    ПараметрыСтрока = Запрос.ПолучитьТелоКакСтроку();
    
    СтруктураПараметров = XMLЗначение(Тип("ХранилищеЗначения"), ПараметрыСтрока).Получить();
    
    Если СтруктураПараметров.МассивПараметров.Количество() = 0 Тогда
        ВозвращаемоеЗначение = СправочнаяСлужебныйПовтИсп.ПовторноИспользуемыйМетод0(СтруктураПараметров.ИмяМетода);
    ИначеЕсли СтруктураПараметров.МассивПараметров.Количество() = 1 Тогда
        ВозвращаемоеЗначение = СправочнаяСлужебныйПовтИсп.ПовторноИспользуемыйМетод1(СтруктураПараметров.ИмяМетода, СтруктураПараметров.МассивПараметров[0]);
    ИначеЕсли СтруктураПараметров.МассивПараметров.Количество() = 2 Тогда
        ВозвращаемоеЗначение = СправочнаяСлужебныйПовтИсп.ПовторноИспользуемыйМетод2(СтруктураПараметров.ИмяМетода, СтруктураПараметров.МассивПараметров[0], СтруктураПараметров.МассивПараметров[1]);
    ИначеЕсли СтруктураПараметров.МассивПараметров.Количество() = 3 Тогда
        ВозвращаемоеЗначение = СправочнаяСлужебныйПовтИсп.ПовторноИспользуемыйМетод3(СтруктураПараметров.ИмяМетода, СтруктураПараметров.МассивПараметров[0], СтруктураПараметров.МассивПараметров[1], СтруктураПараметров.МассивПараметров[2]);
    /// тут дальнейшая идея думаю понятна...
    Иначе
        Ответ = Новый HTTPСервисОтвет(500);
        Ответ.УстановитьТелоИзСтроки("Количество параметров более 9 не поддерживается");
        Возврат Ответ;
    КонецЕсли;
    
    Ответ = Новый HTTPСервисОтвет(200);
    Ответ.УстановитьТелоИзСтроки(XMLСтрока(Новый ХранилищеЗначения(ВозвращаемоеЗначение)));
    Возврат Ответ;
    
КонецФункции

Функции повторно-используемого модуля:

Функция ПовторноИспользуемыйМетод0(ИмяМетода) Экспорт
    
    Возврат Вычислить(ИмяМетода);
    
КонецФункции

Функция ПовторноИспользуемыйМетод1(ИмяМетода, Параметр1) Экспорт
    
    Возврат Вычислить(ИмяМетода + "(Параметр1)");
    
КонецФункции

Функция ПовторноИспользуемыйМетод2(ИмяМетода, Параметр1, Параметр2) Экспорт
    
    Возврат Вычислить(ИмяМетода + "(Параметр1, Параметр2)");
    
КонецФункции

Функция ПовторноИспользуемыйМетод3(ИмяМетода, Параметр1, Параметр2, Параметр3) Экспорт
    
    Возврат Вычислить(ИмяМетода + "(Параметр1, Параметр2, Параметр3)");
    
КонецФункции

/// и так далее...

Подводные камни

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

  • Первый вызов (в рамках которого происходит инициализация общего сеанса) в моем случае длится около 1 секунды. Этот параметр зависит от того, что у вас за конфа. У меня УТ 11.3.
  • Последующие вызовы, которые не инициализируют сеанс, а обращаются к уже готовому, занимают 0,015 - 0,02с, что вполне неплохо (это только время самой сетевой транзакции, если будет выполняться трехэтажный запрос - реальное время будет больше). Но я подозреваю, что тут все сильно зависит от сетевой архитектуры.

Здесь важно обратить внимание: инициализация длинной в 1 секунду будет проходить только один раз для всех возможных методов и наборов их значений, а не для каждого нового метода/значения.

Во-вторых у этого метода есть ограничение сверху, некий "потолок использования", который обусловлен размером оперативной памяти, которую вы готовы выделить под кэширование значений. Хотя, здесь невозможно не заметить, что если вы будете кэшировать эти данные традиционным способом, через повторноиспользуемый модуль для каждого сеанса - это займет памяти больше, кратно количеству сеансов, поэтому здесь есть как ограничение так и простор для экономии.

Дополнительные возможности

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

  • передача данных между сеансами
  • хранение актуальных состояний и управление общим оборудованием, например, одним фискальником на несколько рабочих мест
  • управление соединением с другой ИБ (иметь COM-коннект в одном сеансе и использовать его из остальных)

И не стоит забывать, что все это будет работать только в версии 8.3.9 и выше. Удачи!

Update: Потокобезопасность

Комментарий Юрия Дешина подтолкнул меня к, как теперь кажется, очевидной мысли: заворачивание вызова нескольких сеансов в один, при некоторых обстоятельствах может привести к образованию очереди и массовому отказу в обслуживании вызовов. Так, пока сеанс Х выполняет какие-то вычисления, "заказанные" сеансом 1, сеанс 2 не сможет получить никакие данные (в том числе ранее закэшированные) от сеанса Х. При этом сеанс 2 будет "висеть", пока не будет достигнут тайм-аут (параметр poolTimeout в файле default.vrd), по достижении тайм-аута будет возвращен ответ 500.

В общем, как обычно, когда кто-то встает и спрашивает "а что там с потокобезопастностью?" - все становится сильно сложнее чем было до этого. Но это не значит, что задавать такие вопросы не нужно.

Что же нам со всем этим делать?

Ну во-первых, очень осторожно пользуйтесь вызовом такой процедуры в обработке проведения или внутри какой-либо другой транзакции записи.

Во-вторых, можно наметить следующие пути снижения рисков образования очереди в высоконагруженных системах:

  1. Не использовать универсальных обработчиков, которые отвечают сразу за все (например такие, как описано выше). Т.е. если сервис будет отвечать и за расчет себестоимости и за соединение с соседней базой по COM - неизбежно будут возникать ситуации, когда мы не можем подключиться к соседней базе, потому что считаем себестоимость в этой (я утрирую, но мысль думаю понятна). Однако, если мы примем концепцию типа "одно значение - один сервис", то все будет логично: пока значения нет - все сеансы сидят и ждут когда оно появится. Появилось значение - раздали его всем.
  2. Увеличивать число сеансов, раздающих значения. (параметр poolSize в файле default.vrd). Это экстенсивный путь, но до какой-то степени его можно считать рабочим, поскольку такое решение позволит избежать завешивания: при занятости одного сеанса - платформа автоматически переключит вызов на второй, третий, пятый и т.д., но в этом случае значение вычисленное в сеансе Х1 не будет доступно в сеансе и Х2 и будет вычисляться там заново. Можно думать об этом примерно так: все значения кэшируются в сеансе Х1, но он может быть недоступен, в этом случае вызов будет обработан сеансом Х2 (Х3...Хn), но значение будет вычислено заново.
  3. Городить более сложные схемы взаимодействия с кэширующими сеансами, при которых использование сервиса сильно усложнится, но и вероятность получить отказ будет минимальной. Например такую:

Также, в ключе потокобезопастности стоит обратить внимание на тот факт, что повторное использование сеанса производится в разрезе пользователя, под которым вы подключаетесь к сервису. т.е. если обстоятельства заставляют вас гарантировать отсутствие взаимного влияния каких-то процессов, вы можете сделать это не только сделав новый сервис, но и сделав новый логин к тому же сервису.

В целом же, стоит признать что приведенный в статье метод нельзя использовать как универсальное средство кэширования данных, но при достаточной глубине понимания вопроса из него можно почерпнуть идею для межсеансового взаимодействия.

См. также

Поинтегрируем: сервисы интеграции – новый стандарт или просто коннектор?

Обмен между базами 1C Администрирование СУБД Механизмы платформы 1С Платформа 1С v8.3 Бесплатно (free)

В платформе 8.3.17 появился замечательный механизм «Сервисы интеграции». Многие считают, что это просто коннектор 1С:Шины. Так ли это?

11.03.2024    4625    dsdred    53    

73

Как готовить и есть массивы

Механизмы платформы 1С Платформа 1С v8.3 Бесплатно (free)

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

24.01.2024    5312    YA_418728146    25    

64

Планы обмена VS История данных

Обмен между базами 1C Механизмы платформы 1С Платформа 1С v8.3 Бесплатно (free)

Вы все еще регистрируете изменения только на Планах обмена и Регистрах сведений?

11.12.2023    6440    dsdred    36    

112

1С-ная магия

Механизмы платформы 1С Бесплатно (free)

Язык программирования 1С содержит много нюансов и особенностей, которые могут приводить к неожиданным для разработчика результатам. Сталкиваясь с ними, программист начинает лучше понимать логику платформы, а значит, быстрее выявлять ошибки и видеть потенциальные узкие места своего кода там, где позже можно было бы ещё долго медитировать с отладчиком в поисках источника проблемы. Мы рассмотрим разные примеры поведения кода 1С. Разберём результаты выполнения и ответим на вопросы «Почему?», «Как же так?» и «Зачем нам это знать?». 

06.10.2023    18508    SeiOkami    46    

118

Дефрагментация и реиндексация после перехода на платформу 8.3.22

Механизмы платформы 1С Платформа 1С v8.3 Бесплатно (free)

Начиная с версии платформы 8.3.22 1С снимает стандартные блокировки БД на уровне страниц. Делаем рабочий скрипт, как раньше.

14.09.2023    12113    human_new    27    

74

Валидация JSON через XDTO (включая массивы)

WEB-интеграция Универсальные функции Механизмы платформы 1С Платформа 1С v8.3 Конфигурации 1cv8 Бесплатно (free)

При работе с интеграциями рано или поздно придется столкнуться с получением JSON файлов. И, конечно же, жизнь заставит проверять файлы перед тем, как записывать данные в БД.

28.08.2023    8850    YA_418728146    6    

141

Внешние компоненты Native API на языке Rust - Просто!

Механизмы платформы 1С Платформа 1С v8.3 Конфигурации 1cv8 Бесплатно (free)

Внешние компоненты для 1С можно разработывать очень просто, пользуясь всеми преимуществами языка Rust - от безопасности и кроссплатформенности до удобного менеджера библиотек.

20.08.2023    6290    sebekerga    54    

94

Все скопируем и вставим! (Буфер обмена в 1С 8.3.24)

Механизмы платформы 1С Платформа 1С v8.3 Конфигурации 1cv8 Бесплатно (free)

Рассмотрим новую возможность 8.3.24 и как её можно эффективно использовать

27.06.2023    16021    SeiOkami    31    

103
Комментарии
В избранное Подписаться на ответы Сортировка: Древо развёрнутое
Свернуть все
1. Lem0n 420 09.06.18 10:47 Сейчас в теме
эту задачу решает РС с измерениями: имя метода, хеш параметров, и ресурсом хранилищем + все названные ограничения снимаются(остается только ограничение на несериализуемые данные)
3. Lem0n 420 09.06.18 11:13 Сейчас в теме
(2)
1. Британские ученые
2. Сокеты, а не http протокол
Проверить бы все на реалиях 1С
4. m-rv 962 09.06.18 11:19 Сейчас в теме
(3)
1. а вы считаете себя умнее?
2. such as sockets
так кто ж мешает то?
jONES1979; +1 Ответить
5. пользователь 09.06.18 11:28
Сообщение было скрыто модератором.
...
6. awk 741 09.06.18 16:41 Сейчас в теме
7. t.v.s. 111 09.06.18 17:36 Сейчас в теме
Плюсик за идею, возьму на вооружение
Оффтоп
8. blackhole321 1303 09.06.18 19:33 Сейчас в теме
У Вас в качестве примера доп. возможностей приведена возможность использования com объектов. Как будут обстоять дела с потокобезопасностью?
Или все сводится к последовательным вызовам в одном сеансе, т.е. по сути это один поток выполнения?
m-rv; kote; +2 Ответить
10. m-rv 962 12.06.18 09:31 Сейчас в теме
(8) Спасибо за комментарий, дополнил статью (раздел Потокобезопасность)
9. zeegin 114 09.06.18 21:53 Сейчас в теме
Согласитесь, было бы здорово прочитать, например, параметры обмена данными один раз и использовать их дальше из оперативной памяти.


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

Все это решение - антипаттерн. Не следует кешировать данные в целом, просто потому что не следует вообще кешировать данные вне сеанса.

Вам следует изучить как работает временное хранилище, параметры сеанса и повторно используемые модули и почему они работают так.

В целом утверждение, что повторно используемые модули кешируются на время сенаса - ложь. Время жизни 20 минут, но в некоторых случаях 6 минут.

Попытка кэшировать через HTTP сервер - хорошая идея, но глупая. Время жизни сенасов HTTP контроллируется только с версии 8.3.10 и по умолчанию соединение может повторно использоваться только 20 секунд. Фактически вы кеш с 20 минут изменили на 20 секунд.

Кроме того многие повторно используемые модули могут зависит от прав пользователя, которые рассчитаны для конкретного сеанса, кеширование таких значений для базы потенциально приводит к проблемам.
wowik; PLAstic; asdf_88; anchovy; a_titeev; max_st; CSiER; +7 1 Ответить
11. Pixar0000 12.06.18 13:45 Сейчас в теме
(9) еще дописать RLS на кешируемые данные )))
идея, голой воды идея, но автору за то, что расписал - респект
12. SShipilov 25.06.19 11:09 Сейчас в теме
(9)
Ingvar, очень категорично отзываетесь. Да, автор не описал некоторые важные моменты в реализации решения, например особенности публикации отдельных http-сервисов для такого решения.
Однако решение всё-таки отличное для ряда случаев. Например:
- когда в определенный период времени сервис должен выдавать заполненную страницу с фиксированными данными,
- на сторону сервиса для выполнения запроса часто поступает набор одинаковых параметров;
Действительно нет смысла заполнять страницу сначала по каждому такому запросу. Поэтому аргумент
Не следует кешировать данные в целом, просто потому что не следует вообще кешировать данные вне сеанса.
совсем не уместен.

Относительно времени "жизни" конкретного кэшированного значения в рамках сеанса - вы высказались верно. В таких случаях действительно придет на помощь временное хранилище. А вот сеанс, который обслуживает наш сервис можно подерживать дополнительными запросами со стороны, например через регламентное задание. Лучше мелкие вызовы для поддержки сеанса, чем заполнение данных.

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