IT-notes     О блоге     Архив записей

Контейнер Logstash

Образ Logstash создан на основе официального образа docker.elastic.co/elasticsearch/logstash:5.6.9.

В существующий образ внесены изменения согласно прилагаемому docker-файлy:

FROM docker.elastic.co/logstash/logstash:5.6.9     
RUN rm -f /usr/share/logstash/pipeline/logstash.conf  
ADD pipeline/logstash.conf /usr/share/logstash/pipeline/logstash.conf  
ADD config/logstash.yml /usr/share/logstash/config/logstash.yml

Где, logstash.conf – файл в котором настроен основной pipeline.

input {  
  file {  
    path => [ "/home/logs/*.log" ]  
    type => "microservices"  
    codec => multiline {  
    pattern => "^(%{TIMESTAMP_ISO8601})"  
    negate => true  
    what => "previous"}
  }
}  
filter {  
  grok {  
    match => [ "message", "(?m)%{TIMESTAMP_ISO8601:timestamp} %{NUMBER:duration} %{DATA:application} \[%{DATA:thread}\] %{LOGLEVEL:logLevel} %{DATA:class} \- %{GREEDYDATA:message}" ] 
    overwrite => ["message"] 
  } 
}  
output { 
  if [type] == "microservices" {  
    elasticsearch {  
      hosts => [ "192.168.0.101:9200" ]  
      index => "microservices-%{+YYYY.MM.dd}"  
    }  
  }  
}

Для возможности парсинга логов был изменен pattern для logback:

%d{yyyy-MM-dd HH:mm:ss.SSS} %-4relative ${spring.application.name} [%thread] %-5level %logger{35} - %msg%n

logstash.yml – это конфигурационный файл.

http.host: "192.168.0.101"  
path.config: /usr/share/logstash/pipeline  
path.data: /usr/share/logstash/data  
path.logs: /usr/share/logstash/logs  
xpack.monitoring.elasticsearch.url: "http://192.168.0.101:9200"

Каталоги /usr/share/logstash/data, /usr/share/logstash/logs монтируются из внешних каталогов хоста при старте контейнера.

Проблемы: Для того чтобы контейнер запустился необходимо:

  • дать права на монтируемые каталоги

    chmod 0777 /u01/docker/logstash/data chmod 0777 /u01/docker/logstash/logs

Старт командой:

docker run --rm -d --net=host -v /u01/docker/logstash/data:/usr/share/logstash/data -v /u01/docker/logstash/logs:/usr/share/logstash/logs -v /u01/docker/logs:/home/logs --name=parus-logstash parus-logstash:5.6.9

Индексы в Kibana

Для мониторинга соответствующего индекса в Kibana необходимо его настроить. Заходим в пункт меню Management => Create Index Pattern: kibana-index

В полe «Index pattern» указываем шаблон имени для отслеживаемого индекса. Если заданный индекс существует, то заполнится поле «Time Filter field name». kibana-index

Для заданного имени будут автоматически сгенерированы поля. kibana-index

Для мониторинга информации по заданному индексу необходимо перейти в пункт меню Discover, и в выпадающем списке выбрать необходимый индекс kibana-index

kibana-index

kibana-index

Каждая запись представлена в виде таблицы с настроенными в индексе полями: kibana-index

Kibana-dashboard. Простая таблица

На вкладке Discover настраиваем необходимый фильтр, перечень полей для отображения. kibana-dashboard-table

Сохраняем результаты отбора. Переходим в пункт меню «Save», в поле «Save Search» указываем название для осуществленного поиска, указываем флаг «Save as a new search», и нажимаем на кнопку «Save» . kibana-dashboard-table

Переходим на вкладку Dashboard, нажимаем на кнопку «+» (Create new dashboard). kibana-dashboard-table

В открывшемся окне нажимаем на кнопку Add. kibana-dashboard-table

В открывшемся окне переходим на вкладку «Saved Search», и выбираем сохраненный ранее наш поиск. kibana-dashboard-table

На выбранный поиск добавиться панель, у которой можно отрегулировать размер. На вкладке Options сменить тему оформления. kibana-dashboard-table

kibana-dashboard-table

Затем переходим в пункт меню Save, указываем значение поля Title и Description, флажок Store time with dashboard – если хотим, чтобы показатели в дашборде обновлялись вместе с его загрузкой. Нажимаем на кнопку Save. kibana-dashboard-table

Создается Dashboard, который затем доступен в списке. kibana-dashboard-table

kibana-dashboard-table

Jdbc input

Для чтения данных из БД в Logstash необходимо настроить pipeline с jdbc-input.

input {    
  jdbc {    
    jdbc_driver_library => "/usr/share/logstash/ojdbc6-11.2.0.4.jar"    
    jdbc_driver_class => "Java::oracle.jdbc.driver.OracleDriver"    
    jdbc_connection_string => "jdbc:oracle:thin:@192.168.0.101:1521:test02"   
    jdbc_user => "user"    
    jdbc_password => "password"    
    schedule => "*/5 * * * *"   
    statement_filepath => "/usr/share/logstash/queue-atm.sql"    
    type => "monitoring-queue-atm"    
    jdbc_paging_enabled => "true"    
    jdbc_page_size => "6"    
  }  
}    
output {    
  if [type] == "monitoring-queue-atm" {    
    elasticsearch {  
      hosts => [ "192.168.0.101:9200" ]    
      index => "monitoring-queue-atm-%{+YYYY.MM.dd}"    
    }    
  }    
}

Где,

  • jdbc_driver_library - путь к библиотеке ojdbc. “/usr/share/logstash/ojdbc6-11.2.0.4.jar”.
  • jdbc_driver_class - класс-драйвер. Пример, “Java::oracle.jdbc.driver.OracleDriver”.
  • jdbc_connection_string – строка подключения. Пример, “jdbc:oracle:thin:@192.168.0.101:test02”.
  • jdbc_user – имя пользователя.
  • jdbc_password – пароль пользователя.
  • schedule – расписание, по которому получается запрос.
  • statement_filepath - путь к файлу, в котором указан запрос.
  • type – для фильтра получаемой информации.
  • jdbc_paging_enabled - маркер, что запрос, возвращает более одной записи.
  • jdbc_page_size - количество записей, возвращаемое запросом.

Получаемые по запросу данные передаются в Elasticsearch, и сохраняются там под индексом с шаблоном “monitoring-queue-atm-%{+YYYY.MM.dd}”.

Далее с этим индексом работаем в графическом интерфейсе Kibana. Более подробная информация здесь.

FROM docker.elastic.co/logstash/logstash:5.6.9
RUN rm -f /usr/share/logstash/pipeline/logstash.conf
ADD config/logstash.yml /usr/share/logstash/config/logstash.yml
ADD ojdbc6-11.2.0.4.jar /usr/share/logstash/

Старт docker-контейнера:

docker run –rm -d –net=host -v /u01/docker/logstash/pipeline:/usr/share/logstash/pipeline -v /u01/docker/logstash/queue-atm.sql:/usr/share/logstash/queue-atm.sql -v /u01/docker/logstash/data:/usr/share/logstash/data -v /u01/docker/logstash/logs:/usr/share/logstash/logs -v /u01/docker/logs:/home/logs –name=logstash logstash:5.6.9

Монтируемые каталоги:

  • файл с sql-запросом:
    -v /u01/docker/logstash/queue-atm.sql:/usr/share/logstash/queue-atm.sql
  • Каталог с файлами, в которых настроены pipeline:
    -v /u01/docker/logstash/pipeline:/usr/share/logstash/pipeline
    По умолчанию Logstash в каталоге /usr/share/logstash/pipeline все файлы воспринимает, как pipeline.

Удаление индексов

Чтобы избежать переполнения дискового пространства elasticsearch необходимо периодически очищать индексы. Требования:

  • храним индексы за 1 день
  • удаляем индексы fluentd
  • удаляем все служебные индексы, такие как: .monitoring-logstash-, .monitoring-kibana-, .monitoring-es-*
  • исключаем удаление индекса .kibana, т.к. в нем хранятся основные настройки kibana.

Файл actions.yml для elasticsearch-curator:

---
actions:  
  1:  
    action: delete_indices  
    options:  
      ignore_empty_list: true  
      continue_if_exception: false  
      disable_action: false  
    filters:  
      - filtertype: age  
        source: creation_date  
        direction: older  
        unit: days  
        unit_count: 1  
      - filtertype: pattern  
        kind: regex  
        value: .kibana  
        exclude: True

Docker-контейнер Elasticsearch-curator

Образ Elasticsearch-curator создан на основе образа из dockerfile:

FROM alpine:latest
RUN apk --update add python py-pip && \  
pip install elasticsearch-curator && \  
pip install requests-aws4auth && \  
rm -rf /var/cache/apk/*

В существующий образ внесены изменения согласно прилагаемому docker-файлy:

FROM elasticsearch-curator:latest  
ADD ./entrypoint.sh /entrypoint.sh  
WORKDIR /usr/share/curator  
RUN chmod +x /entrypoint.sh    
ENV CRON 00 5 * * *  
ENV CONFIG_FILE /usr/share/curator/config/curator.yml  
ENV COMMAND /usr/share/curator/config/actions.yml    
ENTRYPOINT ["/entrypoint.sh"]

Где:

  • entrypoint.sh – это скрипт запуска cron-а.
  • curator.yml – конфигурационный файл.
  • actions.yml – файл с описанными действиями. Файлы curator.yml, actions.yml указываются монтированием каталога при старте контейнера.

Стартуем командой:

docker run --rm -d -v /u01/docker/elasticsearch-curator-by-cron/config:/usr/share/curator/config --net=host --name=elasticsearch-curator-by-cron elasticsearch-curator-by-cron:0.0.1 - [elasticsearch-curator](https://github.com/OlgaFedorova/dockers/tree/master/elasticsearch-curator) - [elasticsearch-curator-by-cron](https://github.com/OlgaFedorova/dockers/tree/master/elasticsearch-curator-by-cron)

Контейнер Elasticsearch

Образ Elasticsearch создан на основе официального образа docker.elastic.co/elasticsearch/elasticsearch:5.6.9. В существующий образ внесены изменения согласно прилагаемому docker-файлy:

FROM docker.elastic.co/elasticsearch/elasticsearch:5.6.9  
USER root  
COPY ./limits.conf /etc/security/limits.conf  
RUN chown -R elasticsearch:elasticsearch /usr/share/elasticsearch/data  
RUN chown -R elasticsearch:elasticsearch /usr/share/elasticsearch/logs
USER elasticsearch

Где,

  • elasticsearch.yml – это конфигурационный файл.
  • /usr/share/elasticsearch/data – путь куда пишутся индексы
  • /usr/share/elasticsearch/logs – путь куда пишутся логи
  • limits.conf - cистемный файл /etc/security/limits.conf, в котором указаны настройки для пользователя elasticsearch: максимальное количество открытых файловых дескрипторов (nofile) и максимальное количество запущенных тредов (nproc).

elasticsearch - nofile 65536
elasticsearch - nproc 2048

Данные каталоги монтируются из внешних каталогов хоста при старте контейнера:

Проблемы:

Для того чтобы контейнер запустился необходимо :

  • дать права на монтируемые каталоги

    chmod 0777 /u01/docker/elasticsearch/node1/logs
    chmod 0777 /u01/docker/elasticsearch/node1/data
    chmod 0777 /u01/docker/elasticsearch/node1/config/
    chmod 0777 /u01/docker/elasticsearch/node1/config/elasticsearch.yml
    chmod 0777 /u01/docker/elasticsearch/node2/logs
    chmod 0777 /u01/docker/elasticsearch/node2/data
    chmod 0777 /u01/docker/elasticsearch/node2/config/
    chmod 0777 /u01/docker/elasticsearch/node2/config/elasticsearch.yml

  • Установить значение max virtual memory areas vm.max_map_count:

sysctl -w vm.max_map_count=262144

Кластер Elasticsearch развернут в двух нодах. Пример конфигурационного файла elasticsearch.yml для node1. Пример конфигурационного файла elasticsearch.yml для node2.

Старт node1:

docker run --rm -d --net=host --env "ES_JAVA_OPTS=""-Xms2g -Xmx2g""" --ulimit memlock=-1:-1 -v /u01/docker/elasticsearch/node1/logs:/usr/share/elasticsearch/logs -v /u01/docker/elasticsearch/node1/data:/usr/share/elasticsearch/data -v /u01/docker/elasticsearch/node1/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml --name=elasticsearch1 elasticsearch:5.6.9

Старт node2:

docker run --rm -d --net=host --env "ES_JAVA_OPTS=""-Xms2g -Xmx2g""" --ulimit memlock=-1:-1 -v /u01/docker/elasticsearch/node2/logs:/usr/share/elasticsearch/logs -v /u01/docker/elasticsearch/node2/data:/usr/share/elasticsearch/data -v /u01/docker/elasticsearch/node2/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml --name=elasticsearch2 elasticsearch:5.6.9

Изменить Docker Root Dir

Иногда требуется изменить Docker Root Dir, например когда недостаточно места на диске.

Необходимо в файл /etc/sysconfig/docker в OPTIONS добавить “ -g /u01/.docker-tmp”.

Пример:

OPTIONS='--selinux-enabled --log-driver=journald --signature-verification=false -g /u01/.docker-tmp'

Далее перезапускаем docker:

systemctl daemon-reload   
systemctl restart docker

Проверяем изменения:

docker info

Аналитика таймеров

Почему Quartz, чем он лучше остальных

Среди существующих планировщиков задач (IBM WebSphere Scheduler, EJB Timer Service, Quartz Scheduler, Spring Task Scheduler) предпочтение отдается Quartz Scheduler, так как он обладает рядом полезных свойств:

  • Хранение состояния задачи в БД (Персистентность)
  • Распределение нагрузки между серверами (Кластеризация)
  • Возможность гибкого задания расписания (cron, интервал)
  • Возможность просмотра статуса выполнения задач
  • Возможность запуска задач в контексте транзакции
  • Простота и легкость в использовании

Ссылки:

  • java-quartz-scheduler-ejb-3-0-timer-service-and-java-timer-task-when-to-use-each
  • ejb-timer-performance
  • job-scheduling-ejb-3-1-timers-or-quartz
  • programmatic-timers-and-automatic-timers-difference
  • ejb3-vs-spring-framework.jsp
  • j-quartz
  • java-ee-schedulers.html

    Архитектура Quartz Framework

    Quartz Framework обладает простой для понимания архитектурой и оперирует тремя основными понятиями (классами):

  • Шедулер (планировщик) - управляет таймерами запуска задач. Связывает классы “Job” и “Trigger” вместе и выполняет их.
  • Джоб - конкретная задача, запускаемая по таймеру.
  • Триггер - условие выполнения задачи - задает временные рамки запуска задач и их выполнения. В Quattz два основных тригера:
  • SimpleTrigger – позволяет устанавливать время страта, время окончания и интервал повторения.
  • CronTrigger – позволяет использовать Unix cron-выражения для указания даты и времени запуска вашей джобы.

Ссылки:

  • Работа с шедулером в Java (Spring)
  • quartz-scheduler-tutorial
  • quartz-scheduler-tutorial
  • quartz-2-scheduler-tutorial
  • java-quartz-configuration-example
  • quartz-2.x-configuration

    Персистентность

    Персистентность таймеров – важное необходимое свойство, включающее в себя следующее:

  • Сервер приложений сохраняет события планирования, когда приложение не работает, чтобы не потерять их.
  • Планировщик может быть настроен на сохранение пропущенных событий и их выполнение, когда приложение снова будет работать.
  • Сервер приложений использует внутреннюю базу данных для хранения пропущенных событий.

Примечание: сервер приложений будет генерировать все пропущенные события при рестарте приложения.

Очередь событий настраивается по частоте и задержке.

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

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

Примеры поведения персистентного планировщика:

  • Если триггер должен сработать только несколько раз. То после рестарта приложения Scheduler должен знать об этом и не активировать его.
  • Если у нас есть триггер, который должен срабатывать каждый час, но спустя, например, 59 минут наше приложение упало. То когда его перезапустили, этот триггер должен сработать через минуту, а не через час.

Что выбрать: персистентный или неперсистентный планировщик?

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

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

  • Первое средство, экземпляр класса RAMJobStore, является настройкой по умолчанию. Это самое простое в использовании хранилище заданий, дающее к тому же наибольшую производительность, поскольку все данные хранятся в памяти. Главным недостатком этого метода является недостаточная сохраняемость данных. Поскольку все данные сохраняются в RAM, вся информация будет утеряна после “падения” приложения или системы.
  • Чтобы исправить эти недостатки, Quartz предлагает JDBCJobStore. Как следует из названия, этот способ сохранения заданий помещает данные в базу данных через JDBC. Ценой более надёжного хранения данных является более низкая производительность, а также большая сложность.

Дистрибутив quartz содержит скрипты для генерации таблиц для соответствующих баз в docs/dbTables.

Ссылки:

  • spring-quartz-with-a-database
  • spring-quartz-jdbc-example
  • configuring-quartz-with-jdbcjobstore-in

    Кластеризация

    Проблема: В кластере запущено более чем один экземпляр нашего приложения (на каждый узел кластера) и все экземпляры имеют свои копии шедулеров. Но мы нуждаемся в одном шедулере, запущенном среди всех узлов кластера, в противном случае мы получаем множество копий одного и того же события. Решение проблемы: Каждый сервер приложений имеет свой вариант для решения проблемы “множества экземпляров шедулеров”.

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

Кластеризация в quartz позволяет решить две основные задачи:

  1. Отказоустойчивость
  2. Балансировка нагрузки

Кластеризация в quartz в настоящее время работает только с JDBC-Jobstore (JobStoreTX или JobStoreCMT), и основывается на том, что каждый узел в кластере работает с одной и той же базой.

Балансировка нагрузки происходит автоматически, на каждом узле кластера запускаются джобы так быстро как это возможно. Когда наступает время запуска триггера, первый узел, который овладевает им (размещает блокировку на нем), становится узлом, который запустит его. Только один узел запускает джобу для каждого старта. Т.е. если джоба имеет повторяющийся триггер, который говорит запускаться каждые 10 секунд, то только один узел стартует джобу в 12:00:00, затем в 12:00:10 и т.д. И необязательно, что это будет один и тот же узел каждый раз. Какой именно узел запускает джобу – определяется случайно. Механизм балансировки нагрузки является почти случайным для занятых шедулеров (у которых много триггеров), но поддерживает один и тот же узел для незанятых (несколько триггеров) планировщиков.

Отказоустойчивость случается, когда один узел падает в процессе выполнения одного или нескольких джоб. Когда узел падает, другой узел обнаруживает это состояние и определяет джобы в базе данных, которые были в процессе выполнения внутри упавшего узла. Каждая джоба, помеченная для восстановления (с значением свойства “requests recovery” в JobDetail), будет перевыполнена на оставшихся узлах. Джоба, непомеченная для восстановления, будет освобождена от выполнения в следующий раз при старте связанного триггера.

Кластеризация лучше всего подходит для масштабируемых долго-выполняемых и ресурсо-затратных джоб (распределяя загрузку работы среди множества узлов). Если вам необходимо горизонтально масштабировать поддержку тысячи часто запускаемых (1с) джоб, рассмотрите разделение джоб, используя множество различных планировщиков. Планировщик вынужден использовать кластерную блокировку. Паттерн который ухудшает производительность, когда вы добавляете больше узлов (свыше трех узлов – зависит от возможностей вашей базы данных).

Кластеризация включается установкой свойства «org.quartz.jobStore.isClustered» в значение true. Каждый экземпляр в кластере должен использовать одну и ту же копию файла quartz.properties. Исключением из этого является использование файлов, которые являются идентичными, со следующими допустимыми исключениями: различный размер пула потоков и различное значение для свойства org.quartz.scheduler.instanceId. Каждый узел в кластере должен иметь уникальный instanceId, который настраивается установкой значения «AUTO» в его свойство.

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

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

Пример properties-файла для кластеризованного планировщика:

# Configure Main Scheduler Properties
org.quartz.scheduler.instanceName = MyClusteredScheduler
org.quartz.scheduler.instanceId = AUTO
# Configure ThreadPool
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 25
org.quartz.threadPool.threadPriority = 5
# Configure JobStore
org.quartz.jobStore.misfireThreshold = 60000
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.oracle.OracleDelegate
org.quartz.jobStore.useProperties = false
org.quartz.jobStore.dataSource = myDS
org.quartz.jobStore.tablePrefix = QRTZ_
org.quartz.jobStore.isClustered = true
org.quartz.jobStore.clusterCheckinInterval = 20000
# Configure Datasources
org.quartz.dataSource.myDS.driver = oracle.jdbc.driver.OracleDriver
org.quartz.dataSource.myDS.URL = jdbc:oracle:thin:@polarbear:1521:dev
org.quartz.dataSource.myDS.user = quartz
org.quartz.dataSource.myDS.password = quartz
org.quartz.dataSource.myDS.maxConnections = 5
org.quartz.dataSource.myDS.validationQuery=select 0 from dual

Ссылки

Можно выделить два типа изменений:

  1. Изменения не вовлекают джобы или удаление триггеров. Например, изменение крон-выражения для триггера, изменение параметров джобы, или java-класса для джобы.
  2. Изменения, вовлекающие джобу или удаление триггера. Переименование джобы или триггера попадает в эту категорию.

Spring-овый ‘SchedulerFactoryBean’ поддерживает изменения первого типа: его свойство overwriteExistingJobs определяет, следует ли обновлять определения заданий и триггеров, хранящихся в базе данных, при запуске веб-приложения. По умолчанию используется значение false.

Изменения второго типа: Если данное свойство установить в true, то старые джобы и триггеры не будут удалены, пока они не упоминаются в новой версии вашего приложения.

Возможны два способа решения проблемы:

  1. Запустите сценарий базы данных перед установкой своего веб-приложения, которое уничтожит данные из таблиц (либо просто заданий и триггеров, которые будут удалены, либо все данные). Скрипт должен быть применен, когда приложения остановлены.
  2. Реализуйте подкласс Spring SchedulerFactoryBean, который даст возможность очистить таблицы Quartz от Java при запуске веб-приложения

В Quartz у объекта шедулера можно вызывать следующие методы:

  • pauseJob(String name, String Group) — остановить выполнение задачи шедулера в указанной группе джобов. Остановка происходит путём остановки соответствующего триггера (см. pauseTrigger)
  • resumeJob(String name, String Group) — возобновить выполнение задачи шедулера в указанной группе джобов. Восстановление происходит путём запуска соответствующего триггера (см. resumeTrigger)
  • pauseTrigger(String name, String Group) — останавливает триггер в соответствующей группе
  • resumeTrigger(String name, String Group) — возобновляет работу триггера в соответсвующей группе
  • pauseAll — останавливает все задачи шедулера (pauseJobs(String group) — только у конкретной группы)
  • resumeAll — возобновляет запуск всех задач шедулера (см. также resumeJobs)

    Интеграция Quartz + Spring

    Spring обеспечивает поддержку классов Quartz и предоставляет следующие классы:

  • QuartzJobBean – простая реализация интерфейса org.quartz.Job, в которой необходимо реализовать метод executeInternal, в котором определяется действие для выполнения.
  • JobDetailFactoryBean – фабрика бинов для создания org.quartz.JobDetail. Вы можете сконфигурировать “job class” и “job data” используя bean-style.
  • SimpleTriggerFactoryBean – фабрика бинов для создания org.quartz.SimpleTrigger
  • CronTriggerFactoryBean – фабрика бинов для создания org.quartz.CronTrigger
  • SchedulerFactoryBean – фабрика бинов для создания org.quartz.Scheduler

Ссылки:

Exceptionjavax.naming.ConfigurationException: A JNDI operation on a “java:” name cannot be completed because the server runtime is not able to associate the operation’s thread with any J2EE application component. This condition can occur when the JNDI client using the “java:” name is not executed on the thread of a server application request. Make sure that a J2EE application does not execute JNDI operations on “java:” names within static code blocks or in threads created by that J2EE application. Such code does not necessarily run on the thread of a server application request and therefore is not supported by JNDI operations on “java:” names. [Root exception is javax.naming.NameNotFoundException: Name comp/env/[…] not found in context “java:”.]

Такие пакеты как quartz и jdk таймеры стартуют неуправляемые потоки. Неуправляемые потоки не имеют доступа к информации контекста JEE.

Для того, чтобы сделать Quartz полностью поддерживаемым в WebSphere необходимо быть уверенным, что неуправляемые потоки не стартуются. Тогда ваши джобы получают доступ к JNDI ресурсам и могут участвовать в JTA транзакциях (которые требуют поиска jndi java:comp/UserTransaction).

Это возможно благодаря указанию пользовательского thread pool (пула потоков), который делегирует CommonJ управление по созданию потоков. Однако, Quartz стартует несколько внутренних потоков, из которых стартуются актуальные джобы. Если внутренние quartz-потоки являются неуправляемыми, JEE контекст теряется и не может быть распространён на потоки джобов, даже если они являются управляемыми.

Две проблемы QTZ-113 «Quartz стартует неуправляемые потоки и J2EE контекст не распространяется на потоки джобов» и QTZ-194 «Quartz стартует неуправляемые потоки в JobStoreSupport» были исправлены в версии Quartz 2.1, который представляет новый интерфейс org.quartz.spi.ThreadExecutor.

Для того, чтобы внутренние quartz-потки были управляемыми необходимо установить два свойства в вашей конфигурации quartz:

org.quartz.threadExecutor.class=org.quartz.commonj.WorkManagerThreadExecutor
org.quartz.threadExecutor.workManagerName=wm/default

Spring предлагает поддержку Quartz используя SchedulerFactoryBean:

<bean id="quartzScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
  <property name="taskExecutor" ref="taskExecutor" />
  <property name="quartzProperties"> 
  <props>  
    <prop key="org.quartz.jobStore.class">org.quartz.simpl.RAMJobStore</prop>
    <prop key="org.quartz.threadExecutor.class">org.quartz.commonj.WorkManagerThreadExecutor</prop>  
    <prop key="org.quartz.threadExecutor.workManagerName">wm/default</prop> 
  </props>  
  </property>  
</bean>

Вы можете установить значение task executor в org.springframework.scheduling.commonj.WorkManagerTaskExecutor. Этот класс делегирует полномочия CommonJ Work Manager. Который доступен внутри WebSphere. Так потоки, которые будут созданы для ваших quartz-джобов, оборачиваются в commonj Work объекты, и будут управляться сервером приложений:

<bean id="taskExecutor"  class="org.springframework.scheduling.commonj.WorkManagerTaskExecutor">
  <property name="resourceRef" value="true" />  
  <property name="workManagerName" value="wm/default" />
</bean>

WorkManager в WorkManagerThreadPool инициализируется до старта планировщика из потока Java EE (таких как инициализация сервлета). WorkManagerThreadPool может затем создать поток-демон, который будет обрабатывать все планируемые задания по созданию и планированию новых Work-объектов. В этом случае планировщик (в своем собственном потоке) передает задачи потоку управления (Work-демону).

Т.е. всего два места где необходимо сконфигурировать quartz с commonj. Первое spring-конфиг, taskExecutor в SchedulerFactoryBean, второе определяется в property-файле в свойстве org.quartz.threadExecutor.class.

Настройка в spring позваляет quartz использовать пул потоков websphere для работы собственного пула потоков, настройка в property-файле используется для потока шедулера.

Ссылки