Spring интегрирует Quartz

Java Spring

предисловие

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

Spring интегрирует Quartz

Quartz — зрелая система планирования задач. Spring совместим с Quartz, что удобно для разработки. Давайте посмотрим, как его интегрировать:

1. Файл зависимостей Maven

<dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>4.3.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>4.3.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>4.3.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>4.3.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>2.2.3</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.29</version>
        </dependency>
    </dependencies>

Основными из них являются библиотеки, связанные со Spring, библиотеки кварца и библиотеки драйверов mysql.Примечание: база данных требуется для распределенного планирования, и здесь используется mysql;

2. Настройте задание

Предоставляет два способа настройки задания, а именно: MethodInvokingJobDetailFactoryBean и JobDetailFactoryBean.

2.1MethodInvokingJobDetailFactoryBean

Чтобы вызвать метод определенного компонента, конкретная конфигурация выглядит следующим образом:

<bean id="firstTask" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">  
    <property name="targetObject" ref="firstService" />  
    <property name="targetMethod" value="service" />  
</bea>

2.2JobDetailFactoryBean

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

<bean id="firstTask"
        class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
        <property name="jobClass" value="zh.maven.SQuartz.task.FirstTask" />
        <property name="jobDataMap">
            <map>
                <entry key="firstService" value-ref="firstService" />
            </map>
        </property>
</bean>

Класс задачи, определенный jobClass, наследует QuartzJobBean и реализует метод executeInternal, jobDataMap используется для передачи данных в задачу;

3. Настройте триггеры, используемые при планировании

Также предусмотрены два типа триггеров: SimpleTriggerFactoryBean и CronTriggerFactoryBean. Ориентируясь на CronTriggerFactoryBean, этот тип более гибкий, а именно:

<bean id="firstCronTrigger"
    class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
    <property name="jobDetail" ref="firstTask" />
    <property name="cronExpression" value="0/5 * * ? * *" />
</bean>

jobDetail указывает задание, настроенное на шаге 2, а cronExpression настроен на выполнение задания каждые 5 секунд;

4. Настройте SchedulerFactoryBean планировщика Quartz.

Есть также два способа: память RAMJobStore и способ базы данных.

4.1 Память RAMJobStore

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

<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    <property name="triggers">
        <list>
            <ref bean="firstCronTrigger" />
        </list>
    </property>
</bean>

4.2 Метод базы данных

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

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
        destroy-method="close">
        <property name="driverClass" value="com.mysql.jdbc.Driver" />
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/quartz" />
        <property name="user" value="root" />
        <property name="password" value="root" />
    </bean>
    <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="configLocation" value="classpath:quartz.properties" />
        <property name="triggers">
            <list>
                <ref bean="firstCronTrigger" />
            </list>
        </property>
    </bean>

dataSource используется для настройки источника данных, информации, связанной с таблицей данных, вы можете перейти на официальный сайт, чтобы загрузить пакет gz, файл sql находится по пути: docsdbTables, который предоставляет файл sql основной базы данных, всего 11 таблицы; файлquartz.properties, сконфигурированный с помощью configLocation, находится в файлеquartz.jar в пакете org.quartz, который предоставляет некоторые данные по умолчанию, такие как org.quartz.jobStore.class

org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore

Здесь вам нужно скопироватьquart.properties и внести некоторые изменения.Конкретные изменения заключаются в следующем:

org.quartz.scheduler.instanceId: AUTO
org.quartz.jobStore.class: org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.isClustered: true
org.quartz.jobStore.clusterCheckinInterval: 1000

5. Связанные классы

public class FirstTask extends QuartzJobBean {
 
    private FirstService firstService;
 
    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        firstService.service();
    }
 
    public void setFirstService(FirstService firstService) {
        this.firstService = firstService;
    }
}

FirstTask наследует QuartzJobBean, реализует метод executeInternal и вызывает FirstService;

public class FirstService implements Serializable {
 
    private static final long serialVersionUID = 1L;
 
    public void service() {
        System.out.println(new SimpleDateFormat("YYYYMMdd HH:mm:ss").format(new Date()) + "---start FirstService");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(new SimpleDateFormat("YYYYMMdd HH:mm:ss").format(new Date()) + "---end FirstService");
    }
}

FirstService должен предоставить интерфейс сериализации, потому что он должен храниться в базе данных;

public class App {
    public static void main(String[] args) {
        AbstractApplicationContext context = new ClassPathXmlApplicationContext("quartz.xml");
    }
}

Основной класс используется для загрузки файла конфигурации кварца;

Тестирование распределенного планирования

1. Запустите приложение дважды одновременно и просмотрите журнал:

20180405 14:48:10---start FirstService
20180405 14:48:12---end FirstService
20180405 14:48:15---start FirstService
20180405 14:48:17---end FirstService

Среди них у A1 есть вывод журнала, у A2 нет, когда A1 остановлен, у A2 есть вывод журнала;

2. Добавьте новые задания и создайте новые: SecondTask и SecondService, одновременно добавьте соответствующие файлы конфигурации и запустите приложение для наблюдения за журналом: журнал A1 выглядит следующим образом:

20180405 15:03:15---start FirstService
20180405 15:03:15---start SecondService
20180405 15:03:17---end FirstService
20180405 15:03:17---end SecondService
20180405 15:03:20---start FirstService
20180405 15:03:22---end FirstService
20180405 15:03:25---start FirstService
20180405 15:03:27---end FirstService

Журнал А2 выглядит следующим образом:

20180405 15:03:20---start SecondService
20180405 15:03:22---end SecondService
20180405 15:03:25---start SecondService
20180405 15:03:27---end SecondService

Можно обнаружить, что и A1, и A2 имеют задачи на выполнение, но одна и та же задача будет выполняться только на одном узле в одно и то же время, и назначить ее на другие узлы можно только после окончания выполнения;

3. Если время интервала меньше времени выполнения задачи, например, измените его на sleep(6000) A1 log следующим образом:

20180405 15:14:40---start FirstService
20180405 15:14:45---start FirstService
20180405 15:14:46---end FirstService
20180405 15:14:50---start FirstService
20180405 15:14:50---start SecondService
20180405 15:14:51---end FirstService

Журнал А2 выглядит следующим образом:

20180405 15:14:40---start SecondService
20180405 15:14:45---start SecondService
20180405 15:14:46---end SecondService
20180405 15:14:51---end SecondService

Время интервала составляет 5 секунд, а выполнение задачи занимает 6 секунд.Посмотрев лог, можно обнаружить, что задача не закончилась, а началась новая задача.Эта ситуация может вызвать логические проблемы в приложении, на самом деле , может ли задача поддерживать сериализацию;

4. Аннотация @DisallowConcurrentExecution обеспечивает сериализацию задач
Добавьте аннотации @DisallowConcurrentExecution к FirstTask и SecondTask соответственно, и результаты журнала будут следующими:
Журнал A1 выглядит следующим образом:

20180405 15:32:45---start FirstService
20180405 15:32:51---end FirstService
20180405 15:32:51---start FirstService
20180405 15:32:51---start SecondService
20180405 15:32:57---end FirstService
20180405 15:32:57---end SecondService
20180405 15:32:57---start FirstService
20180405 15:32:57---start SecondService

Журнал А2 выглядит следующим образом:

20180405 15:32:45---start SecondService
20180405 15:32:51---end SecondService

Наблюдая за журналом, можно обнаружить, что задача будет запускать новую задачу только после завершения, что реализует сериализацию задачи;

Суммировать

Эта статья нацелена на интуитивное понимание распределенного планирования Spring+Quartz и решение проблемы путем фактического использования.Конечно, может возникнуть много вопросов, например, как оно запланировано, что произойдет, если база данных зависнет и т. д., и потребности предстоит сделать Более глубокое понимание.