Мегатонны макулатуры легким движением руки

в 15:36, , рубрики: ERP-системы, oracle, oracle database, Блог компании Ultima, Программирование

Добрый день. В этой статье расскажем о том, как устроена печать в нашей платформе.

Немного истории

Для понимания, полезно будет рассказать о истории возникновения этой функции. Изначально решение что называется было «в лоб». Использовался виндовый спулер и печать осуществлялась через стандартные процедуры. Собственно, такое решение продержалось недолго, ровно до того момента, как потребовалось реализовать печать с сервера приложений, а не из клиентского приложения.
На всякий случай приведу схему из первой статьи:

Мегатонны макулатуры легким движением руки

Как видно, сервера приложений стояли в датацентре. И офисы были соединены в единую сеть. Как только мы реализовали печать с сервера приложений образовалось несколько проблем:

  1. Загрузка каналов связи выросла очень значительно. В некоторых офисах до 100%
  2. Сервер приложений начал зависать.
  3. Иногда отправленная на печать бумажка просто не распечатывалась.
  4. Упала производительность системы (пользователи начали жаловаться, что им приходится ждать завершения операций по нескольку минут)
  5. Перепутывание порядка документов при печати

Собственно расследование быстро показало, что причинами высокой загрузки каналов, как, наверное, все догадались, была передача отрендеренной печатной формы непосредственно на принтер.
С причиной номер 2 пришлось повозиться как следует, но выяснилось, что драйвера принтеров пишут тоже люди! Это они (драйвера) зависали при высокой нагрузке и тянули за собой весь сервер приложений. Вывести его из кататонического состояния можно было только его перезапуском.
Причины 3 и 5 нашлись в принт-спулере Windows.
С проблемой 4 удалось разобраться, внимательно исследуя ожидания сессий. Причины, как теперь кажется, лежат на поверхности, но тогда мы были молоды… Обнаружилось, что из-за печати с сервера приложений сильно выросло время доступа к принтеру — теперь мы печатали через интернет-каналы, а не по локальной сети. Все это время, транзакция продолжала блокировать объекты системы, на которые натыкались другие серверные вызовы и устраивали нам веселые минутки. Можно было бы конечно фиксировать транзакцию перед отпавкой на печать, но это неправильно с нашей точки зрения. Приносим извинения, но про это в другой статье про управление транзакциями.
По рассказам знакомых, я знаю что такие проблемы были не только у нас.
Тогда и решили сделать то, что на схеме обозначено как принт-сервер.

Печатные формы

Нужно сделать отступление и рассказать, что же собственно мы печатали.
Для печати в платформе реализована такая штука как «Печатные формы». Извините, более оригинального названия мы не смогли придумать. Печатная форма — это шаблон для отчетника и скрипт, который готовит табличку с данными на основе какого-то заданного разработчиком алгоритма. Да, иногда встречаются случаи, когда это нельзя сформулировать в виде одного запроса. Надо уточнить — не в виде таблицы в базе данных, а в виде объекта “типа” DataTable. В кавычках — потому, что используется собственный класс для хранения таблиц, менее требовательный к памяти и компактнее сериализуемый.
Если говорить о деталях, то используется отчетник от DevExpress XtraReports.
И скрипт и шаблон отчета доступны для редактирования в run-time через GUI главного клиентского приложения.
Скрипт, напомню, всегда выполняется на сервере приложений. Подавляющая часть бумаг, печатаемых торговой организацией — это всевозможные счета, счета-фактуры, товарные накладные, товарные чеки, листы набора на складе, и прочая, и прочая. При этом, шаблон то не меняется, меняются только данные!
Таким образом, первое, что мы сделали — вынесли на сервер печати задачу рендеринга и отсылки на принтер отрендеренной формы. Сервер приложений только готовил данные и отсылал их на сервер печати. Экономия на трафике — с (в среднем) 2Мб на документ до 40кб, шаблоны сервер печати кеширует локально.

Устройство и возможности

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

Итак, что мы имеем сейчас.

Синхронная и асинхронная печать.

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

Мегатонны макулатуры легким движением руки

Работающие в отдельных транзакциях (и потоках) внутри сервера приложений процедуры PrintBuilder и PrintSender занимаются обработкой очередей поступивших заданий. Технически — это потоки внутри сервера приложений, исполняющие бесконечный цикл. Количество этих поток настраивается администратором в зависимости от нагрузки. PrintBuilder обрабатывает задания поступившие асинхронно, выполняет скрипты для подготовки данных печатных форм и передает их в следующую очередь к PrintSender. В ту же очередь попадают и синхронные задания на печать.
В свою очередь PrintSender достает задания из очереди и передает их на сервер печати.
Как видно, при таком подходе радикально сокращается время блокирования в Транзакции 1, а чем меньше время блокирования, тем выше количество транзакций которое может обработать сервер!

Пакетная печать

Гарантированная печать на принтере документов в заданной последовательности. Вне зависимости от других заданий, все документы в пакете будут распечатаны неразрывно. Возможность потребовалась нам при реализации печати документов для водителей (пример применения в практике реального бизнеса). Это задача когда на принтер надо печатать Маршрутный лист (документ с перечислением всех адресов и картой) и далее бухгалтерские документы по каждому заказу. Все было хорошо пока водителей не стало больше 100 и виндовый спулер не начал путаться. Ситуация ухудшалась, если кто-то другой пытался напечатать другие документы на этот же принтер. Эти документы по какой-то причине иногда оказывались среди документов для водителя. Пришлось отказаться от спулера и сделать свой.

Гарантированная доставка

Документ, отправленный на печать, будет гарантированно отправлен на принтер. Если принтер недоступен, или недоступен офис с принтсервером система будет дожидаться восстановления связи и слать администраторам SMS и email.

Вот схема, иллюстрирующая схему работы принт-сервера:

Мегатонны макулатуры легким движением руки

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

Учет печати

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

Виртуализация принтеров

Принтеры, на которые можно печатать из системы перечисляются в настройках системы и подключаются только к серверу печати. Это значительно упрощает работу админам — не надо подключать принтера к каждому узлу кластера серверов приложений, и на каждой клиентской машине, устанавливать там драйвера и заниматься прочим шаманством. Кроме того, перечисление принтеров в системе позволяет легко реализовать UI для выбора принтера обычным пользователям. Например на какой принтер печатать бумаги на складе.

Мегатонны макулатуры легким движением руки

Таким образом, каждый принтер имеет свой идентификатор, который сохраняется при изменении принтера, и код для печати выглядит примерно так:

Мегатонны макулатуры легким движением руки

В данном примере распечатывается печатная форма для заданного документа на принтер, указанный в сотруднике.

Очереди в RDBMS

Обещали рассказать про очереди. Рассказываем.
В подсистеме печати, как видно, активно используется механизм очередей. К счастью, в Oracle Database есть возможность организовать их внутри базы данных, не прибегая к специальным отдельным сервисам. Реализуются они достаточно просто с использованием конструкции

select .. for update skip locked

. Выполнение такого запроса возвращает только НЕ заблокированные другими транзакциями строки. Некоторое неудобство доставляет то, что его нельзя использовать совместно с rownum, т.е. можно только все строки получить. Однако, есть обходной маневр — все сказанное верно только для выполнения запроса из клиентского (по отношению к БД) приложения! А значит, можно написать процедуру:

Мегатонны макулатуры легким движением руки

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

Например, аналогичным образом решается задача пересчета цен для 400 000 товаров.

Предвосхищая вопрос, почему не воспользовались Oracle Advanced Queuing, отвечаем:

  1. Мы используем Managed ODP.NET, а он до сих пор не поддерживает AQ.
  2. Использование своих очередей позволяет хранить данные в структурированном виде, и реализовать встроенные механизмы управления этими самыми очередями:
    Мегатонны макулатуры легким движением руки

Заключение

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

Автор: Rupper

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js