Tomáš Holý
26.1.2012

Synchronizace scheduleru v clusteru



Na našem projektu Starthead jsme potřebovali implementovat automatické provádění úkolu určeného ke zpracovávání dat v databázi. Ve frameworku Spring, který je pro vývoj použit, je tato úloha jednoduše řešitelná například pomocí TaskScheduler. Zajímavější situace nastává, pokud má aplikace bežet v clusterovém řešení. To znamená, že je potřeba řešit synchronizaci, aby nedocházelo k vícenásobnému spouštění jobů ve stejný čas. Snažili jsme se najít co nejjednodužší řešení, které by splňovalo danou podmínku a tím se v našem případě ukázalo využití Quartz scheduleru s patřičnou clusterovou konfigurací a synchronizováním pomocí ukládání do databáze.

Zde je uveden jednoduchý příklad vytvoření takového úkolu a jeho konfigurace ve Spring frameworku.

Implementace jednoduchého jobu

package com.aspectworks;

import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.StatefulJob;
import org.springframework.scheduling.quartz.QuartzJobBean;

public class TestJob extends QuartzJobBean implements StatefulJob {

  private TestService testService;

  protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
    //implementace úkolu 
  } 

  public void setTestService(TestService testService) {
    this.testService = testService;
  }
} 


Definice triggeru úkolu pomocí Cron definice

<bean id="testTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
  <property name="jobDetail">
    <bean class="org.springframework.scheduling.quartz.JobDetailBean">
      <property name="jobClass" value="com.aspectworks.TestJob" />
      <property name="name" value="TestJob" />
    </bean>
  </property>
  <property name="jobDataAsMap">
    <map>
      <entry key="testAttribute" value="value " />
    </map>
  </property>
  <property name="cronExpression" value="1 * * * * ?" />
</bean>

 
Konfigurace scheduleru pro clusterové řešení

<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
  <property name="waitForJobsToCompleteOnShutdown" value="true" />
  <property name="dataSource" ref="dataSource" />
  <property name="overwriteExistingJobs" value="true" />
  <property name="autoStartup" value="true" />
  <property name="triggers">
    <list>
      <ref bean="testTrigger" />
    </list>
  </property>
  <property name="quartzProperties">
    <props>
      <prop key="org.quartz.scheduler.instanceName">TestScheduler</prop>
      <prop key="org.quartz.scheduler.instanceId">AUTO</prop>
      <prop key="org.quartz.jobStore.misfireThreshold">60000</prop>
      <prop key="org.quartz.jobStore.class">org.quartz.impl.jdbcjobstore.JobStoreTX</prop>
      <prop key="org.quartz.jobStore.driverDelegateClass">org.quartz.impl.jdbcjobstore.StdJDBCDelegate</prop>
      <prop key="org.quartz.jobStore.tablePrefix">QRTZ_</prop>
      <prop key="org.quartz.jobStore.isClustered">true</prop>
      <prop key="org.quartz.threadPool.class">org.quartz.simpl.SimpleThreadPool</prop>
      <prop key="org.quartz.threadPool.threadCount">25</prop>
      <prop key="org.quartz.threadPool.threadPriority">5</prop>
    </props>
  </property>
  <property name="schedulerContextAsMap">
    <map>
      <entry key="testService" value-ref="testService" />
    </map>
  </property>
</bean>

 
V dané konfiguraci je důležité nastavení atributů obsahujících org.quartz.jobStore a org.quartz.impl.jdbcjobstore. Ty říkají, že úkoly poběží v clusteru, a že se má provést jejich ukládání do tabulek s prefixem QRTZ_. Toto nastavení nám samo zajistí, že po inicializaci se úkol uloží do databáze s příslušnými parametry a ve chvíli vykonávání úkolu smí pouze jeden stroj v clusteru provést jeho spuštění. Právo na vykonání úkolu dostane první z běžících strojů v clusteru, který o to požádá.

Vysvětlení nejdůležitějších atributů:

org.quartz.jobStore.isClustered – definování běhu úkolu v clusteru
org.quartz.scheduler.instanceId – nastavení typu určování id instance (defaultně nastaveno na NON_CLUSTERED)
org.quartz.jobStore.class – definování typu ukládání synchronizace úkolů (v našem případě definuje ukládání do databáze)
org.quartz.jobStore.driverDelegateClass – definování driveru pro ukládání dat do databáze (v našem případě MySQL). Sql soubor pro založení tabulek můžete najít v adresáři doc v konkrétní verzi quartz implementace.
org.quartz.jobStore.tablePrefix – nastavení prefixu Qartz tabulek pro ukládání synchronizace úkolů Pokud se použije ukládání úkolů do databáze, ukládá se společně s JobDetailem i mapa jeho atributů jobDataAsMap. To znamená, že musí být možné je serializovat a také, že by to neměly být žádné objekty s aplikační logikou. Pokud je potřeba v úkolu využít nějakou service spravovanou Springem, je lepší ji předat do úkolu přes schedulerContextAsMap definovanou v SchedulerFactoryBeanu. Tento způsob uloží objekty do contextu Scheduleru a do úkolů je předá až při jejich inicializování, tedy bez nutnosti ukládání do databáze s ostatnímy atributy. Více o Quartz schedulerech je možné najít na stránkách http://quartz-scheduler.org/overview

Vaše emailová adresa nebude zveřejněna

Komentáře

Děkujeme za váš komentář
Další
  • Iba kratke doplnenie, ze toto riesenie je velmi nachylne na presne zosynchronizovany cas na vsetkych nodoch, co nie je problem zabezpecit, len treba na to mysliet.