Сравнение технического выбора задач по времени Spring Boot

Java задняя часть
Сравнение технического выбора задач по времени Spring Boot

Всем привет, я Ю, маленький толстяк, который каждый день мастурбирует в метро~

Сегодня давайте взглянем на технический выбор задач синхронизации в приложениях Spring Boot~

В этой статье приведен полный пример кода, см.GitHub.com/Y u — День V/SP…изlab-28содержание.

Оригинальность не так проста, дай мне немногоStarЭй, пошли утку!

😜 Идем к концу, до праздника остался всего один день!

1 Обзор

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

Среди них мне больше всего нравится "пучок зарплатных ведомостей в начале каждого месяца", а вам?

В JDK есть два встроенных класса, которые могут реализовать функцию временных задач:

  • java.util.Timer: может быть созданjava.util.TimerTaskРасписание задач в том же потокеСерийныйВыполнять и влиять друг на друга. То есть для нескольких задач TimerTask в одном и том же Timer, если одна задача TimerTask выполняется, другие задачи TimerTask могут только ждать в очереди, даже если они достигают времени выполнения. Поскольку таймер последовательный, он существует одновременно.яма, поэтому позже JDK запустил ScheduledExecutorService , а Timer практически больше не используется.
  • java.util.concurrent.ScheduledExecutorService: Добавлено в JDK 1.5, на основе класса задач синхронизации, разработанного пулом потоков, каждая задача планирования будет назначена пулу потоков.параллелизмВыполнять, не влияя друг на друга. Таким образом, ScheduledExecutorService решает проблему сериализации таймера.

В повседневной разработке мы редко используем Timer или ScheduledExecutorvice для реализации задач, требующих выполнения задач по времени. Причин несколько:

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

Поэтому при нормальных обстоятельствах мы выберем профессиональныхПО промежуточного слоя для планирования задач.

о"Задача"называется", также называется "Операция"Да. В английском есть Task и Job. Суть одна, и в этой статье будут использоваться оба.

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

В весенней системе есть два встроенных решения для задач времени:

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

  • секунда,Spring BootВерсия 2.0, интегрированнаяQuartzСтруктура планирования работы, которая обеспечиваетМощныйвыполнение задач на время.

    Примечание. Spring Framework имеет встроенную интеграцию с Quartz. Версия Spring Boot 1.X не обеспечивает автоматическую настройку Quartz, а версия 2.X обеспечивает поддержку.

В экосистеме Java есть много отличных промежуточных программ для планирования задач с открытым исходным кодом:

В настоящее время Elastic-Job и XXL-JOB в основном используются в Китае. Из того, что я узнал, будет больше команд, использующих XXL-JOB, в основном потому, что с ним легче начать работу, а функции эксплуатации и обслуживания более полны.

В этой статье мы начнем отдельно в порядке Spring Task, Quartz и XXL-JOB. В конце статьи я кратко расскажу о принципе реализации задач с распределенным временем.

2. Задача быстрого начала

Пример кода соответствует складу:lab-28-task-demo.

Учитывая фактическую сцену, мы редко используем задачу весны, поэтому этот раздел будет писать больше лаконичков. Если Pangyou Spring заинтересована задача, вы можете прочитать себя«Документация Spring Framework — выполнение задач и планирование»Documentation, который содержит подробную документацию, связанную с Spring Task.

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

2.1 Знакомство с зависимостями

существуетpom.xmlВ файл вводятся соответствующие зависимости.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>lab-28-task-demo</artifactId>

    <dependencies>
        <!-- 实现对 Spring MVC 的自动化配置 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

</project>

Поскольку Spring Task является модулем Spring Framework, мы вводимspring-boot-starter-webПосле зависимости нет необходимости вводить ее специально.

В то же время, учитывая, что мы хотим, чтобы проект запускался без автоматического завершения процесса JVM, мы ввелиspring-boot-starter-webполагаться.

2.2 ScheduleConfiguration

существуетcn.iocoder.springboot.lab28.task.configВ пути к пакету создайтеScheduleConfigurationКласс, который настраивает Spring Task. код показывает, как показано ниже:

// ScheduleConfiguration.java

@Configuration
@EnableScheduling
public class ScheduleConfiguration {
}
  • В классе добавить@EnableSchedulingАннотация для запуска функции планирования запланированных задач Spring Task.

2.3 DemoJob

существуетcn.iocoder.springboot.lab28.task.jobВ пути к пакету создайтеDemoJobclass, пример класса задач на время. код показывает, как показано ниже:

// DemoJob.java

@Component
public class DemoJob {

    private Logger logger = LoggerFactory.getLogger(getClass());

    private final AtomicInteger counts = new AtomicInteger();

    @Scheduled(fixedRate = 2000)
    public void execute() {
        logger.info("[execute][定时第 ({}) 次执行]", counts.incrementAndGet());
    }

}
  • В классе добавить@ComponentАннотация для создания объекта DemoJob Bean.
  • Создайте#execute()Метод позволяет журналу печати. Между тем, в методе, добавляя@ScheduledАннотация, установленная для выполнения метода каждые 2 секунды.

Несмотря на то,@ScheduledАннотации можно добавлять к нескольким методам класса, но с точки зрения личных привычек это по-прежнему класс Job и запланированная задача. 😈

2.4 Application

СоздайтеApplication.javaкласс, комплектация@SpringBootApplication注解即可。 код показывает, как показано ниже:

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

Класс приложения запущен, запустите пример проекта. Выходной журнал упрощен следующим образом:

# 初始化一个 ThreadPoolTaskScheduler 任务调度器
2019-11-30 18:02:58.415  INFO 83730 --- [           main] o.s.s.c.ThreadPoolTaskScheduler          : Initializing ExecutorService 'taskScheduler'

# 每 2 秒,执行一次 DemoJob 的任务
2019-11-30 18:02:58.449  INFO 83730 --- [ pikaqiu-demo-1] c.i.springboot.lab28.task.job.DemoJob    : [execute][定时第 (1) 次执行]
2019-11-30 18:03:00.438  INFO 83730 --- [ pikaqiu-demo-1] c.i.springboot.lab28.task.job.DemoJob    : [execute][定时第 (2) 次执行]
2019-11-30 18:03:02.442  INFO 83730 --- [ pikaqiu-demo-2] c.i.springboot.lab28.task.job.DemoJob    : [execute][定时第 (3) 次执行]
  • В журнале мы видим, что планировщик задач ThreadPoolTaskScheduler инициализирован. После этого каждые 2 секунды выполняется задание DemoJob.

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

под"2.5 по расписанию"и«2.6 Профили приложений»Два подраздела являются дополнительными знаниями, и их рекомендуется просмотреть.

2.5 @Scheduled

@ScheduledАннотация, задать план выполнения запланированного задания.

Обычно используетсяСвойства следующие:

  • cronСвойства: выражение Spring Cron. Например,"0 0 12 * * ?"Указывает на то, что он выполнен один раз в день в полдень,"11 11 11 11 11 ?"Указывает, что он будет выполнен один раз в 11:11:11 11 ноября (хахаха). Дополнительные примеры и пояснения см."Выражения Spring Cron"статья. Обратите внимание, что для вызовавремя завершенияПора начинать хронометраж.
  • fixedDelayАтрибут: Фиксированный интервал выполнения, единица измерения: миллисекунды. Обратите внимание, что для вызовавремя завершенияПора начинать хронометраж.
  • fixedRateАтрибут: Фиксированный интервал выполнения, единица измерения: миллисекунды. Обратите внимание, что для вызованачальное времяПора начинать хронометраж.
  • Эти три свойства чем-то похожи, вы можете посмотреть«Разница между fixedRate, fixedDelay и cron запланированных задач @Scheduled», должны различать разницу.

Необычно используетсяСвойства следующие:

  • initialDelayАтрибут: Инициализированная задержка выполнения задачи синхронизации, единица измерения: миллисекунды.
  • zoneСвойство: часовой пояс, в который разрешается выражение Spring Cron. По умолчанию используется локальный часовой пояс сервера.
  • initialDelayStringАтрибуты:initialDelayв строковой форме.
  • fixedDelayStringАтрибуты:fixedDelayв строковой форме.
  • fixedRateStringАтрибуты:fixedRateв строковой форме.

2.6 Файл конфигурации приложения

существуетapplication.yml, добавьте конфигурацию задачи синхронизации Spring Task следующим образом:

spring:
  task:
    # Spring Task 调度任务的配置,对应 TaskSchedulingProperties 配置类
    scheduling:
      thread-name-prefix: pikaqiu-demo- # 线程池的线程名的前缀。默认为 scheduling- ,建议根据自己应用来设置
      pool:
        size: 10 # 线程池大小。默认为 1 ,根据自己应用来设置
      shutdown:
        await-termination: true # 应用关闭时,是否等待定时任务执行完成。默认为 false ,建议设置为 true
        await-termination-period: 60 # 等待任务完成的最大时长,单位为秒。默认为 0 ,根据自己应用来设置
  • существуетspring.task.schedulingЭлемент конфигурации, конфигурация задачи планирования Spring Task, соответствующаяTaskSchedulingPropertiesКласс конфигурации.
  • Spring Boot TaskSchedulingAutoConfigurationКласс автоматической настройки, реализовать автоматическую настройку Spring Task, создатьThreadPoolTaskSchedulerПланировщик задач на основе пула потоков. По сути, ThreadPoolTaskScheduler основан на инкапсуляции ScheduledExecutorService для улучшения функции планирования времени.

Уведомление,spring.task.scheduling.shutdownЭлемент конфигурации предназначен для корректного завершения запланированных задач Spring Task. Давайте представим, что если запланированная задача находится в процессе выполнения, если приложение начинает закрываться, а Spring Bean, который должен использовать запланированная задача, уничтожается, например, пул соединений с базой данных, то запланированная задача все еще выполняется. в это время, когда необходимо получить доступ к базе данных. , может вызвать ошибку.

  • Итак, настроивawait-termination = true, При реализации приложение закрывается, ожидая момента завершения выполнения задачи. Таким образом, когда вы закроете приложение, Spring будет отдавать приоритет ожиданиям после того, как ThreadPoolTaskScheduler выполнит задачу, а затем начнет уничтожать Spring Bean.
  • В то же время, учитывая, что бесконечное ожидание выполнения всех запланированных задач у нас невозможно, можно настроитьawait-termination-period = 60, максимальное время ожидания завершения задачи в секундах. Конкретное время ожидания может быть установлено в соответствии с потребностями вашего приложения.

3. Автономный кварц быстрого старта

Пример кода соответствует складу:lab-28-task-quartz-memory.

Когда компания впервые приступила к стажировке, она использовала Quartz в качестве промежуточного программного обеспечения для планирования задач. Учитывая, что мы хотим добиться высокой доступности запланированных задач, нам необходимо развернуть несколько процессов JVM. Что более удобно, так это то, что Quartz поставляется с кластерным решением. Он работает, сохраняя информацию о заданиях в реляционной базе данных и используя данные реляционной базы данных.блокировка строкиДобиться реализации конкурсной работы, тем самым обеспечив несколько процессов, одну и ту же задачу в одно и то же время нельзя повторять.

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

FROM www.oschina.net/p/quartz

Quartz — это среда планирования заданий с открытым исходным кодом, полностью написанная на Java и предназначенная для использования в приложениях J2SE и J2EE. Он предлагает большую гибкость без ущерба для простоты. Вы можете использовать его для создания простых или сложных расписаний для выполнения задания.

Он имеет много функций, таких как: поддержка базы данных,кластер, плагины, предварительные сборки заданий EJB, JavaMail и многое другое, поддержка cron-подобных выражений и многое другое.

В архитектуре Quartz важны три компонента:

  • Планировщик: планировщик
  • Триггер: триггер
  • Работа: задача

Толстые друзья, которые не знают, вы можете посмотреть прямо«Введение в кварц»статья. Здесь мы не будем повторять это.

FROM Medium.com/@cha mit and ko ...

Кварцевая общая архитектура

Кварц делится на автономный режим и кластерный режим.

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

😈 А теперь начинаем экскурсию~

3.1 Знакомство с зависимостями

существуетpom.xmlВ файл вводятся соответствующие зависимости.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>lab-28-task-quartz-memory</artifactId>

    <dependencies>
        <!-- 实现对 Spring MVC 的自动化配置 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- 实现对 Quartz 的自动化配置 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-quartz</artifactId>
        </dependency>
    </dependencies>

</project>

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

3.2 Пример работы

существуетcn.iocoder.springboot.lab28.task.config.jobВ пути к пакету давайте создадим пример Job.

СоздайтеDemoJob01класс, пример задания на время 01 класс. код показывает, как показано ниже:

// DemoJob01.java

public class DemoJob01 extends QuartzJobBean {

    private Logger logger = LoggerFactory.getLogger(getClass());

    private final AtomicInteger counts = new AtomicInteger();

    @Autowired
    private DemoService demoService;

    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        logger.info("[executeInternal][定时第 ({}) 次执行, demoService 为 ({})]", counts.incrementAndGet(),
                demoService);
    }

}
  • наследоватьQuartzJobBeanабстрактный класс, реализация#executeInternal(JobExecutionContext context)метод, который выполняет логику пользовательской задачи по времени.

  • QuartzJobBean реализуетorg.quartz.JobИнтерфейс, предоставляющий кварцу в зависимость от работы в зависимости от рабочих имущественных свойств бобов рабочих мест, когда вы создаете работу для выполнения журнала Timing. Например, Demojob01 необходимо@AutowiredинъецированныйdemoServiceАтрибуты. Основной код выглядит следующим образом:

    // QuartzJobBean.java
    
    public final void execute(JobExecutionContext context) throws JobExecutionException {
        try {
            // 将当前对象,包装成 BeanWrapper 对象
            BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
            // 设置属性到 bw 中
            MutablePropertyValues pvs = new MutablePropertyValues();
            pvs.addPropertyValues(context.getScheduler().getContext());
            pvs.addPropertyValues(context.getMergedJobDataMap());
            bw.setPropertyValues(pvs, true);
    	} catch (SchedulerException ex) {
    		throw new JobExecutionException(ex);
    	}
    
        // 执行提供给子类实现的抽象方法
        this.executeInternal(context);
    }
    
    protected abstract void executeInternal(JobExecutionContext context) throws JobExecutionException;
    
    • Глядя на это таким образом, разве это не намного яснее? Не бойтесь исходного кода промежуточного программного обеспечения.Если вам интересно, какой класс или метод, просто нажмите, чтобы увидеть его. Все равно денег нет.
  • countsсвойства, счетчики. для нас позже мы покажем,Каждый раз, когда DemoJob01 будет создавать новый объект Job Quartz для выполнения задачи. Это очень важно, и вы должны быть очень осторожны.

СоздайтеDemoJob02класс, пример задания на время 02 класс. код показывает, как показано ниже:

// DemoJob02.java

public class DemoJob02 extends QuartzJobBean {

    private Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        logger.info("[executeInternal][我开始的执行了]");
    }

}
  • Это относительно просто, чтобы продемонстрировать случай позже.

3.3 ScheduleConfiguration

существуетcn.iocoder.springboot.lab28.task.configВ пути к пакету создайтеScheduleConfigurationкласс, который настраивает два приведенных выше примера Jobs. код показывает, как показано ниже:

// ScheduleConfiguration.java

@Configuration
public class ScheduleConfiguration {

    public static class DemoJob01Configuration {

        @Bean
        public JobDetail demoJob01() {
            return JobBuilder.newJob(DemoJob01.class)
                    .withIdentity("demoJob01") // 名字为 demoJob01
                    .storeDurably() // 没有 Trigger 关联的时候任务是否被保留。因为创建 JobDetail 时,还没 Trigger 指向它,所以需要设置为 true ,表示保留。
                    .build();
        }

        @Bean
        public Trigger demoJob01Trigger() {
            // 简单的调度计划的构造器
            SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
                    .withIntervalInSeconds(5) // 频率。
                    .repeatForever(); // 次数。
            // Trigger 构造器
            return TriggerBuilder.newTrigger()
                    .forJob(demoJob01()) // 对应 Job 为 demoJob01
                    .withIdentity("demoJob01Trigger") // 名字为 demoJob01Trigger
                    .withSchedule(scheduleBuilder) // 对应 Schedule 为 scheduleBuilder
                    .build();
        }

    }

    public static class DemoJob02Configuration {

        @Bean
        public JobDetail demoJob02() {
            return JobBuilder.newJob(DemoJob02.class)
                    .withIdentity("demoJob02") // 名字为 demoJob02
                    .storeDurably() // 没有 Trigger 关联的时候任务是否被保留。因为创建 JobDetail 时,还没 Trigger 指向它,所以需要设置为 true ,表示保留。
                    .build();
        }

        @Bean
        public Trigger demoJob02Trigger() {
            // 基于 Quartz Cron 表达式的调度计划的构造器
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("0/10 * * * * ? *");
            // Trigger 构造器
            return TriggerBuilder.newTrigger()
                    .forJob(demoJob02()) // 对应 Job 为 demoJob02
                    .withIdentity("demoJob02Trigger") // 名字为 demoJob02Trigger
                    .withSchedule(scheduleBuilder) // 对应 Schedule 为 scheduleBuilder
                    .build();
        }

    }

}
  • Два класса конфигурации, DemoJob01Configuration и DemoJob02Configuration, создаются внутри для настройки двух Quartz Jobs DemoJob01 и DemoJob02 соответственно.
  • ========== DemoJob01Configuration ==========
  • #demoJob01()для создания объекта JobDetail Bean объекта DemoJob01.
  • #demoJob01Trigger()Метод, создайте объект Trigger Bean объекта DEMOJOB01. Среди них мы используемSimpleScheduleBuilderПростой конструктор расписания, который создает бесконечно повторяющееся расписание, которое выполняется каждые 5 секунд.
  • ========== DemoJob2Configuration ==========
  • #demoJob2()для создания объекта JobDetail Bean объекта DemoJob02.
  • #demoJob02Trigger()для создания объекта Trigger Bean объекта DemoJob02. Среди них мы используемCronScheduleBuilderКонструктор для планирования расписаний на основе выражений Quartz Cron, создающий каждыйпервоеРасписание, которое выполняется один раз каждые 10 секунд. Вот, порекомендуйтеИнструменты онлайн-генерации Quartz / Cron / Crontab Expression, который нам удобен для генерации выражений Quartz Cron и вычисления последних 5 раз выполнения.

😈 Поскольку JobDetail и Trigger обычно отображаются парами, их принято настраивать как класс конфигурации Configuration.

3.4 Application

СоздайтеApplication.javaкласс, комплектация@SpringBootApplicationПросто аннотируйте. код показывает, как показано ниже:

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

Запустите класс Application, чтобы запустить пример проекта. Выходной журнал сжат следующим образом:

# 创建了 Quartz QuartzScheduler 并启动
2019-11-30 23:40:05.123  INFO 92812 --- [           main] org.quartz.impl.StdSchedulerFactory      : Using default implementation for ThreadExecutor
2019-11-30 23:40:05.130  INFO 92812 --- [           main] org.quartz.core.SchedulerSignalerImpl    : Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl
2019-11-30 23:40:05.130  INFO 92812 --- [           main] org.quartz.core.QuartzScheduler          : Quartz Scheduler v.2.3.2 created.
2019-11-30 23:40:05.131  INFO 92812 --- [           main] org.quartz.simpl.RAMJobStore             : RAMJobStore initialized.
2019-11-30 23:40:05.132  INFO 92812 --- [           main] org.quartz.core.QuartzScheduler          : Scheduler meta-data: Quartz Scheduler (v2.3.2) 'quartzScheduler' with instanceId 'NON_CLUSTERED'
  Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
  NOT STARTED.
  Currently in standby mode.
  Number of jobs executed: 0
  Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 10 threads.
  Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.

2019-11-30 23:40:05.132  INFO 92812 --- [           main] org.quartz.impl.StdSchedulerFactory      : Quartz scheduler 'quartzScheduler' initialized from an externally provided properties instance.
2019-11-30 23:40:05.132  INFO 92812 --- [           main] org.quartz.impl.StdSchedulerFactory      : Quartz scheduler version: 2.3.2
2019-11-30 23:40:05.132  INFO 92812 --- [           main] org.quartz.core.QuartzScheduler          : JobFactory set to: org.springframework.scheduling.quartz.SpringBeanJobFactory@203dd56b
2019-11-30 23:40:05.158  INFO 92812 --- [           main] o.s.s.quartz.SchedulerFactoryBean        : Starting Quartz Scheduler now
2019-11-30 23:40:05.158  INFO 92812 --- [           main] org.quartz.core.QuartzScheduler          : Scheduler quartzScheduler_$_NON_CLUSTERED started.

# DemoJob01
2019-11-30 23:40:05.164  INFO 92812 --- [eduler_Worker-1] c.i.springboot.lab28.task.job.DemoJob01  : [executeInternal][定时第 (1) 次执行, demoService 为 (cn.iocoder.springboot.lab28.task.service.DemoService@23d75d74)]
2019-11-30 23:40:09.866  INFO 92812 --- [eduler_Worker-2] c.i.springboot.lab28.task.job.DemoJob01  : [executeInternal][定时第 (1) 次执行, demoService 为 (cn.iocoder.springboot.lab28.task.service.DemoService@23d75d74)]
2019-11-30 23:40:14.865  INFO 92812 --- [eduler_Worker-4] c.i.springboot.lab28.task.job.DemoJob01  : [executeInternal][定时第 (1) 次执行, demoService 为 (cn.iocoder.springboot.lab28.task.service.DemoService@23d75d74)]

# DemoJob02
2019-11-30 23:40:10.004  INFO 92812 --- [eduler_Worker-3] c.i.springboot.lab28.task.job.DemoJob02  : [executeInternal][我开始的执行了]
2019-11-30 23:40:20.001  INFO 92812 --- [eduler_Worker-6] c.i.springboot.lab28.task.job.DemoJob02  : [executeInternal][我开始的执行了]
2019-11-30 23:40:30.002  INFO 92812 --- [eduler_Worker-9] c.i.springboot.lab28.task.job.DemoJob02  : [executeInternal][我开始的执行了]
  • Когда проект начинается, кварцевый кварцШедудер создан и начался.
  • Учитывая удобство чтения логов, мы разделяем здесь логи DemoJob01 и DemoJob02.
  • Для DemoJob01 выполняйте каждые 5 секунд или около того. В то же время мы можем видеть, что,demoServiceуспешно введен, в то время какcountsКаждый раз равен 1 , что указывает на то, что каждый раз создается новое задание DemoJob01.
  • Для DemoJob02 каждыйпервоеВыполнять раз в 10 секунд.

под«3.5 Файл конфигурации приложения»Два подраздела являются дополнительными знаниями, и их рекомендуется просмотреть.

3.5 Файл конфигурации приложения

существуетapplication.ymlДобавлена ​​следующая конфигурация Quartz:

spring:
  # Quartz 的配置,对应 QuartzProperties 配置类
  quartz:
    job-store-type: memory # Job 存储器类型。默认为 memory 表示内存,可选 jdbc 使用数据库。
    auto-startup: true # Quartz 是否自动启动
    startup-delay: 0 # 延迟 N 秒启动
    wait-for-jobs-to-complete-on-shutdown: true # 应用关闭时,是否等待定时任务执行完成。默认为 false ,建议设置为 true
    overwrite-existing-jobs: false # 是否覆盖已有 Job 的配置
    properties: # 添加 Quartz Scheduler 附加属性,更多可以看 http://www.quartz-scheduler.org/documentation/2.4.0-SNAPSHOT/configuration.html 文档
      org:
        quartz:
          threadPool:
            threadCount: 25 # 线程池大小。默认为 10 。
            threadPriority: 5 # 线程优先级
            class: org.quartz.simpl.SimpleThreadPool # 线程池类型
#    jdbc: # 这里暂时不说明,使用 JDBC 的 JobStore 的时候,才需要配置
  • существуетspring.quartzЭлемент конфигурации, конфигурация Quartz, соответствующаяQuartzPropertiesКласс конфигурации.
  • Spring Boot QuartzAutoConfigurationКласс автоматической настройки, реализовать автоматическую настройку Quartz, создать QuartzScheduler(планировщик) Фасоль.

Уведомление,spring.quartz.wait-for-jobs-to-complete-on-shutdownЭлемент конфигурации предназначен для достижения изящного отключения кварца, рекомендуется открыть его. Об этом и наше«2.6 Файл конфигурации приложения»Упомянутое соответствует.

4. Начало работы с кварцевым кластером снова

Пример кода соответствует складу:lab-28-task-quartz-memory.

В реальных сценариях мы должны рассмотреть время запланированных задач.Высокая доступность, поэтому обязательно используйте схему кластеризации Quartz. Поэтому в этом разделе мы используем Quartz'sJDBCобъем памятиJobStoreTX, и использует MySQL в качестве базы данных.

Ниже приведено сравнение двух модулей памяти Quartz:

FROM blog.CSDN.net/Эван Кака/AR…

тип преимущество недостаток
RAMJobStore Нет внешней базы данных, легко настроить и быстро запустить Поскольку информация о планировщике хранится в памяти, выделенной для JVM, вся информация о планировании будет потеряна, когда приложение перестанет работать. Кроме того, поскольку он хранится в памяти JVM, количество заданий и триггеров, которые можно сохранить, будет ограничено.
Хранилище заданий JDBC Поддержка кластеризации, так как вся информация о задачах будет сохранена в базе данных, вы сможете управлять вещами, а если сервер приложений будет выключен или перезапущен, информация о задаче не будет потеряна, а задачи, которые не удалось выполнить из-за выключения или перезапуска сервера можно восстановить. Скорость работы зависит от скорости подключения к БД

Най Най: На самом деле программа имеет то преимущество, что оба эти метода могут быть реализованы, мы«666. Пасхальное яйцо»в центре.

Кроме того, примеры, представленные в этом подразделе и«3. Автономный кварц для быстрого старта»基本一致。 😈 А теперь начинаем экскурсию~

4.1 вводит зависимость

существуетpom.xmlВ файл вводятся соответствующие зависимости.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.10.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>lab-28-task-quartz-jdbc</artifactId>

    <dependencies>
        <!-- 实现对数据库连接池的自动化配置 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency> <!-- 本示例,我们使用 MySQL -->
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.48</version>
        </dependency>

        <!-- 实现对 Spring MVC 的自动化配置 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- 实现对 Quartz 的自动化配置 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-quartz</artifactId>
        </dependency>

        <!-- 方便等会写单元测试 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>
  • и«3.1 Знакомство с зависимостями»В основном то же самое, только дополнительно введеноspring-boot-starter-testЗависимость и так будет писать два метода модульного тестирования.

4.2 Пример работы

существуетcn.iocoder.springboot.lab28.task.config.jobВ пути к пакету создайтеDemoJob01иDemoJob02своего рода. код показывает, как показано ниже:

// DemoJob01.java

@DisallowConcurrentExecution
public class DemoJob01 extends QuartzJobBean {

    private Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private DemoService demoService;

    @Override
    protected void executeInternal(JobExecutionContext context) {
        logger.info("[executeInternal][我开始的执行了, demoService 为 ({})]", demoService);
    }

}

// DemoJob02.java

@DisallowConcurrentExecution
public class DemoJob02 extends QuartzJobBean {

    private Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    protected void executeInternal(JobExecutionContext context) {
        logger.info("[executeInternal][我开始的执行了]");
    }

}

УведомлениеНе к кварцуJobДля измерения гарантируется, что в нескольких процессах JVM выполняется только один узел,Вместо этого возьмите JobDetail в качестве измерения. Хотя в большинстве случаев мы гарантируем, что Job и JobDetailкорреспонденция один на один. 😈 所以,搞不清楚这个概念的胖友,最好搞清楚这个概念。实在有点懵逼,保证一个 Job 和 JobDetail 是корреспонденция один на одинВерно.

И JobDetailУникально идентифицируетдаJobKey,использоватьname + groupдва свойства. В общем, нам нужно только установитьnameВот и кварц будет дефолтным.group = DEFAULT.

Однако здесь можно добавить еще кое-что.внимание, в кварце,то же имя планировщикаузлы для формирования кластера Quartz. Далее мы можем пройтиspring.quartz.scheduler-nameЭлемент конфигурации, задайте имя Планировщика.

【важный】Зачем это говорить? Потому что мы хотим улучшить приведенное выше утверждение: добавив в класс реализации Job@DisallowConcurrentExecutionАннотация, реализованная втакой жеВ кластере Quartz Scheduler,такой жеJobDetail JobKey гарантирует, что среди нескольких процессов JVM будет выполняться только один узел.

4.3 Файл конфигурации приложения

существуетapplication.yml, добавьте конфигурацию Quartz следующим образом:

spring:
  datasource:
    user:
      url: jdbc:mysql://127.0.0.1:3306/lab-28-quartz-jdbc-user?useSSL=false&useUnicode=true&characterEncoding=UTF-8
      driver-class-name: com.mysql.jdbc.Driver
      username: root
      password:
    quartz:
      url: jdbc:mysql://127.0.0.1:3306/lab-28-quartz-jdbc-quartz?useSSL=false&useUnicode=true&characterEncoding=UTF-8
      driver-class-name: com.mysql.jdbc.Driver
      username: root
      password:

  # Quartz 的配置,对应 QuartzProperties 配置类
  quartz:
    scheduler-name: clusteredScheduler # Scheduler 名字。默认为 schedulerName
    job-store-type: jdbc # Job 存储器类型。默认为 memory 表示内存,可选 jdbc 使用数据库。
    auto-startup: true # Quartz 是否自动启动
    startup-delay: 0 # 延迟 N 秒启动
    wait-for-jobs-to-complete-on-shutdown: true # 应用关闭时,是否等待定时任务执行完成。默认为 false ,建议设置为 true
    overwrite-existing-jobs: false # 是否覆盖已有 Job 的配置
    properties: # 添加 Quartz Scheduler 附加属性,更多可以看 http://www.quartz-scheduler.org/documentation/2.4.0-SNAPSHOT/configuration.html 文档
      org:
        quartz:
          # JobStore 相关配置
          jobStore:
            # 数据源名称
            dataSource: quartzDataSource # 使用的数据源
            class: org.quartz.impl.jdbcjobstore.JobStoreTX # JobStore 实现类
            driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
            tablePrefix: QRTZ_ # Quartz 表前缀
            isClustered: true # 是集群模式
            clusterCheckinInterval: 1000
            useProperties: false
          # 线程池相关配置
          threadPool:
            threadCount: 25 # 线程池大小。默认为 10 。
            threadPriority: 5 # 线程优先级
            class: org.quartz.simpl.SimpleThreadPool # 线程池类型
    jdbc: # 使用 JDBC 的 JobStore 的时候,JDBC 的配置
      initialize-schema: never # 是否自动使用 SQL 初始化 Quartz 表结构。这里设置成 never ,我们手动创建表结构。
  • Есть много элементов конфигурации, мы в основном сравниваем«3.5 Файл конфигурации приложения»приди и посмотри.

  • существуетspring.datasourceВ элементе конфигурации конфигурация используется для создания нескольких источников данных.

    • userнастроить, подключитьlab-28-quartz-jdbc-userбиблиотека. Цель состоит в том, чтобы смоделировать бизнес-базу данных, используемую в наших общих проектах.
    • quartzнастроить, подключитьlab-28-quartz-jdbc-quartzбиблиотека. Предполагается, что Quartz будет использовать отдельную базу данных. 😈 Если у нас есть несколько проектов, которым необходимо использовать базу данных Quartz,можно использовать унифицированный, но обратите внимание на конфигурациюspring.quartz.scheduler-nameУстановите разные имена планировщика для формирования разных кластеров Quartz.
  • существуетspring.quartzПод элементами конфигурации добавлены некоторые дополнительные элементы конфигурации, давайте рассмотрим их один за другим.

    • scheduler-nameКонфигурация, имя планировщика. Мы объясняли это много раз выше, если вы все еще не понимаете, пожалуйста, застрелитесь.
    • job-store-typeнастраивать, устанавливать с помощью"jdbc"память работы.
    • properties.org.quartz.jobStoreКонфигурация, добавлена ​​конфигурация, связанная с магазином заданий. Дело в том, что поdataSourceЭлемент конфигурации, установите имя использования на"quartzDataSource"DataSource для источника данных. В 😈«4.4 Конфигурация источника данных», мы будем использоватьspring.datasource.quartzконфигурация для создания источника данных.
    • jdbcЭлемент конфигурации, хотя и называется так, в основном используется для установки инициализированной структуры таблицы Quartz с помощью SQL. Здесь мы устанавливаемinitialize-schema = never, мы вручную создаем структуру таблицы.

Кхм, элементов конфигурации действительно много. Если вы временно не понимаете толстых друзей, вы можете просто сказатьspring.datasourceИсточники данных, могут быть изменены на ваши собственные.

4.4 Инициализация структуры таблицы Quartz

существуетQuartz Downloadадрес, загрузите релизный пакет соответствующей версии. После декомпрессии мы можемsrc/org/quartz/impl/jdbcjobstore/каталог, чтобы увидеть сценарии инициализации для структуры таблицы Quartz различных баз данных. Здесь, поскольку мы используем MySQL, мы используемtables_mysql_innodb.sqlсценарий.

Выполните сценарий в базе данных, чтобы завершить инициализацию структуры кварцевой таблицы. Как показано ниже:Quartz 表结构

Описание структуры каждой таблицы Quartz см.«Quartz Framework (2) — Подробное объяснение полей таблицы базы данных JobStore»статья. 😈 На самом деле не видно, ха-ха-ха-ха.

Мы обнаружим, что каждая таблица имеетSCHED_NAMEПоле, имя кварцевого планировщика. Таким образом, каждый кластер Quartz разделяется на уровне данных.

4.5 DataSourceConfiguration

существуетcn.iocoder.springboot.lab28.task.configВ пути к пакету создайтеDataSourceConfigurationКласс, настроить источники данных. код показывает, как показано ниже:

// DataSourceConfiguration.java

@Configuration
public class DataSourceConfiguration {

    /**
     * 创建 user 数据源的配置对象
     */
    @Primary
    @Bean(name = "userDataSourceProperties")
    @ConfigurationProperties(prefix = "spring.datasource.user") // 读取 spring.datasource.user 配置到 DataSourceProperties 对象
    public DataSourceProperties userDataSourceProperties() {
        return new DataSourceProperties();
    }

    /**
     * 创建 user 数据源
     */
    @Primary
    @Bean(name = "userDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.user.hikari") // 读取 spring.datasource.user 配置到 HikariDataSource 对象
    public DataSource userDataSource() {
        // 获得 DataSourceProperties 对象
        DataSourceProperties properties =  this.userDataSourceProperties();
        // 创建 HikariDataSource 对象
        return createHikariDataSource(properties);
    }

    /**
     * 创建 quartz 数据源的配置对象
     */
    @Bean(name = "quartzDataSourceProperties")
    @ConfigurationProperties(prefix = "spring.datasource.quartz") // 读取 spring.datasource.quartz 配置到 DataSourceProperties 对象
    public DataSourceProperties quartzDataSourceProperties() {
        return new DataSourceProperties();
    }

    /**
     * 创建 quartz 数据源
     */
    @Bean(name = "quartzDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.quartz.hikari")
    @QuartzDataSource
    public DataSource quartzDataSource() {
        // 获得 DataSourceProperties 对象
        DataSourceProperties properties =  this.quartzDataSourceProperties();
        // 创建 HikariDataSource 对象
        return createHikariDataSource(properties);
    }

    private static HikariDataSource createHikariDataSource(DataSourceProperties properties) {
        // 创建 HikariDataSource 对象
        HikariDataSource dataSource = properties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
        // 设置线程池名
        if (StringUtils.hasText(properties.getName())) {
            dataSource.setPoolName(properties.getName());
        }
        return dataSource;
    }

}
  • на основеspring.datasource.userэлемент конфигурации, созданный с именем"userDataSource"Компонент DataSource. и, вдобавок к этому, мы добавили@PrimayАннотация, что этохозяинисточник данных.
  • на основеspring.datasource.quartzэлемент конфигурации, созданный с именем"quartzDataSource"Компонент DataSource. и, вдобавок к этому, мы добавили@QuartzDataSourceАннотация, что этоQuartzисточник данных. 😈 Обратите внимание, вы должны настроить его, я застрял здесь на долгое время! ! ! !

4.6 Настройка запланированного задания

После выполнения вышеуказанных работ нам необходимо настроить запланированные задачи Quartz. В настоящее время есть два пути:

4.6.1 Автоматические настройки компонента

существуетcn.iocoder.springboot.lab28.task.configВ пути к пакету создайтеScheduleConfigurationкласс, который настраивает два приведенных выше примера Jobs. код показывает, как показано ниже:

// ScheduleConfiguration.java

@Configuration
public class ScheduleConfiguration {

    public static class DemoJob01Configuration {

        @Bean
        public JobDetail demoJob01() {
            return JobBuilder.newJob(DemoJob01.class)
                    .withIdentity("demoJob01") // 名字为 demoJob01
                    .storeDurably() // 没有 Trigger 关联的时候任务是否被保留。因为创建 JobDetail 时,还没 Trigger 指向它,所以需要设置为 true ,表示保留。
                    .build();
        }

        @Bean
        public Trigger demoJob01Trigger() {
            // 简单的调度计划的构造器
            SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
                    .withIntervalInSeconds(5) // 频率。
                    .repeatForever(); // 次数。
            // Trigger 构造器
            return TriggerBuilder.newTrigger()
                    .forJob(demoJob01()) // 对应 Job 为 demoJob01
                    .withIdentity("demoJob01Trigger") // 名字为 demoJob01Trigger
                    .withSchedule(scheduleBuilder) // 对应 Schedule 为 scheduleBuilder
                    .build();
        }

    }

    public static class DemoJob02Configuration {

        @Bean
        public JobDetail demoJob02() {
            return JobBuilder.newJob(DemoJob02.class)
                    .withIdentity("demoJob02") // 名字为 demoJob02
                    .storeDurably() // 没有 Trigger 关联的时候任务是否被保留。因为创建 JobDetail 时,还没 Trigger 指向它,所以需要设置为 true ,表示保留。
                    .build();
        }

        @Bean
        public Trigger demoJob02Trigger() {
            // 简单的调度计划的构造器
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("0/10 * * * * ? *");
            // Trigger 构造器
            return TriggerBuilder.newTrigger()
                    .forJob(demoJob02()) // 对应 Job 为 demoJob02
                    .withIdentity("demoJob02Trigger") // 名字为 demoJob02Trigger
                    .withSchedule(scheduleBuilder) // 对应 Schedule 为 scheduleBuilder
                    .build();
        }

    }

}

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

  • Scheduler#addJob(JobDetail jobDetail, boolean replace)метод для сохранения JobDetail в базе данных.
  • Scheduler#scheduleJob(Trigger trigger)метод для сохранения триггера в базе данных.

4.6.2 Ручная настройка планировщика

В целом,рекомендоватьЗадается вручную с помощью Планировщика.

СоздайтеQuartzSchedulerTestclass для создания конфигураций задач Quartz, которые добавляют DemoJob01 и DemoJob02 соответственно. код показывает, как показано ниже:

// QuartzSchedulerTest.java

@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class QuartzSchedulerTest {

    @Autowired
    private Scheduler scheduler;

    @Test
    public void addDemoJob01Config() throws SchedulerException {
        // 创建 JobDetail
        JobDetail jobDetail = JobBuilder.newJob(DemoJob01.class)
                .withIdentity("demoJob01") // 名字为 demoJob01
                .storeDurably() // 没有 Trigger 关联的时候任务是否被保留。因为创建 JobDetail 时,还没 Trigger 指向它,所以需要设置为 true ,表示保留。
                .build();
        // 创建 Trigger
        SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
                .withIntervalInSeconds(5) // 频率。
                .repeatForever(); // 次数。
        Trigger trigger = TriggerBuilder.newTrigger()
                .forJob(jobDetail) // 对应 Job 为 demoJob01
                .withIdentity("demoJob01Trigger") // 名字为 demoJob01Trigger
                .withSchedule(scheduleBuilder) // 对应 Schedule 为 scheduleBuilder
                .build();
        // 添加调度任务
        scheduler.scheduleJob(jobDetail, trigger);
//        scheduler.scheduleJob(jobDetail, Sets.newSet(trigger), true);
    }

    @Test
    public void addDemoJob02Config() throws SchedulerException {
        // 创建 JobDetail
        JobDetail jobDetail = JobBuilder.newJob(DemoJob02.class)
                .withIdentity("demoJob02") // 名字为 demoJob02
                .storeDurably() // 没有 Trigger 关联的时候任务是否被保留。因为创建 JobDetail 时,还没 Trigger 指向它,所以需要设置为 true ,表示保留。
                .build();
        // 创建 Trigger
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule("0/10 * * * * ? *");
        Trigger trigger = TriggerBuilder.newTrigger()
                .forJob(jobDetail) // 对应 Job 为 demoJob01
                .withIdentity("demoJob02Trigger") // 名字为 demoJob01Trigger
                .withSchedule(scheduleBuilder) // 对应 Schedule 为 scheduleBuilder
                .build();
        // 添加调度任务
        scheduler.scheduleJob(jobDetail, trigger);
//        scheduler.scheduleJob(jobDetail, Sets.newSet(trigger), true);
    }

}
  • Код для создания JobDetail и Trigger фактически такой же, как«4.6.1 Автоматическая настройка зерен»Это то же самое.
  • В конце каждого метода модульного тестирования вызовитеScheduler#scheduleJob(JobDetail jobDetail, Trigger trigger)метод для сохранения JobDetail и Trigger в базе данных.
  • Если вы хотите переопределить конфигурацию запланированной задачи Quartz в базе данных, вы можете вызватьScheduler#scheduleJob(JobDetail jobDetail, Set<? extends Trigger> triggersForJob, boolean replace)метод, пройтиreplace = trueПереопределить конфигурацию.

4.7 Application

СоздайтеApplication.javaкласс, комплектация@SpringBootApplicationПросто аннотируйте. код показывает, как показано ниже:

// Application.java

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}
  • Запустите класс Application, чтобы запустить пример проекта.具体的执行日志,和"3.4 приложение"В основном то же самое, я не буду повторять список здесь.

Если вы хотите протестировать работу кластера, вы можете создать его зановоApplication02.javaкласс, комплектация@SpringBootApplicationПросто аннотируйте. код показывает, как показано ниже:

// Application02.java

@SpringBootApplication
public class Application02 {

    public static void main(String[] args) {
        // 设置 Tomcat 随机端口
        System.setProperty("server.port", "0");

        // 启动 Spring Boot 应用
        SpringApplication.run(Application.class, args);
    }

}
  • Запустите класс Application02, чтобы начать образец проекта снова. Затем соблюдайте журнал «Выходной журнал», вы можете увидеть, что начнется два примерных проекта, будут иметь журналы выполнения Demojob01 и Demojob02.

5. Быстрый старт XXL-РАБОТА

Пример кода соответствует складу:lab-28-task-xxl-job.

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

Но сейчас в сообществе с открытым исходным кодом есть много отличных промежуточных программ для планирования задач. Среди них наиболее представительными являютсяXXL-JOB. Его собственное определение выглядит следующим образом:

XXL-Job - это легкая планировка распределенных задач, целью основной дизайнерской целью является разработка быстрого развития, простого обучения, легкого, легко расширения.

Для введения XXL-JOB я уже«Минималистское введение Taro Road XXL-JOB»Написано в статье, толстые друзья сначала переходят к этой статье, чтобы прочитать. Суть в том, чтобы сначала построить диспетчерский центр XXL-JOB. 😈 Потому что в этой статье мы здесь, чтобы реализовать исполнителя XXL-JOB в проекте Spring Boot.

5.1 Знакомство с зависимостями

существуетpom.xmlВ файл вводятся соответствующие зависимости.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>lab-28-task-xxl-job</artifactId>

    <dependencies>
        <!-- 实现对 Spring MVC 的自动化配置 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- XXL-JOB 相关依赖 -->
        <dependency>
            <groupId>com.xuxueli</groupId>
            <artifactId>xxl-job-core</artifactId>
            <version>2.1.1</version>
        </dependency>
    </dependencies>

</project>

Для конкретной функции каждой зависимости вы можете внимательно прочитать все добавленные вами комментарии. Жаль, что XXL-JOB в настоящее время официально не предоставляет пакет Spring Boot Starter, что немного смущает. Однако кто-то в сообществе уже отправил запрос на слияние, см. подробности.GitHub.com/Сюэли Сюй/Спасибо....

5.2 Файл конфигурации приложения

существуетapplication.yml, добавьте конфигурацию Quartz следующим образом:

server:
  port: 9090 #指定一个端口,避免和 XXL-JOB 调度中心的端口冲突。仅仅测试之用

# xxl-job
xxl:
  job:
    admin:
      addresses: http://127.0.0.1:8080/xxl-job-admin # 调度中心部署跟地址 [选填]:如调度中心集群部署存在多个地址则用逗号分隔。执行器将会使用该地址进行"执行器心跳注册"和"任务结果回调";为空则关闭自动注册;
    executor:
      appname: lab-28-executor # 执行器 AppName [选填]:执行器心跳注册分组依据;为空则关闭自动注册
      ip: # 执行器IP [选填]:默认为空表示自动获取IP,多网卡时可手动设置指定IP,该IP不会绑定Host仅作为通讯实用;地址信息用于 "执行器注册" 和 "调度中心请求并触发任务";
      port: 6666 # ### 执行器端口号 [选填]:小于等于0则自动获取;默认端口为9999,单机部署多个执行器时,注意要配置不同执行器端口;
      logpath: /Users/yunai/logs/xxl-job/lab-28-executor # 执行器运行日志文件存储磁盘路径 [选填] :需要对该路径拥有读写权限;为空则使用默认路径;
      logretentiondays: 30 # 执行器日志文件保存天数 [选填] : 过期日志自动清理, 限制值大于等于3时生效; 否则, 如-1, 关闭自动清理功能;
    accessToken: yudaoyuanma # 执行器通讯TOKEN [选填]:非空时启用;
  • Для конкретной функции каждого параметра вы можете самостоятельно прочитать подробные примечания.

5.3 XxlJobConfiguration

существуетcn.iocoder.springboot.lab28.task.configВ пути к пакету создайтеDataSourceConfigurationclass, который настраивает исполнителя XXL-JOB. код показывает, как показано ниже:

// XxlJobConfiguration.java

@Configuration
public class XxlJobConfiguration {

    @Value("${xxl.job.admin.addresses}")
    private String adminAddresses;
    @Value("${xxl.job.executor.appname}")
    private String appName;
    @Value("${xxl.job.executor.ip}")
    private String ip;
    @Value("${xxl.job.executor.port}")
    private int port;
    @Value("${xxl.job.accessToken}")
    private String accessToken;
    @Value("${xxl.job.executor.logpath}")
    private String logPath;
    @Value("${xxl.job.executor.logretentiondays}")
    private int logRetentionDays;

    @Bean
    public XxlJobSpringExecutor xxlJobExecutor() {
        // 创建 XxlJobSpringExecutor 执行器
        XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
        xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
        xxlJobSpringExecutor.setAppName(appName);
        xxlJobSpringExecutor.setIp(ip);
        xxlJobSpringExecutor.setPort(port);
        xxlJobSpringExecutor.setAccessToken(accessToken);
        xxlJobSpringExecutor.setLogPath(logPath);
        xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);
        // 返回
        return xxlJobSpringExecutor;
    }

}
  • существует#xxlJobExecutor()метод, который создает объект исполняющего компонента XXL-JOB в контейнере Spring. Обратите внимание, что добавлен метод@BeanАннотация, которая настраивает методы запуска и уничтожения.

5.4 DemoJob

существуетcn.iocoder.springboot.lab28.task.jobВ пути к пакету создайтеDemoJobclass, пример класса задач на время. код показывает, как показано ниже:

// DemoJob.java

@Component
@JobHandler("demoJob")
public class DemoJob extends IJobHandler {

    private Logger logger = LoggerFactory.getLogger(getClass());

    private final AtomicInteger counts = new AtomicInteger();

    @Override
    public ReturnT<String> execute(String param) throws Exception {
        // 打印日志
        logger.info("[execute][定时第 ({}) 次执行]", counts.incrementAndGet());
        // 返回执行成功
        return ReturnT.SUCCESS;
    }

}
  • Наследовать от XXL-JOBIJobHandlerабстрактный класс, реализованный#execute(String param)метод для реализации логики временных задач.
  • По методу добавить@JobHandlerАннотация, задающая имя JobHandler. В дальнейшем нам нужно будет использовать это имя при добавлении задач в консоли диспетчерского центра.

#execute(String param)Возвращаемый результат методаReturnTтип. Когда возвращаемое значение соответствует“ReturnT.code == ReturnT.SUCCESS_CODE”Если задача выполняется успешно, в противном случае это указывает на то, что выполнение задачи не выполнено, и она может пройти“ReturnT.msg”Отзовите информацию об ошибке в диспетчерский центр, таким образом, результат выполнения задачи можно будет легко контролировать в логике задачи.

#execute(String param)Параметром метода является «параметр задачи», настраиваемый при добавлении задачи в консоли диспетчерского центра. В обычных условиях он не будет использоваться.

5.5 Application

СоздайтеApplication.javaкласс, комплектация@SpringBootApplicationПросто аннотируйте. код показывает, как показано ниже:

// Application.java

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

Запустите класс Application, чтобы запустить пример проекта. Выходной журнал сжат следующим образом:

# XXL-JOB 启动日志
2019-11-29 00:58:42.429  INFO 46957 --- [           main] c.xxl.job.core.executor.XxlJobExecutor   : >>>>>>>>>>> xxl-job register jobhandler success, name:demoJob, jobHandler:cn.iocoder.springboot.lab28.task.job.DemoJob@3af9aa66
2019-11-29 00:58:42.451  INFO 46957 --- [           main] c.x.r.r.provider.XxlRpcProviderFactory   : >>>>>>>>>>> xxl-rpc, provider factory add service success. serviceKey = com.xxl.job.core.biz.ExecutorBiz, serviceBean = class com.xxl.job.core.biz.impl.ExecutorBizImpl
2019-11-29 00:58:42.454  INFO 46957 --- [           main] c.x.r.r.provider.XxlRpcProviderFactory   : >>>>>>>>>>> xxl-rpc, provider factory add service success. serviceKey = com.xxl.job.core.biz.ExecutorBiz, serviceBean = class com.xxl.job.core.biz.impl.ExecutorBizImpl
2019-11-29 00:58:42.565  INFO 46957 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2019-11-29 00:58:42.629  INFO 46957 --- [       Thread-7] com.xxl.rpc.remoting.net.Server          : >>>>>>>>>>> xxl-rpc remoting server start success, nettype = com.xxl.rpc.remoting.net.impl.netty_http.server.NettyHttpServer, port = 6666

В настоящее время, поскольку мы не настроили центр планирования XXL-JOB, DemoJob выполняться не будет. Далее настроим соответствующим образом диспетчерский центр XXL-JOB.

5.6 Новый исполнитель

браузер открытhttp://127.0.0.1:8080/xxl-job-admin/jobgroupАдрес, то есть меню «Управление исполнителем». Как показано ниже:执行器管理

Нажмите кнопку «Новый актуатор», чтобы открыть интерфейс «Новый актуатор». Как показано ниже:新增执行器

Заполнение"lab-28-executor"Информация об приводе, нажмите кнопку «Сохранить», чтобы сохранить. Подождите некоторое время, исполнитель будет автоматически зарегистрирован. Как показано ниже:执行器管理

  • Список онлайн-исполнителей отображается в списке исполнителей, а машины кластера, соответствующие исполнителю, можно просмотреть через «Онлайн-машины».

Тот же привод может быть настроен только один раз.

5.7 Новая задача

браузер открытhttp://127.0.0.1:8080/xxl-job-admin/jobinfoАдрес, меню «Управление задачей». Как показано ниже:任务管理

点击最右边的「新增」按钮,弹出「新增」界面。 Как показано ниже:新增

завершенный"demoJob"информацию о задаче, нажмите кнопку «Сохранить», чтобы сохранить. Как показано ниже:任务管理

нажмите"demoJob"Кнопкой «Работа» задачи выберите «Начать», после подтверждения"demoJob"Статус задачи становитсяRUNNING. Как показано ниже:任务管理

На этом этапе мы открываем интерфейс IDE привода, вы можете видеть, что DemoJob выполняется один раз в минуту. Лог выглядит следующим образом:

2019-11-29 01:30:00.161  INFO 48374 --- [      Thread-18] c.i.springboot.lab28.task.job.DemoJob    : [execute][定时第 (1) 次执行]
2019-11-29 01:31:00.012  INFO 48374 --- [      Thread-18] c.i.springboot.lab28.task.job.DemoJob    : [execute][定时第 (2) 次执行]
2019-11-29 01:32:00.009  INFO 48374 --- [      Thread-18] c.i.springboot.lab28.task.job.DemoJob    : [execute][定时第 (3) 次执行]
2019-11-29 01:33:00.010  INFO 48374 --- [      Thread-18] c.i.springboot.lab28.task.job.DemoJob    : [execute][定时第 (4) 次执行]
2019-11-29 01:34:00.005  INFO 48374 --- [      Thread-18] c.i.springboot.lab28.task.job.DemoJob    : [execute][定时第 (5) 次执行]

И мы на интерфейсе диспетчерского центра, нажмите"demoJob"Нажмите кнопку «Операция» задачи и выберите «Журнал запросов», чтобы просмотреть соответствующий журнал планирования. Как показано ниже:查询日志

На данный момент мы завершили внедрение привода XXL-JOB.

6. Быстрый старт Elastic-Job

Возможно, многие толстые друзья не понимают промежуточного программного обеспечения Elastic-Job. Давайте посмотрим на введение в его официальную документацию:

Elastic-Job — это решение для распределенного планирования, состоящее из двух независимых подпроектов Elastic-Job-Lite и Elastic-Job-Cloud.

Elastic-Job-Lite позиционируется как легковесное нецентрализованное решение, использующее форму пакета jar для предоставления координированных услуг распределенным задачам.

Эластичная работа Базовый внутренний открытый источниксамыйВ промежуточном программном обеспечении планирования задач есть несколько промежуточных программ, может не быть ни одной из них, хе-хе. В настоящее время он находится в несколько «сломанном» состоянии, которое можно увидеть в деталях.GitHub.com/эластик-работа/….

Что касается этого примера, мы пока не будем его приводить. Если вас интересует исходный код Elastic-Job, вы можете ознакомиться со следующими двумя сериями:

666. Пасхальное яйцо

① Как выбрать?

Может быть, толстые друзья захотят сравнить различные промежуточные программы планирования. Таблица выглядит следующим образом:

характеристика quartz elastic-job-lite xxl-job LTS
полагаться MySQL, JDK JDK, Zookeeper MySQL, JDK. jdk, смотритель зоопарка, maven
Высокая доступность Многоузловое развертывание путем конкуренции за блокировки базы данных, чтобы гарантировать, что только один узел выполняет задачу. Благодаря регистрации и обнаружению zookeeper серверы могут добавляться динамически. На основе конкурирующих блокировок базы данных только один узел может выполнять задачи и поддерживать горизонтальное расширение. Вы можете вручную добавлять запланированные задачи, запускать и приостанавливать задачи, а также отслеживать Развертывание кластера, вы можете динамически добавить сервер. Время задач можно увеличивать вручную, запускать и приостанавливать задачи. Монитор
Задача ломтик ×
интерфейс управления ×
Степень сложности Простой Простой Простой немного сложно
Расширенные возможности - Эластичное расширение, несколько режимов работы, аварийное переключение, сбор текущего состояния, многопоточная обработка данных, идемпотентность, отказоустойчивая обработка, поддержка пространства имен Spring Эластичное расширение, широковещательная передача фрагментации, отработка отказа, скользящий журнал в реальном времени, GLUE (поддержка кода онлайн-редактирования, бесплатный выпуск), задачи мониторинга хода выполнения, зависимости задач, шифрование данных, оповещение по электронной почте, запуск отчета, международный Поддержка весны, весенней загрузки, бизнес-логгера, поддержки расширения SPI, отработки отказа, мониторинга узлов, поддержки диверсифицированных результатов выполнения задач, отказоустойчивости FailStore, динамического расширения.
обновление новой версии Нет обновлений уже пол года 2 года без обновления Недавно обновленный 1 год без обновления

Также рекомендуем прочитать следующие статьи:

Текущая ситуация, если вы действительно не знаете, что выбрать, вы можете сначала попробоватьXXL-JOB.

② Централизация против децентрализации

Далее давайте кратко расскажем о классификации реализации распределенного планирования промежуточного программного обеспечения. Распределенное промежуточное программное обеспечение будет иметь две роли:

  • Планировщик: отвечает за планирование задач и отправляет их на привод.
  • Исполнитель: отвечает за получение задач и выполнение конкретных задач.

Тогда, если изПерспектива системы планированияЕго можно разделить на две категории:

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

Таким образом, видно, что XXL-Job принадлежитцентрализованныйплатформа планирования задач. В настоящее время эту схему используют:

  • цепьkob
  • Meituan's Crane (пока не с открытым исходным кодом)

децентрализацияПлатформа планирования задач в настоящее время включает в себя:

  • Elastic Job
  • Vipshop'sSaturn
  • QuartzПрограмма базы данных на основе кластера
  • ТаобаоTBSchedule(Приостановите обновление, используйте только Alibaba CloudSchedulerXСлужить)

剉承: Если толстый друг хочет понять больше, вы можете прочитать, что написал друг.«Централизованное планирование против децентрализованного планирования»

③ Конкурс задач VS предварительная постановка задач

Тогда, если изУгол назначения миссииЕго можно разделить на две категории:

  • Конкуренция задач: планировщик будет выдавать задачи исполнителям через конкурирующие задачи.
  • Предварительное назначение задач: планировщик предварительно назначает задачи разным исполнителям без конкуренции.

Таким образом, видно, что XXL-Job принадлежитсоревнование задачплатформа планирования задач. В настоящее время эту схему используют:

  • цепьkob
  • Meituan's Crane (пока не с открытым исходным кодом)
  • QuartzКластерное решение на основе базы данных

предварительная постановка задачиПлатформа планирования задач в настоящее время включает в себя:

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

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

Вот статья, рекомендованная Чжаном Ляном, разработчиком Elastic Job.«Подробное объяснение эластичной работы Dangdang с распределенной работой», Грей всегда силен!

④ Quartz — отличное ядро ​​планирования

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

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

XXL-JOB, наконец, выбирает диспетчерский компонент собственной разработки (ранние диспетчерские компоненты на основе кварца);

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

В плане разработки Elastic-Job 3.X также есть план самостоятельной разработки компонентов планирования для замены Quartz.