2012-08-29

Использование Spring task scheduler

Не так давно в одном из проектов стояла задача регулярного опроса базы данных и обработки записей полученных оттуда по мере их возникновения. Эту задачу мы решали с помощью Spring Task Scheduling. Начнем с конфигурации контекста:

Spring предоставляет удобный namespace task, который содержит все необходимое для создания Executor/Scheduler бинов. В моей конфигурации максимальное кол-во потоков, проводящих опрос базы данных - пять. <task:annotation-driven> элемент позволяет использовать аннотировнные компоненты для запуска задач, а также указывает executor/scheduler которые будут использоваться по умолчанию для запуска аннотированных методов.


Как видно из примера выше, для создания задачи достаточно аннотировать необходимый метод аннотацией @Scheduled, которой необходимо указать один из трех параметров: 
1. fixedDelay - период между завершением предыдущего запуска и началом следущего запуска задачи в миллисекундах
2. fixedRate - период между каждым запуском задачи в миллисекундах (считается от старта предыдущей задачи)
3. cron - указывает cron подобное выражение, в котором можно более точно указать периоды запуска задачи

Хочу обратить внимание, что fixedDelay следует использовать в тех случаях, когда время выполнения задачи может превышать период между выполнениями задач. Для остальных случаев вполне подойдет fixedRate.

После старта контекста Spring автоматически настроит необходимые Executor бины и будет выполнять метод pollDatabase раз в секунду (ну или чуть реже, учитывая время на выполнение самого метода).н

Правда, во время эксплуатации этого кода, возникла одна неприятная проблема, которая в общем-то не влияла на работоспособность системы, но оставляла мусор в логах. Дело в том, что выключая контейнер (Tomcat в нашем случае), Spring закрывает контекст и разрушает все бины, находящиеся в нем. При этом выполняемые запланированные задачи иногда разрушались после того как уже были разрушены сервисные бины; задачи пытались использовать разрушенные сервисные бины, вызывая различные странные исключения.

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

Мы включили в нашу компоненту две новых зависимости (databasePollExecutordatabasePollScheduler), которые дали нам доступ к executor/scheduler, обьявленных в XML контексте. Также мы добавили в нее реализацию интерфейса, которая уничтожает executor/scheduler и связанные с ними задачи. Метод onApplicationEvent будет вызван Spring-ом до начала разрушения остальных бинов и контекста в целом, а это именно то что нам нужно.

7 comments:

  1. Не подскажите, есть ли в спринге возможность, не используя других средств, создать скедьюлер наподобие кварцовского clustered скедьюлера? Чтобы одинковое приложение, задеплоенное на разные ноды не выполняло одну и туже работу по несколько раз.

    ReplyDelete
  2. Насколько я понимаю Вам нужен SchedulerFactoryBean, он может работать с Quartz кластером. Судя по тому что пишут сам Quartz поддерживает для кластера только хранение jobs в некоем JDBC-хранилище из которого мы потом собственно их и забираем нашей фактори. В инете много примеров. Может я не понял вопрос?

    ReplyDelete
  3. Спасибо за ответ, Алексей.
    Есть некое приложение, задеплоенное на несколько инстансов apache tomcat, находящихся за load balancer. Приложение умеет выполнять некие джобы по скедьюлеру. Допустим они тянут откуда-то некие данные и кладут их в хранилище. Задача состояла в том, чтобы при выполнении задачи на одном инстансе, запретить ее выполнение на других. Quartz умеет решать данную проблему, синхронизируя выполнение задач через DB.
    Мы сначала сделали все на спринге, как и у вас, но когда наступило время переезжать с одного инстанса на неколько нам пришлось все переделать на quartz.
    Так вот, вопрос был в том, умеет ли такое spring без quartz. Но, насколько я понял, перед вами такая задача пока что не стояла.
    В любом случае, спасибо.

    ReplyDelete
  4. Не умеет, Вы все правильно сделали. Spring предлагает готовые решения для работы с Quartz Jobs, и если нужна кластеризация, то используйте SchedulerFactoryBean и встроенные средства самого Quartz

    ReplyDelete
  5. Вы правы не сталкивался. По поводу Spring - не встречал описание такого его поведения. Можно написать разработчикам :)

    ReplyDelete
  6. И все такие не понял вопроса сначала :)

    ReplyDelete
  7. Женя и Алексей, спасибо за ответы.
    Надеюсь это будет следующим этапом развития спринга. Так как, если откинуть кластеризацию, простой скедьюлер на спринге создать намного проще, чем с помощью кварца. Хотелось бы такой простоты во всем :)

    ReplyDelete