Структура
Ограничения.
При формировании подписок от модифицированных копий приложений, развернутых на одном сервере необходимо учитывать, что брокер запрещает создавать одноименные подписки с разными критериями фильтрации. Разные приложения могут создавать одноименные подписки. Сообщение по одноименным подпискам доставляется только одному произвольному приложению.
Следует учитывать, что идентификатор агента не уникален среди всех приложений, например, если на основе одного приложения копированием делается другое приложение с некоторыми изменениями. Поэтому идентификатор агента рассматривается внутри пространства имен, определяемого уникальным именем приложения.
Группа агентов по-расписанию.
При активации КМА должна быть создана одна задача для получения сигнала на запуск произвольной группы агентов и распределена на исполнение в LongRunning пул потоков. Задача слушает сигналы из 2х JMS-очередей. Одна для режима ANY, другая - для MANY. При получении сигнала, задача должна сформировать новую задачу получения сигнала и поставить её в очередь пула потоков (ExecutorService,submit()), а сама продолжить работу по запуску агентов группы в своем потоке. При формировании новой задачи лучше не создавать новый объект, а использовать текущий ( submit(this) ) - поможем GC. Активация агента в потоке, получившем сигнал, гарантирует наличие свободного потока и активацию без ожидания в очереди. В противном случае, если поток, получивший сигнал - последний в пуле, то выставление отдельной задачи на запуск агента поставит её в очередь, а сигнал уже не будет доставлен на другой сервер, в котором есть свободный поток.
Если сигнал не получен, необходимо форсировать переключение ядра ЦПУ на исполнение другого потока вызовом Thread.yield() и вернуться в цикл опроса сигналов.
Чтобы извлекать JMS-сообщение при освобождении потока, подходит только вариант JMS API с периодическим не блокирующим опросом (JMSConsumer.receive(long timeout)).
Описанный подход обеспечит достаточную степень равномерности распределения исполнений агентов по серверам и в каждом процессе ОС, но имеет недостатки:
- При отсутствии сигналов на запуск агентов, поток находится в холостом цикле опроса, отъедая ресурс CPU.
ASAP и Delayed агентов.
В JMS есть 2 механизма, которые потенциально подходят для реализации агенов в режиме ASAP и Delayed - MDB и JMSConsumer.setMessageLister. Эти механизмы реализуют асинхронное API, т.е. при появлении нового сообщения контейнер вызовет callback-функцию КМА. Метод setMessageLister запрещен JMS-спецификацией для применения в EJB-бинах, но допустим в объектах, управляемых приложениями. MDB конфигурируется только на этапе инициализации приложения, соответственно нельзя его переконфигурировать в рантайм в соответствии с настройками, выполненными в МА. Т.о. будем использовать setMessageLister для получения сообщений с данными для ASAP агентов. Получение служебных сообщений на запуск ASAP-агентов тоже будем делать через setMessageLister. MDB не подходит из-за необходимости фильтрации по имени приложения и хоста, которые известны только при инициализации приложения, а параметры MDB конфигурируется только на этапе разработки (хардкодятся). MDB ещё не удобно тем, что контейнер активирует его ещё до окончания инициализации приложения. В результате могут приходить сообщения в момент, когда приложение ещё не готово их обрабатывать.
КМА, при инициализации, отправляет МА сообщение о своей активации. МА в ответ шлет сигналы для создания JMS-подписок для ASAP-агентов. Если агент в режиме MANY, то сигнал отправляется по всем НЗ, привязанным к пулу серверов, в который входит данный КМА. Для агентов в режиме ASAP-ANY МА должен выполнить перераспределение исполнителей с учетом появления нового КМА (см. ниже).
Сигнал от МА на запуск ASAP или Delayed агента получает MessageListener для служебных сообщений. При получении сигнала, необходимо создать JMS-подписку и задать ей индивидуальный объект MessageListener для обработки сообщений с данными. MessageListener, обрабатывающий получение данных, должен вызвать соответствующий агент, передав ему коллекцию, состоящую из одного JMS-сообщения. Если несколько НЗ указывают на одну и туже очередь, подписка всё равно создается, т.к. доставку сообщения надо подтверждать независимо для каждого агента в контексте отдельной транзакции.
При поступлении JMS-сообщения с данными, JMS-провайдер будет вызывать соответствующий MessageListener в одном из потоков пула, управляемого провайдером. В WildFly размер пула определяется параметром thread-pool-max-size.
<subsystem xmlns="urn:jboss:domain:messaging-activemq:3.0"> <server name="default" thread-pool-max-size="32">