Solutions for Java timing tasks (Quartz, etc.)

① JDK timer: Timer

Theoretical basis: time wheel algorithm

  • Linked list or array implements time wheel: while-true-sleep

    Traverse the array, place a linked list for each subscript, place tasks in the linked list nodes, take out and execute when the traversal is reached

    Defect: Assuming that the length of the array is 12 to represent the hour, then I want to execute it at 13 o'clock, which is not very convenient

  • Round-type time wheel: a round is recorded on the task, and when it is traversed, the round is reduced by one, and when it is 0, it is taken out and executed

    Need to traverse all tasks, low efficiency

  • Hierarchical time wheel: using multiple wheels of different dimensions

    Tianlun: record when to execute

    Lunar Wheel: Record Sign Execution

    The moon wheel has been traversed, and the task can be removed and placed in the sky wheel, so that it can be executed on a certain date and time

/**
 * @author cVzhanshi
 * @create 2022-11-02 20:27
 */
public class TimerTest {
    
    
    public static void main(String[] args) {
    
    
        Timer timer = new Timer();  // 任务启动
        for (int i = 0; i < 2; i++) {
    
    
            TimerTask timerTask = new FooTimerTask("cvzhanshi" + i);
            // 任务添加 第二个参数,启动时间   第三个参数,执行间隔时间
            timer.schedule(timerTask , new Date() , 2000);
            // 预设的执行时间nextExecutTime 12:00:00   12:00:02  12:00:04
            //schedule  真正的执行时间 取决上一个任务的结束时间  ExecutTime   03  05  08  丢任务(少执行了次数)
            //scheduleAtFixedRate  严格按照预设时间 12:00:00   12:00:02  12:00:04(执行时间会乱)
            //单线程  任务阻塞  任务超时
        }
    }
}

Perform source code parsing

// Timer类中的两个常量
// 任务队列
private final TaskQueue queue = new TaskQueue();
// 执行任务的线程
private final TimerThread thread = new TimerThread(queue);
  1. new Timer();

    // 进入构造器
    public Timer() {
          
          
        // 调用了另一个构造器
        this("Timer-" + serialNumber());
    }
    
    public Timer(String name) {
          
          
        thread.setName(name);
        // 启动线程执行线程的run方法
        thread.start();
    }
    
  2. The thread's run() method

    public void run() {
          
          
        try {
          
          
            // 主要方法 后面再说
            mainLoop();
        } finally {
          
          
          ...
        }
    }
    
  3. timer.schedule(timerTask , new Date() , 2000);

    public void schedule(TimerTask task, Date firstTime, long period) {
          
          
        if (period <= 0)
            throw new IllegalArgumentException("Non-positive period.");
        sched(task, firstTime.getTime(), -period);
    }
    
    
    private void sched(TimerTask task, long time, long period) {
          
          
        if (time < 0)
            throw new IllegalArgumentException("Illegal execution time.");
    
        // 充分限制周期值,以防止数字溢出,同时仍然有效地无限大。
        if (Math.abs(period) > (Long.MAX_VALUE >> 1))
            period >>= 1;
    
        
        synchronized(queue) {
          
          
            if (!thread.newTasksMayBeScheduled)
                throw new IllegalStateException("Timer already cancelled.");
    
            synchronized(task.lock) {
          
          
                if (task.state != TimerTask.VIRGIN)
                    throw new IllegalStateException(
                    "Task already scheduled or cancelled");
                // 设置下次执行时间
                task.nextExecutionTime = time;
                task.period = period;
                task.state = TimerTask.SCHEDULED;
            }
    		// 加入任务队列
            queue.add(task);
            // 获取最近要执行的任务,如果是当前任务,唤醒队列
            if (queue.getMin() == task)
                queue.notify();
        }
    }
    
  4. mainLoop() main execution steps

    private void mainLoop() {
          
          
        while (true) {
          
          
            try {
          
          
                TimerTask task;
                boolean taskFired;
                synchronized(queue) {
          
          
                    // 等待队列变为非空
                    while (queue.isEmpty() && newTasksMayBeScheduled)
                        queue.wait();
                    if (queue.isEmpty())
                        break; // 队列是空的,将永远保留;死亡
    
                    // 队列非空;获取第一个任务去执行
                    long currentTime, executionTime;
                    // 获取任务
                    task = queue.getMin();
                    synchronized(task.lock) {
          
          
                        // 如果任务状态是取消,无需操作,再次轮询队列
                        if (task.state == TimerTask.CANCELLED) {
          
          
                            queue.removeMin();
                            continue; 
                        }
                        // 当前时间
                        currentTime = System.currentTimeMillis();
                        // 下次执行的时间
                        executionTime = task.nextExecutionTime;
                        // 如果下次执行时间小于等于当前时间
                        if (taskFired = (executionTime<=currentTime)) {
          
          
                            // 如果任务是单次的,直接删除
                            if (task.period == 0) {
          
           
                                queue.removeMin();
                                task.state = TimerTask.EXECUTED;
                            } else {
          
           
                                // 重复任务,重新安排,计算下次执行时间
                                queue.rescheduleMin(
                                    task.period<0 ? currentTime   - task.period
                                    : executionTime + task.period);
                            }
                        }
                    }
                    //任务尚未启动;等待
                    if (!taskFired) 
                        queue.wait(executionTime - currentTime);
                }
                if (taskFired)
                    // 执行任务,注意是单线程运行,不是运行start方法运行的
                    task.run();
            } catch(InterruptedException e) {
          
          
            }
        }
    }
    

Summary: Timer is a flawed single-threaded task blocking task timeout

  • TaskQueue: small top heap, storing timeTask
  • TimerThread: task execution thread
    • The infinite loop constantly checks whether there is a task that needs to be executed, and executes it if there is one
    • still execute in this thread
  • Single-threaded execution of tasks, tasks may block each other
    • schedule: Task execution timeout will cause subsequent tasks to be pushed back, and the number of executions within the specified time will be reduced
    • scheduleAtFixedRate: Task timeout may cause the next task to be executed immediately, without intermediate intervals
  • A runtime exception will cause the timer thread to terminate
  • Task scheduling is based on absolute time and is sensitive to system time

② Timing task thread pool

test code

/**
 * @author cVzhanshi
 * @create 2022-11-03 15:05
 */
public class ScheduleThreadPoolTest {
    
    

    public static void main(String[] args) {
    
    
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
        for (int i = 0; i < 2; i++){
    
    
            // 第一个参数:任务
            // 第二个参数:第一次执行的时间
            // 第三个参数:下次任务间隔的时间
            // 第四个参数:时间单位
            scheduledThreadPool.scheduleAtFixedRate(new Task("task-" + i ),0,2, TimeUnit.SECONDS);
            
            // 执行单次任务的api
            // scheduledThreadPool.schedule(new Task("task-" + i ),0, TimeUnit.SECONDS);
        }
    }
}
class Task implements Runnable{
    
    

    private String name;

    public Task(String name) {
    
    
        this.name = name;
    }

    public void run() {
    
    
        try {
    
    
            System.out.println("name="+name+",startTime=" + new Date());
            Thread.sleep(3000);
            System.out.println("name="+name+",endTime=" + new Date());

            //线程池执行
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }
}

ScheduledThreadPoolExecutor

  • Use multiple threads to perform tasks without blocking each other
  • If the thread fails, a new thread will be created to perform the task
    • If the thread throws an exception, the task will be discarded and needs to be captured
  • DelayedWorkQueue: small top heap, unbounded queue
    • In a timed thread pool, the maximum number of threads is meaningless
    • Tasks whose execution time is closer to the current time are at the front of the queue
    • Used to add ScheduleFutureTask (inherit Future, implement RunnableScheduledFuture interface)
    • The thread in the thread pool gets the ScheduleFutureTask from the DelayQueue, and then executes the task
    • Implement the Delayd interface, you can get the delay time through the getDelay method
  • Leader-Follower mode
    • Suppose there are a bunch of tasks waiting to be executed (usually stored in a queue and sorted), and only one of all worker threads is a leader thread, and the other threads are follower threads. Only the leader thread can perform tasks, while the remaining follower threads will not perform tasks, and they will be in a dormant state. When the leader thread gets the task and before it executes the task, it will become a follower thread, and a new leader thread will be selected at the same time, and then the task will be executed. If there is a next task at this time, it is the new leader thread to execute, and this process is repeated. When the previous task-executing thread finishes executing and comes back, it will judge that if there is no task at this time, or if there is a task but there are other threads as the leader thread, then it will sleep by itself; if there is a task but no leader at this time thread, then it will re-become the leader thread to perform tasks
    • Avoid unnecessary wakeup and blocking operations
  • Application Scenario
    • It is suitable for multiple background threads to perform periodic tasks, and it is necessary to limit the number of background threads in order to meet the needs of resource management

SingleThreadScheduledExecutor

  • Single-threaded ScheduledThreadPoolExecutor
  • Application scenario: It is suitable for a single background thread to execute periodic tasks, and at the same time, it is necessary to ensure that the tasks are executed sequentially

③ Timing framework Quartz

Learning Document 1

Learning Document 2

3.1 Introduction

Quartz is another open source project of the OpenSymphony open source organization in the field of Job scheduling. It can be combined with 2EE and |2SE applications or used alone.

Quartz is an open source "task scheduling library" with rich features , which can be integrated into any java application, ranging from small independent applications to large e-commerce systems . Quartz can create simple and complex schedules to execute tens, hundreds, or even tens of thousands of tasks. Task jobs are defined as standard java components that can perform any function you want. The quartz scheduling framework includes many enterprise-level features, such as JTA transactions and cluster support.

In short, quartz is a task scheduling framework based on java, which is used to execute any task you want.

Design patterns involved in Quartz

● Builder mode

● Factory mode

● Component mode JobDetail Trigger

● Chain programming

3.2 Core Concepts and Architecture

  • Task Job

    Job is the task class you want to implement. Each Job must implement the org.quartz.job interface, and only need to implement the execute() method defined by the interface.

  • Trigger Trigger

    Trigger is the trigger for you to execute the task. For example, if you want to send a statistical email at 3 o'clock every day, Trigger will set 3 o'clock to execute the task. Trigger mainly includes two types of SimplerTrigger and CronTrigger.

  • SchedulerScheduler

    The Scheduler is the scheduler of the task, which integrates the task Job and the trigger Trigger, and is responsible for executing the Job based on the time set by the Trigger.

Quartz Architecture

3.3 Commonly used components

The following are several important interfaces of the Quartz programming API, which are also important components of Quartz.

  • Scheduler - The main API for interacting with the scheduler.
  • Job - the interface you want the task component to be implemented by the scheduler
  • JobDetail - An instance used to define a job.
  • Trigger - A component that defines a plan for executing a given job.
  • JobBuilder - Used to define/build a JobDetail instance, used to define an instance of a job.
  • TriggerBuilder - Used to define/build trigger instances.
  • The life cycle of the Scheduler starts when the SchedulerFactory creates it and ends when the Scheduler calls the shutdown() method; after the Scheduler is created, it can add, delete, and enumerate Jobs and Triggers, and perform other scheduling-related operations (such as suspending Triggers) . However, the Scheduler will actually trigger the trigger (i.e. execute the job) only after calling the start() method

3.4 Getting Started Demo

  • Create a spring boot project

  • import dependencies

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-quartz</artifactId>
    </dependency>
    
  • Create HelloJob

    /**
     * @author cVzhanshi
     * @create 2022-11-02 18:35
     */
    public class HelloJob implements Job {
          
          
        @Override
        public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
          
          
            System.out.println("HelloJob" + new Date());
        }
    }
    
  • Call the configuration task in the main method

    /**
     * @author cVzhanshi
     * @create 2022-11-02 18:36
     */
    public class HelloSchedulerDemo {
          
          
        public static void main(String[] args) throws SchedulerException {
          
          
            // 1.调度器(Scheduler),从工厂获取调度实例
            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
            //2.任务实例(JobDetail)
            JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)   //加载任务类,与HelloJob完成绑定,要求HelloJob实现Job接口
                    .withIdentity("job1", "group1")      //参数1:任务的名称(唯一实例),参数2:任务组的名称
                    .build();
            //3.触发器(Trigger)
            SimpleTrigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity("trigger1", "group2")  //参数1:触发器的名称(唯一实例),参数2:触发器组的名称
                    .startNow() // 马上启动触发器
                    .withSchedule(SimpleScheduleBuilder.simpleSchedule().repeatSecondlyForever(5))// 每5s触发一次,一直执行
                    .build();
            //让调度器关联任务和触发器,保证按照触发器定义的条件执行任务
            scheduler.scheduleJob(jobDetail, trigger);
    
            scheduler.start();
        }
    }
    
  • Results of the

    HelloJobThu Nov 03 16:54:05 CST 2022 //每五秒执行一次
    
    HelloJobThu Nov 03 16:54:10 CST 2022
    
    HelloJobThu Nov 03 16:54:15 CST 2022
    

3.5 Job 和 JobDetail

public static void main(String[] args) throws SchedulerException {
    
    
    // 1.调度器(Scheduler),从工厂获取调度实例
    Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
    //2.任务实例(JobDetail)
    JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)   //加载任务类,与HelloJob完成绑定,要求HelloJob实现Job接口
        .withIdentity("job1", "group1")      //参数1:任务的名称(唯一实例),参数2:任务组的名称
        .build();
    //3.触发器(Trigger)
    SimpleTrigger trigger = TriggerBuilder.newTrigger()
        .withIdentity("trigger1", "group2")  //参数1:触发器的名称(唯一实例),参数2:触发器组的名称
        .startNow() // 马上启动触发器
        .withSchedule(SimpleScheduleBuilder.simpleSchedule().repeatSecondlyForever(5))
        .build();
    //让调度器关联任务和触发器,保证按照触发器定义的条件执行任务
    scheduler.scheduleJob(jobDetail, trigger);

    scheduler.start();
}

We know through the demo. We pass the scheduler a JobDetail instance, because when we create JobDetail, we pass the class name of the job to be executed to JobDetail, so the scheduler knows to execute

What type of job to execute; every time the scheduler executes the job, a new instance of the class will be created before calling its execute(...) method; after execution, the reference to the instance will be discarded, and the instance will be garbage

Garbage collection; one consequence of this execution strategy is that the job must have a no-argument constructor (when using the default JobFactory); another consequence is that stateful data should not be defined in the job class

properties, because the values ​​of these properties are not preserved across multiple executions of the job .

So how to add attributes or configurations to the job instance? How to track the status of the job during multiple executions of the job?

  • JobDataMap

JobDataMap

JobDataMap can contain unlimited (serialized) data objects, and the data in it can be used when the job instance is executed; JobDataMap is an implementation of the Java Map interface, adding an additional

Methods that facilitate access to primitive types of data.

Before adding the job to the scheduler, when constructing the JobDetail, you can put the data into the JobDataMap, as shown in the code:

//创建一个job
JobDetail job = JobBuilder.newJob(HelloJob.class)
    .usingJobData("j1", "jv1")
    .withIdentity("myjob", "mygroup")
    .usingJobData("jobSays", "Hello World!")
    .usingJobData("myFloatValue", 3.141f)
    .build();

During the execution of the job, data can be retrieved from the JobDataMap, as shown in the following example:

public class HelloJob implements Job {
    
    
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
    
    
        Object tv1 = context.getTrigger().getJobDataMap().get("t1");
        Object tv2 = context.getTrigger().getJobDataMap().get("t2");
        Object jv1 = context.getJobDetail().getJobDataMap().get("j1");
        Object jv2 = context.getJobDetail().getJobDataMap().get("j2");
        Object sv = null;
        try {
    
    
            sv = context.getScheduler().getContext().get("skey");
        } catch (SchedulerException e) {
    
    
            e.printStackTrace();
        }
        System.out.println(tv1+":"+tv2);
        System.out.println(jv1+":"+jv2);
        System.out.println(sv);
        System.out.println("hello:"+ LocalDateTime.now());
    }
}

If you add a set method to the key of the data stored in the JobDataMap in the job class (such as in the above example, add the setJobSays(String val) method), then Quartz's default JobFactory implementation will be called automatically when the job is instantiated These set methods, so that you don't need to explicitly fetch data from the map in the execute() method.

/**
 * @author cVzhanshi
 * @create 2022-11-02 18:35
 */
public class HelloJob implements Job {
    
    
    private String j1;

    public void setJ1(String j1) {
    
    
        this.j1 = j1;
    }

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
    
    
        
        System.out.println(j1); // jv1
    }
}


JobDetail job = JobBuilder.newJob(HelloJob.class)
    .usingJobData("j1", "jv1")
    .withIdentity("myjob", "mygroup")
    .build();

When the Job is executed, the JobDataMap in the JobExecutionContext provides us with a lot of convenience. It is the union of JobDataMap in JobDetail and JobDataMap in Trigger, but if there is

In the same data, the latter will override the value of the former.

@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
    
    
    JobDataMap mergedJobDataMap = jobExecutionContext.getMergedJobDataMap();
    System.out.println(mergedJobDataMap.get("j1"));
    System.out.println(mergedJobDataMap.get("t1"));
    System.out.println(mergedJobDataMap.get("name")); // jobDetail中的数据会被trigger覆盖,如果key相同的话
}

Job instance

  • You can create only one job class, and then create multiple JobDetail instances associated with the job, each instance has its own property set and JobDataMap, and finally, add all instances to the scheduler.

  • For example, you create a class "SalesReportJob" that implements the Job interface. The job requires a parameter (passed in via the JobdataMap) that represents the name of the salesperson responsible for the sales report. Therefore, you can create multiple instances of the job (JobDetail), such as "SalesReportForJoe", "SalesReportForMike", and pass "joe" and "mike" as the data of the JobDataMap to the corresponding job instance.

  • When a trigger is triggered, the associated JobDetail instance will be loaded, and the job class referenced by JobDetail is initialized through the JobFactory configured on the Scheduler. The default JobFactory implementation just calls the newInstance() method of the job class , and then tries to call the setter method of the key in the JobDataMap. You can also create your own JobFactory implementation, such as allowing your IOC or DI container to create/initialize job instances.

  • In Quartz's description language, we refer to the saved JobDetail as "job definition" or "JobDetail instance" , and an executing job as "job instance" or "job definition instance ". When we use "job", we generally refer to the job definition, or JobDetail; when we refer to a class that implements the Job interface, we usually use "job class".

Job status and concurrency

  • Every time the Scheduler is executed, a new Job instance will be created according to the JobDetail, so that the problem of concurrent visits can be avoided (the instance of JobDetail is also new), see the following code example

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
          
          
        System.out.println("JobDetail: " + System.identityHashCode(jobExecutionContext.getJobDetail().hashCode()));
        System.out.println("Job: " + System.identityHashCode(jobExecutionContext.getJobInstance().hashCode()));
    }
    
    JobDetail: 2046941357
    Job: 1895412701
    // 每次都不一样
    JobDetail: 1157785854
    Job: 1341784974
    
  • **Quartz scheduled tasks are executed concurrently by default, and will not wait for the last task to be executed, as long as the interval is up, it will be executed. If the scheduled task is executed for too long, resources will be occupied for a long time, causing other tasks to be blocked. **Test concurrent execution:

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
          
          
        System.out.println("execute:" + new Date());
        try {
          
          
            Thread.sleep(3000);
        } catch (InterruptedException e) {
          
          
            e.printStackTrace();
        }
    }
    
    // 任务设置成1s执行一次
    // 执行结果 还是每秒执行一次,并没有sleep3s 所以是并发执行
    // execute:Thu Nov 03 17:36:55 CST 2022
    // execute:Thu Nov 03 17:36:56 CST 2022
    // execute:Thu Nov 03 17:36:57 CST 2022 
    

There are a few things to note about job state data (i.e. JobDataMap) and concurrency. Some annotations can be added to the job class, and these annotations will affect the status and concurrency of the job.

  • @DisallowConcurrentExecution: Add this annotation to the job class to tell Quartz not to execute multiple instances of the same job definition (here, a specific job class) concurrently . Take the example above

    For example, if there is this annotation on the "SalesReportJob" class, only one "SalesReportForJoe" instance is allowed to execute at the same time, but one instance of the "SalesReportForMike" class can be executed concurrently.

    So the restriction is for the JobDetail, not the job class. But we think (when designing Quartz) that annotation should be placed on the job class, because changes to the job class often cause its behavior to change

    Variety.

    code testing

    @DisallowConcurrentExecution
    public class HelloJob implements Job {
          
           
        @Override
        public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
          
          
            System.out.println("execute:" + new Date());
            try {
          
          
                Thread.sleep(3000);
            } catch (InterruptedException e) {
          
          
                e.printStackTrace();
            }
        }
    }
    
    // 任务设置成1s执行一次
    // 执行结果 
    // execute:Thu Nov 03 17:36:55 CST 2022
    // execute:Thu Nov 03 17:36:58 CST 2022 
    

​The execution result is to sleep for 3s before executing the next task, so there is no concurrent execution of multiple instances of the same job definition (here refers to a specific job class)

  • **@PersistJobDataAfterExecution: Add this annotation to the job class to tell Quartz to update the data in JobDataMap in JobDetail (invalid for the datamap in Trigger) after the execute method of the job class is successfully executed (without any exception), so that the When the job (JobDetail) is executed next time, the updated data in the JobDataMap is not the old data before the update. **Same as the @DisallowConcurrentExecution annotation, although the annotation is added to the job class, its restriction is for the job instance, not the job class. The annotation is carried by the job class because the content of the job class often affects its behavior state (for example, the execute method of the job class needs to explicitly "understand" its "state"). Explanation: Because each execution instance is a new instance, the data in the instance is new. With this annotation, the update of the previous instance will update the data for the next instance.

If you use the @PersistJobDataAfterExecution annotation, we strongly recommend that you also use the @DisallowConcurrentExecution annotation, because when two instances of the same job (JobDetail)

When executed concurrently, the data stored in the JobDataMap is likely to be uncertain due to competition.

Other features of Job

Through the JobDetail object, other attributes that can be configured for the job instance are:

  • Durability: If a job is non-durable, it will be automatically deleted from the scheduler when there is no active trigger associated with it. In other words, the lifetime of a non-persistent job is determined by the trigger

    Whether it exists or not;

  • RequestsRecovery: If a job is recoverable, and during its execution, the scheduler has a hard shutdown (hard shutdown) (such as a running process crashing, or shutting down), then

    When the scheduler restarts, the job will be re-executed. At this point, the job's JobExecutionContext.isRecovering() returns true.

3.6 Trigger

Trigger's public properties

All types of triggers have the attribute TriggerKey, which indicates the identity of the trigger; in addition, the trigger has many other public attributes. These attributes can be passed when building the trigger

TriggerBuilder settings.

The public properties of trigger are:

  • jobKey attribute: the identity of the job executed when the trigger is triggered ;

  • startTime attribute: set the time when the trigger is triggered for the first time ; the value of this attribute is java.util.Date type, indicating a specified point in time; some types of triggers will be triggered immediately at the set startTime

    That is, trigger, some types of triggers indicate that the trigger will take effect after startTime. For example, it is January now, and you set a trigger-"execute on the fifth day of every month", then you will

    If the startTime attribute is set to April 1st, the trigger will be triggered for the first time in a few months (that is, April 5th).

  • endTime attribute: Indicates the time point when the trigger fails. For example, for a trigger that is "executed on the fifth day of each month", if its endTime is July 1, its last execution time is June 5.

priority

If you have a lot of triggers (or too few worker threads in the Quartz thread pool), Quartz may not have enough resources to trigger all triggers at the same time; in this case, you may want to control which triggers are used first

Quartz's worker thread, to achieve this purpose, you can set the priority attribute on the trigger. For example, if you have N triggers that need to be triggered at the same time, but there are only Z worker threads, the Z triggers with the highest priority will be triggered

Trigger first. If no priority is set for the trigger, the trigger uses the default priority with a value of 5; the value of the priority attribute can be any integer, positive or negative.

  • Note: Only triggers that are triggered at the same time will compare priorities. The trigger triggered at 10:59 is always executed before the trigger triggered at 11:00.
  • Note: If the trigger is recoverable, when it is rescheduled after recovery, the priority is the same as that of the original trigger.

missed trigger

The trigger also has an important attribute misfire; if the scheduler is closed, or there are no available threads in the Quartz thread pool to execute the job, the persistent trigger will miss (miss) its trigger time,

That is, a misfire is missed. Different types of triggers have different misfire mechanisms. They all use "smart policy" by default, which dynamically adjusts behavior according to the type and configuration of the trigger. when

When the scheduler starts, query all persistent triggers that have missed triggers (misfire). Then update the trigger information according to their respective misfire mechanism.

Conditions for judging misfire

  • The job has not been executed when it reaches the trigger time
  • The executed delay time exceeds the misfire Threshole threshold configured by Quartz

possible cause

  • When the job reaches the trigger time, all threads are occupied by other jobs and no threads are available
  • At the point when the job needs to be triggered, the scheduler stops (possibly unexpectedly stopped)
  • The job uses the @ DisallowConcurrentExecution annotation. The job cannot be executed concurrently. When the next job execution point is reached, the previous task has not been completed.
  • The job specifies the start execution time in the past, for example, the current time is 8:00:00, and the specified start time is 7:00:00

3.7 Spring Boot integrates Quartz

  • Create a springboot project

  • Import required dependencies

    <?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.5.7</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>cn.cvzhanshi</groupId>
        <artifactId>test</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>test</name>
        <description>Demo project for Spring Boot</description>
        <properties>
            <java.version>1.8</java.version>
        </properties>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-quartz</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-jpa</artifactId>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-autoconfigure</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
    </project>
    
  • Edit the configuration file application.yaml

    server:
      port: 8889
      datasource:
        url: jdbc:mysql://localhost:3306/test_quartz?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
        username: root
        password: 123456
        driver-class-name: com.mysql.cj.jdbc.Driver
    
  • Edit Quartz configuration file spring-quartz.properties

    #============================================================================
    # 配置JobStore
    #============================================================================
    # JobDataMaps是否都为String类型,默认false
    org.quartz.jobStore.useProperties=false
    
    # 表的前缀,默认QRTZ_
    org.quartz.jobStore.tablePrefix = QRTZ_
    
    # 是否加入集群
    org.quartz.jobStore.isClustered = true
    
    # 调度实例失效的检查时间间隔 ms
    org.quartz.jobStore.clusterCheckinInterval = 5000
    
    # 数据保存方式为数据库持久化
    org.quartz.jobStore.class=org.springframework.scheduling.quartz.LocalDataSourceJobStore
    
    # 数据库代理类,一般org.quartz.impl.jdbcjobstore.StdJDBCDelegate可以满足大部分数据库
    org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
    
    #============================================================================
    # Scheduler 调度器属性配置
    #============================================================================
    # 调度标识名 集群中每一个实例都必须使用相同的名称
    org.quartz.scheduler.instanceName = ClusterQuartz
    # ID设置为自动获取 每一个必须不同
    org.quartz.scheduler.instanceId= AUTO
    
    #============================================================================
    # 配置ThreadPool
    #============================================================================
    # 线程池的实现类(一般使用SimpleThreadPool即可满足几乎所有用户的需求)
    org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
    
    # 指定线程数,一般设置为1-100直接的整数,根据系统资源配置
    org.quartz.threadPool.threadCount = 5
    
    # 设置线程的优先级(可以是Thread.MIN_PRIORITY(即1)和Thread.MAX_PRIORITY(这是10)之间的任何int 。默认值为Thread.NORM_PRIORITY(5)。)
    org.quartz.threadPool.threadPriority = 5
    
  • Edit the Job class

    /**
     * @author cVzhanshi
     * @create 2022-11-07 10:20
     */
    @PersistJobDataAfterExecution
    @DisallowConcurrentExecution
    public class QuartzJob extends QuartzJobBean {
          
          
        @Override
        protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
          
          
            try {
          
          
                Thread.sleep(2000);
                System.out.println(context.getScheduler().getSchedulerInstanceId());
                System.out.println("taskname=" + context.getJobDetail().getKey().getName());
                System.out.println("执行时间:" + new Date());
            } catch (InterruptedException e) {
          
          
                e.printStackTrace();
            } catch (SchedulerException e) {
          
          
                e.printStackTrace();
            }
        }
    }
    
  • Unified use of a configured scheduler Scheduler

    /**
     * @author cVzhanshi
     * @create 2022-11-07 10:53
     */
    @Configuration
    public class SchedulerConfig {
          
          
    
        @Autowired
        private DataSource dataSource;
    
        @Bean
        public Scheduler scheduler() throws IOException {
          
          
            return schedulerFactoryBean().getScheduler();
        }
    
        @Bean
        public SchedulerFactoryBean schedulerFactoryBean() throws IOException {
          
          
            SchedulerFactoryBean factoryBean = new SchedulerFactoryBean();
            factoryBean.setSchedulerName("cluster_scheduler");
            // 设置数据源
            factoryBean.setDataSource(dataSource);
            factoryBean.setApplicationContextSchedulerContextKey("application");
            // 加载配置文件
            factoryBean.setQuartzProperties(quartzProperties());
            // 设置线程池
            factoryBean.setTaskExecutor(schedulerThreadPool());
            // 设置延迟开启任务时间
            factoryBean.setStartupDelay(0);
            return factoryBean;
        }
    
        @Bean
        public Properties quartzProperties() throws IOException {
          
          
            PropertiesFactoryBean factoryBean = new PropertiesFactoryBean();
            factoryBean.setLocation(new ClassPathResource("/spring-quartz.properties"));
            factoryBean.afterPropertiesSet();
            return factoryBean.getObject();
        }
    
    
        @Bean
        public Executor schedulerThreadPool(){
          
          
            ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
            executor.setCorePoolSize(Runtime.getRuntime().availableProcessors());
            executor.setMaxPoolSize(Runtime.getRuntime().availableProcessors());
            executor.setQueueCapacity(Runtime.getRuntime().availableProcessors());
            return executor;
        }
    }
    
  • Write a listener, listen to container loading events, and start the task when the loading is complete

    /**
     * @author cVzhanshi
     * @create 2022-11-07 11:04
     */
    @Component
    // 监听springboot容器是否启动,启动了就执行定时任务
    public class StartApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
          
          
    
        @Autowired
        private Scheduler scheduler;
    
        @Override
        public void onApplicationEvent(ContextRefreshedEvent event) {
          
          
            TriggerKey triggerKey = TriggerKey.triggerKey("trigger1", "group1");
            try {
          
          
                /**
                 * 在调度器中获取指定key的trigger,
                 */
                Trigger trigger = scheduler.getTrigger(triggerKey);
                if(trigger == null){
          
          
                    trigger = TriggerBuilder.newTrigger()
                            .withIdentity("trigger1","group1")
                            .withSchedule(CronScheduleBuilder.cronSchedule("0/10 * * * * ?"))
                            .startNow()
                            .build();
                }
                JobDetail jobDetail = JobBuilder.newJob(QuartzJob.class).withIdentity("job1", "group1").build();
                scheduler.scheduleJob(jobDetail, trigger);
                scheduler.start();
            } catch (SchedulerException e) {
          
          
                e.printStackTrace();
            }
        }
    }
    
  • start service

    # 输出结果
    taskname=job1
    执行时间:Mon Nov 07 14:19:02 CST 2022
    A029095-NC1667801934353
    taskname=job1
    执行时间:Mon Nov 07 14:19:12 CST 2022
    

3.8 Quartz Cluster

Quartz cluster is a jobdetail assigned to a node and will not change at this node

demo example:

  • Modify the above integrated code

    /**
     * @author cVzhanshi
     * @create 2022-11-07 11:04
     */
    @Component
    // 监听springboot容器是否启动,启动了就执行定时任务
    public class StartApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
          
          
    
        @Autowired
        private Scheduler scheduler;
    
        @Override
        public void onApplicationEvent(ContextRefreshedEvent event) {
          
          
    
            try {
          
          
                TriggerKey triggerKey = TriggerKey.triggerKey("trigger1", "group1");
                /**
                 * 在调度器中获取指定key的trigger,
                 */
                Trigger trigger = scheduler.getTrigger(triggerKey);
                if(trigger == null){
          
          
                    trigger = TriggerBuilder.newTrigger()
                            .withIdentity("trigger1","group1")
                            .withSchedule(CronScheduleBuilder.cronSchedule("0/10 * * * * ?"))
                            .startNow()
                            .build();
                }
                JobDetail jobDetail = JobBuilder.newJob(QuartzJob.class).withIdentity("job1", "group1").build();
                scheduler.scheduleJob(jobDetail, trigger);
    
    
                TriggerKey triggerKey2 = TriggerKey.triggerKey("trigger1", "group1");
                /**
                 * 在调度器中获取指定key的trigger,
                 */
                Trigger trigger2 = scheduler.getTrigger(triggerKey2);
                if(trigger2 == null){
          
          
                    trigger2 = TriggerBuilder.newTrigger()
                            .withIdentity("trigger2","group2")
                            .withSchedule(CronScheduleBuilder.cronSchedule("0/10 * * * * ?"))
                            .startNow()
                            .build();
                }
                JobDetail jobDetail2 = JobBuilder.newJob(QuartzJob.class).withIdentity("job2", "group2").build();
                scheduler.scheduleJob(jobDetail2, trigger2);
                scheduler.start();
            } catch (SchedulerException e) {
          
          
                e.printStackTrace();
            }
        }
    }
    
  • Start a node first

    # 任务是在同样一个节点执行的
    A029095-NC1667802623784
    taskname=job1
    执行时间:Mon Nov 07 14:30:32 CST 2022
    A029095-NC1667802623784
    taskname=job2
    执行时间:Mon Nov 07 14:30:32 CST 2022
    A029095-NC1667802623784
    taskname=job1
    执行时间:Mon Nov 07 14:30:42 CST 2022
    A029095-NC1667802623784
    taskname=job2
    执行时间:Mon Nov 07 14:30:42 CST 2022
    
  • Change a port number to start a node

    first node

    A029095-NC1667802623784
    taskname=job1
    执行时间:Mon Nov 07 14:31:42 CST 2022
    A029095-NC1667802623784
    taskname=job1
    执行时间:Mon Nov 07 14:31:52 CST 2022
    

    second node

    A029095-NC1667802696272
    taskname=job2
    执行时间:Mon Nov 07 14:31:42 CST 2022
    A029095-NC1667802696272
    taskname=job2
    执行时间:Mon Nov 07 14:31:52 CST 2022
    

v

Guess you like

Origin blog.csdn.net/qq_45408390/article/details/127732350