предисловие
Для обеспечения высокой доступности и высокого параллелизма приложений, как правило, разворачивают несколько узлов; для запланированных задач, если каждый узел выполняет свои собственные запланированные задачи, с одной стороны потребляются системные ресурсы, а с другой стороны, некоторые задачи выполняются несколько раз, что может вызвать проблемы с логикой приложения, поэтому необходима распределенная система планирования для координации каждого узла для выполнения задач по времени.
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 и решение проблемы путем фактического использования.Конечно, может возникнуть много вопросов, например, как оно запланировано, что произойдет, если база данных зависнет и т. д., и потребности предстоит сделать Более глубокое понимание.