Quartz (task scheduling framework)

The Quartz framework is a lightweight task scheduling framework that provides many built-in functions, including: supporting job scheduling, cluster scheduling, persistence, task persistence, task dependencies, priorities, concurrency control, failure retry, etc. . Custom job types and trigger types are also supported.

The following are detailed notes on learning the Quartz framework:

Quartz framework overview

The Quartz framework is an Open Source task scheduling framework for Java applications. Its main advantages are simple integration with various applications and powerful scheduling capabilities. It is a pure Java framework that can be used in any Java application.

Core concepts of the Quartz framework

The core concepts of the Quartz framework include:

1. Job: Indicates a task that needs to be performed. In Quartz, all tasks are classes that implement the Job interface.

2. JobDetail (job details) represents a specific executable scheduler. Job is the content to be executed by the executable scheduler. In addition, JobDetail also includes the task scheduling plan. and strategys.

3. Trigger Trigger represents the configuration of a scheduling parameter and when to adjust.

4.Scheduler (schedule) scheduler, represents a scheduling container. Multiple JobDetails and Triggers can be registered in a scheduling container. When Trigger is combined with JobDetail, it can be scheduled by the Scheduler container.

Scheduler Scheduler 》 Trigger Trigger 》 JobDetail Work Strategy 》Job Job

Quartz framework workflow

The workflow of the Quartz framework is as follows:

  • First, define the task (Job) that needs to be executed, and specify the task execution rules (Trigger).
  • Second, register tasks and triggers with the scheduler.
  • Finally, the scheduler executes the tasks according to the specified conditions and rules.

Usage of Quartz framework

The steps to use the Quartz framework are as follows:

  • Introducing dependencies: First, you need to introduce the dependencies of the Quartz framework into the project.
<dependency>
    <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
  • Write a task class: define the task class that needs to be executed and implement the Job interface.
package com.xxgc.demo.job;
/*
 *
 * @Author:He Baodan
 * @Date: 2023/9/27-09-27-14:23
 * Job work object
 */

import com.xxgc.demo.utils.SpeechSynthesisUtil;
import lombok.extern.slf4j.Slf4j;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

@Slf4j
public class SendEmailToGirlFriendJob implements Job {

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        //Specific code to be executed
        log.info("The conditions are met and I was executed");
        SpeechSynthesisUtil synthesisUtil = new SpeechSynthesisUtil();
        synthesisUtil.speak("I bought breakfast for you and your boyfriend this morning. You said thank you to me, and I knew you had me in your heart.");

    }
}
  • Write a trigger class: define task scheduling rules and implement the Trigger interface.
public class HelloScheduler {
    public static void main(String[] args) throws SchedulerException {
        //1. Create an instance of jobDetail and bind the instance to HelloJob Class
        JobDetail jobDetail = JobBuilder
                .newJob(HelloJob.class) //Define the Job class as the HelloJob class, where the real execution logic lies
                .withIdentity("myJob", "group1") //Define name and group
                .usingJobData("message","hello myJob1") //Add attributes to jobDataMap
                .usingJobData("FloatJobValue",8.88f) //Add attributes to jobDataMap
                .build();

        //2. Create an instance of the Trigger trigger, define the job to be executed immediately, and executed every 2 seconds, always
        SimpleTrigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("myTrigger", "group1")
                .startNow()
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2).repeatForever())
                .build();
        //3. Create schedule instance
        StdSchedulerFactory factory = new StdSchedulerFactory();
        Scheduler scheduler = factory.getScheduler();
        scheduler.start(); //Start
        scheduler.scheduleJob(jobDetail,trigger); // jobDetail and trigger are added to the schedule

    }
}
  • Register tasks and triggers: Register tasks and triggers with the scheduler.
@Schema(name = "Task Scheduling VO", description = "Task Scheduling VO")
@Data
public class QuartzTaskVO {

    @NotNull(message = "Cron expression cannot be null")
    @Schema(description = "Cron expression")
    private String cron;
    @NotNull(message = "Trigger name cannot be empty")
    @Schema(description = "trigger name")
    private String triggerName;
    @NotNull(message = "Trigger group cannot be empty")
    @Schema(description = "Trigger grouping")
    private String triggerGroup;
    @NotNull(message = "Task name cannot be empty")
    @Schema(description = "task name")
    private String jobName;
    @NotNull(message = "Task group cannot be empty")
    @Schema(description = "Task grouping")
    private String jobGroup;
    @NotNull(message = "The execution task object address cannot be empty")
    @Schema(description = "Execution task object address")
    private String jobClass;
/*
 *
 * @Author:H
 * @Date: 2023/9/27-09-27-16:07
 * Task scheduling interface
 */
@Slf4j
@Tag(name = "Task Scheduling")
@RestController
@RequestMapping("/quartz")
public class QuartzController {

    @Operation(summary = "Add task", description = "Task")
    @PostMapping("/addTask")
    public Result addTask(@RequestBody @Valid QuartzTaskVO qtvo) throws SchedulerException, ClassNotFoundException {
        SchedulerUtil.create()
                .withTrigger(qtvo.getTriggerName(),qtvo.getTriggerGroup(),qtvo.getCron())
                .withJob(qtvo.getJobName(),qtvo.getJobGroup(), (Class<? extends Job>) Class.forName(qtvo.getJobClass()))
                .schedule();

        return Result.ok("Add task successfully");
    }




}
  • Start the scheduler: Call the start() method of the scheduler to start the scheduler and start executing the task.
  • Shut down the scheduler: Call the shutdown() method of the scheduler to shut down the scheduler.
@Test
    void quartzTest() throws SchedulerException, InterruptedException {
        //Scheduler - obtained from factory
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

        //Trigger - trigger condition (when to execute)
        String cron = "0 13 15 * * ?";
        Trigger trigger = TriggerBuilder.newTrigger()
                //Trigger name and category
                .withIdentity("sendEmail","girlFriend")
                //Add conditions to the trigger using cron expressions
                .withSchedule(CronScheduleBuilder.cronSchedule(cron))
                //Execute immediately if conditions are met
                .startNow()
                .build();

        //Work strategy - specifies which class will do it
        JobDetail jobDetail = JobBuilder.newJob(SendEmailToGirlFriendJob.class)
                .withIdentity("sendEmailJob","girlFriendJobGroup")
                .build();

        //Bind the trigger to the work strategy and hand it over to the scheduler for management
        scheduler.scheduleJob(jobDetail,trigger);
        //Start the scheduler
        scheduler.start();

        //Stuck here for 1 hour
        Thread.sleep(1000 * 60 * 60);
    }

Trigger types of Quartz framework

The Quartz framework supports multiple types of triggers, including:

  • SimpleTrigger: used to trigger a task at a specified time.
  • CronTrigger: used to trigger tasks according to specified time rules.
  • CalendarIntervalTrigger: used to trigger tasks at specified intervals.
  • DailyTimeIntervalTrigger: used to trigger tasks according to the specified time interval and time period.

Task types of Quartz framework

The Quartz framework supports many types of tasks, including:

  • SimpleJob: Simple task.
  • StatefulJob: A stateful task that needs to wait for the task to be completed before executing the next task.
  • JobDetail: You can set the parameters and description information of the task.

Cluster scheduling of Quartz framework

The Quartz framework supports cluster scheduling and can schedule and execute tasks on multiple nodes. In a cluster, a task will only be executed on one of the nodes.

Quartz provides a mechanism called cluster scheduling that allows multiple nodes to work together to ensure that only one node is running a job at any time.

To implement cluster scheduling, each node must be connected to the same database, and the scheduler instance must have a unique identifier.

In a cluster environment, all nodes can read and update the scheduling status in the database. When the scheduler starts, it will try to obtain all unlocked triggers and schedule them for execution on the local node.

If another node already has a scheduled trigger for a job, the job will be executed on that node. When the node completes the job, it updates the job status to the database, notifying other nodes that it has completed the job.

In addition, Quartz uses a database-based locking mechanism by default to ensure that only one node is running a job at the same time. If a node cannot obtain a lock, it will wait until it can obtain a lock.

  1. Advantages and Disadvantages of Cluster Scheduling

advantage:

  • Ensure that only one node is running the job at any time to avoid running the job repeatedly.
  • Allowing rapid horizontal expansion, adding nodes can improve the overall performance of the system.

shortcoming:

  • Cluster scheduling requires a shared database, which may become a bottleneck in the system.
  • The use of effective locking mechanisms and database structures must be considered to ensure system stability and reliability.

Persistence of Quartz framework

The Quartz framework supports task persistence and can store task status in the database to ensure that task information will not be lost.

  1. Persistence using JDBC: Quartz supports persistence using JDBC. When using JDBC persistence, you need to configure Quartz’s data source and corresponding database table. When starting Quartz, it will automatically create any missing tables. If you choose to use JDBC persistence, you will also need to configure a special data source for Quartz, which must provide access to all tables required by Quartz.

  2. Persistence using Terracotta: Terracotta is a memory-based distributed caching solution that can be used to persist Quartz’s tasks and triggers in memory. Using Terracotta persistence improves Quartz’s performance and scalability and does not require any dedicated database tables.

  3. Tradeoffs: Which persistence mechanism you choose depends on the specific requirements of your application. If your application requires persistent tasks and triggers, but not performance and scalability, using JDBC persistence may be the best option. If you need better performance and scalability, Terracotta persistence may be a better choice.

  4. State Objects: When using any type of persistence, you must consider how to handle the state objects of your tasks and triggers. When Quartz restarts, it must be able to recreate these state objects. You can choose to embed a serialized object in the state object or save the state in a database or distributed cache.

Error handling and retry mechanism of Quartz framework

The Quartz framework supports error handling and retry mechanisms, which can handle errors and retry after task execution fails to ensure the correct execution of the task.

cron expression:

  • *: represents matching all possible values.

  • -: used to specify a range, for example, 1-5 means from 1 to 5.

  • /: used to specify the step size, for example, 0/15 means execution every 15 minutes.

  • ,: used to specify multiple values, for example, 1,3,5 means execution at 1, 3 and 5.

  • ?: Available in some fields, for example, in the week field, it means not specifying a value.

Quartz tool class encapsulation:

/*
 *
 * @Author:He Baodan
 * @Date: 2023/9/27-09-27-16:02
 * Task scheduling tools
 */

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

public class SchedulerUtil {

    private Scheduler scheduler;
    private Trigger trigger;
    private JobDetail jobDetail;

    private SchedulerUtil(Scheduler scheduler) {
        this.scheduler = scheduler;
    }

    public static SchedulerUtil create() throws SchedulerException {
        return new SchedulerUtil(StdSchedulerFactory.getDefaultScheduler());
    }

    public SchedulerUtil withTrigger(String name, String group, String cronExpression) {
        this.trigger = TriggerBuilder.newTrigger()
                .withIdentity(name, group)
                .withSchedule(CronScheduleBuilder.cronSchedule(cronExpression))
                .startNow()
                .build();
        return this;
    }

    public SchedulerUtil withJob(String name, String group, Class<? extends Job> jobClass) {
        this.jobDetail = JobBuilder.newJob(jobClass)
                .withIdentity(name, group)
                .build();
        return this;
    }

    public void schedule() throws SchedulerException {
        if (trigger == null || jobDetail == null) {
            throw new IllegalStateException("Trigger and JobDetail must be set before scheduling.");
        }
        scheduler.scheduleJob(jobDetail, trigger);
        scheduler.start();
    }
}