Springboot officially supports the task scheduling framework, which is also very lightweight!

Time:2022-6-11

Hello, everyone. I’m the second brother. The application scenarios of scheduled tasks are quite common, for example:

  • Data backup
  • Automatic cancellation if the order is not paid
  • Timed crawling data
  • Timed push information
  • Publish articles regularly
  • Wait (I can’t think of anything. I have to wait. Anyway, as long as I wait, it needs to be timed. How about this round field

In the actual programming project, a function of regularly publishing articles is required. At the beginning, I wanted to use spring task, so I studied it and found that spring task is really simple to use, but it is powerless for complex businesses.

So I turned my attention to quartz, an old and robust open source task scheduling framework.

I remember that I used it when I developed the bulk futures trading platform in 2014. Every morning, I need to count a wave of trading data and generate daily reports. It was used in conjunction with cron expression at that time.

Unfortunately, the platform became stable and new policies came out, which directly destroyed the bulk futures trading. So my chance to get rich was dashed. It’s a pity to think about it, hahaha.

With the passage of time, quartz has been able to seamlessly connect with the spring boot project. Today, let’s practice.

Timer

JDK 1.3 started to support the implementation of a scheduled task. Internally, scheduled tasks are stored through the taskqueue class, which is relatively simple to use, but there are many defects. For example, a timer will start a thread, and the performance will be very poor when there are many tasks. For example, if a TimerTask takes a long time during task execution, it will affect the scheduling of other tasks.

@Slf4j
public class TimerDemo {
    public static void main(String[] args) {
        TimerTask task = new TimerTask() {
            @Override
            public void run() {
                log. Debug ("current time {} thread name {}", datetime.now(),
                        Thread.currentThread().getName());
            }
        };
        log. Debug ("current time {} thread name {}", datetime.now(),
                Thread.currentThread().getName());
        Timer timer = new Timer("TimerDemo");
        timer.schedule(task,1000L);
    }
}

The log after the code runs is as follows:

13:11:45.268 [main] DEBUG top. springtask. Timerdemo - current time 2022-04-27 13:11:45 thread name main
13:11:46.280 [TimerDemo] DEBUG top. springtask. Timerdemo - current time 2022-04-27 13:11:46 thread name timerdemo

ScheduledThreadPoolExecutor

JDK 1.5 starts to provide scheduled tasks. It inherits the ThreadPoolExecutor and implements the scheduledexecutorservice interface, so it supports task execution in concurrent scenarios. At the same time, the defects of timer are optimized. However, since the queue is used to implement the timer, there are operations such as entering and leaving the queue and adjusting the heap, so the timing is not very accurate (picky).

@Slf4j
public class ScheduledThreadPoolExecutorDemo {
    public static void main(String[] args) throws InterruptedException {
        TimerTask task = new TimerTask() {
            @Override
            public void run() {
                log. Debug ("current time {} thread name {}", datetime.now(),
                        Thread.currentThread().getName());
            }
        };

        log. Debug ("current time {} thread name {}", datetime.now(),
                Thread.currentThread().getName());
        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(3);
        executorService.scheduleAtFixedRate(task, 1000L,1000L, TimeUnit.MILLISECONDS);
        Thread.sleep(1000+1000*4);
        executorService.shutdown();
    }
}

The output results are as follows:

14:43:41.740 [main] DEBUG top. springtask. Scheduledthreadpoolexecutordemo - current time 2022-04-27 14:43:41 thread name main
14:43:42.752 [pool-1-thread-1] DEBUG top. springtask. Scheduledthreadpoolexecutordemo - current time 2022-04-27 14:43:42 thread name pool-1-thread-1
14:43:43.748 [pool-1-thread-1] DEBUG top. springtask. Scheduledthreadpoolexecutordemo - current time 2022-04-27 14:43:43 thread name pool-1-thread-1
14:43:44.749 [pool-1-thread-2] DEBUG top. springtask. Scheduledthreadpoolexecutordemo - current time 2022-04-27 14:43:44 thread name pool-1-thread-2
14:43:45.749 [pool-1-thread-2] DEBUG top. springtask. Scheduledthreadpoolexecutordemo - current time 2022-04-27 14:43:45 thread name pool-1-thread-2
14:43:46.749 [pool-1-thread-2] DEBUG top. springtask. Scheduledthreadpoolexecutordemo - current time 2022-04-27 14:43:46 thread name pool-1-thread-2

Spring Task

Spring task is a lightweight scheduled task tool provided by spring, which means that there is no need to add third-party dependencies. It is more convenient and easy to use than other third-party class libraries.

It seems that there is no more nonsense about spring task. Let’s get started directly.

The first step is to create a new configuration class springtaskconfig and add the @enablesscheduling annotation to start the spring task.

@Configuration
@EnableScheduling
public class SpringTaskConfig {
}

Of course, you can add the @enablesscheduling annotation to the main class without creating a new configuration class.

@SpringBootApplication
@EnableScheduling
public class CodingmoreSpringtaskApplication {

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

}

Step 2: create a new scheduled task class crontask, and use the @scheduled annotation to register cron expressions to execute scheduled tasks.

@Slf4j
@Component
public class CronTask {
    @Scheduled(cron = "0/1 * * ? * ?")
    public void cron() {
        log. Info ("scheduled execution, time {}", dateutil.now());
    }
}

Start the server and find that the log will be printed every other second, proving that the cron expression form of spring task has worked.

By default, the thread pool size created by @scheduled is 1. If you want to increase the thread pool size, you can let the springtaskconfig class implement the schedulingconfigurer interface and increase the thread pool size through setpoolsize.

@Configuration
@EnableScheduling
public class SpringTaskConfig implements SchedulingConfigurer {
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();

        threadPoolTaskScheduler.setPoolSize(10);
        threadPoolTaskScheduler.setThreadNamePrefix("my-scheduled-task-pool-");
        threadPoolTaskScheduler.initialize();

        taskRegistrar.setTaskScheduler(threadPoolTaskScheduler);
    }
}

After the service hot deployment is completed, you will see the following information on the console:

It can be confirmed that the customized thread pool size has taken effect. Some tasks use thread led-task-pool-3, and some use thread led-task-pool-7. After running for a long time, it can be found that led-task-pool-1 to led-task-pool-10 are available.

In addition to supporting cron expressions, spring task has three usages: fixedrate (fixed rate execution), fixeddelay (fixed delay execution) and initialdelay (initial delay).

/**
 *Fixedrate: fixed rate execution. Every 5 seconds.
 */
@Scheduled(fixedRate = 5000)
public void reportCurrentTimeWithFixedRate() {
    log.info("Current Thread : {}", Thread.currentThread().getName());
    log.info("Fixed Rate Task : The time is now {}", DateUtil.now());
}

/**
 *Fixeddelay: fixed delay execution. Only 2 seconds after the last successful call.
 */
@Scheduled(fixedDelay = 2000)
public void reportCurrentTimeWithFixedDelay() {
    try {
        TimeUnit.SECONDS.sleep(3);
        log.info("Fixed Delay Task : The time is now {}",DateUtil.now());
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

/**
 *Initialdelay: initial delay. The first execution of the task will be delayed by 5 seconds, and then will be executed at a fixed interval of 5 seconds.
 */
@Scheduled(initialDelay = 5000, fixedRate = 5000)
public void reportCurrentTimeWithInitialDelay() {
    log.info("Fixed Rate Task with Initial Delay : The time is now {}", DateUtil.now());
}

However, the fixedrate has a pit. If the timer of a method sets a fixed rate of execution every 5 seconds, the method now needs to execute the following four tasks. The time-consuming of the four tasks is: 6S, 6S, 2S, 3S. How will the tasks be executed (in a single thread environment)?

2022-04-27 15:25:52.400  INFO 4343 --- [led-task-pool-1] c.codingmore.component.PublishPostTask   : Fixed Rate Task : The time is now 2022-04-27 15:25:52
2022-04-27 15:25:58.401  INFO 4343 --- [led-task-pool-1] c.codingmore.component.PublishPostTask   : Fixed Rate Task : The time is now 2022-04-27 15:25:58
2022-04-27 15:26:00.407  INFO 4343 --- [led-task-pool-1] c.codingmore.component.PublishPostTask   : Fixed Rate Task : The time is now 2022-04-27 15:26:00
2022-04-27 15:26:04.318  INFO 4343 --- [led-task-pool-1] c.codingmore.component.PublishPostTask   : Fixed Rate Task : The time is now 2022-04-27 15:26:04

The relative starting time of the first task is the 0th second. However, because it has been executed for 6 seconds, the task that should have been executed in the 5th second is delayed until the 6th second. The third task is delayed for 12 seconds. It should have been executed in the 10th second. The third task is not delayed and will be executed after 15 seconds.

What would happen if we used the @enableasync annotation to start a multithreaded environment?

2022-04-27 15:33:01.385  INFO 4421 --- [led-task-pool-1] c.codingmore.component.PublishPostTask   : Fixed Rate Task : The time is now 2022-04-27 15:33:01
2022-04-27 15:33:07.390  INFO 4421 --- [led-task-pool-1] c.codingmore.component.PublishPostTask   : Fixed Rate Task : The time is now 2022-04-27 15:33:07
2022-04-27 15:33:09.391  INFO 4421 --- [led-task-pool-1] c.codingmore.component.PublishPostTask   : Fixed Rate Task : The time is now 2022-04-27 15:33:09
2022-04-27 15:33:13.295  INFO 4421 --- [led-task-pool-1] c.codingmore.component.PublishPostTask   : Fixed Rate Task : The time is now 2022-04-27 15:33:13

About cron expressions

By the way, we will popularize cron expressions, which are often encountered in scheduled tasks. The word cron comes from the Greek word Chronos, which originally means time.

Cron expression is a string with time meaning, separated by 5 spaces and divided into 6 time elements. A few examples will make it clear at a glance.

Example explain
0 15 10 ? * * Perform tasks at 10:15 a.m. every day
0 0 10,14,16 * * ? Perform tasks at 10:00, 14:00 and 16:00 every day
0 0 12 ? * 3 Every Wednesday at 12 noon
0 15 10 15 * ? Perform the task at 10:15 a.m. on the 15th day of each month

The syntax format of cron can be summarized as follows:

Seconds Minutes Hours DayofMonth Month DayofWeek

The value range of each time element and the special characters that can appear are as follows.

Time element Value range Special characters that can appear
second [0,59] *,-/
minute [0,59] *,-/
hour [0,59] *,-/
date [0,31] *,-/?LW
month [1,12] *,-/
week [1,7] *,-/?L#

The meanings and examples of special characters are shown below.

Special characters meaning Example
* All possible values It is easy to understand that the month field refers to each month and the week day in the week field
, Enumerated values Well understood, in the hour domain10,14,16, which means these hours are optional
- Range Very easy to understand. In the minute field10-19, which means every other minute for 10-19 minutes
/ Specify the increment of the value Very easy to understand. In the minute field0/15, which means every 15 minutes
? Do not specify a value It is easy to understand that if the date field specifies the week field, the value cannot be specified, and vice versa, because the date field and the week field belong to a conflicting relationship
L First letter of the word last It is easy to understand. The date field and the week field are supported. They represent the last day of the month or the last day of the week
W Weekdays other than weekends Very well understood. Only the date field supports
# The week ordinal of each month It’s easy to understand. Only the week domain supports it,4#2Indicates the second Thursday of a month

About quartz

Quartz is a powerful open source task scheduling framework. There are 5k+ stars on GitHub. From stand-alone applications to distributed applications, quartz can be integrated.

Before using quartz, let’s clarify four core concepts:

  • Job: task, the specific content to be executed.
  • JobDetail: task details. Job is the content to be executed, and it also contains the strategy and scheme for task scheduling.
  • Trigger: trigger. You can specify the time of task execution through cron expression.
  • Scheduler: a scheduler that can register multiple jobdetails and triggers to schedule, pause, and delete tasks.

Integrate quartz

There are two ways for quartz to store tasks. One is to use memory, and the other is to use database. Memory is lost after the program restarts, so we use the database method to persist the task this time.

Step 1: in pom Add the starter of quartz to the XML file.

org.springframework.boot
    spring-boot-starter-quartz
    2.6.7

Step 2: in application YML adds quartz related configurations. See the notes directly for configuration instructions.

spring:
  quartz:
    Job store type: JDBC \s \y default to the memory mode. Here we use the database mode
    Wait for jobs to complete on shutdown: true \
    Overwrite existing jobs: true \
    jdbc:
      Initialize schema: whether never \
    Properties: \quartz native configuration
      org:
        quartz:
          scheduler:
            Instancename: scheduler \scheduler instance name
            Instanceid: auto \; scheduler instance ID is automatically generated
          #Jobstore related configurations
          jobStore:
            class: org. quartz. impl. jdbcjobstore. Jobstoretx \jobstore implementation class
            driverDelegateClass: org. quartz. impl. jdbcjobstore. Stdjdbcdelegate \\use a fully JDBC compatible driver
            tablePrefix: QRTZ_ #  Quartz table prefix
            Useproperties: false \whether to convert properties in jobdatamap to string storage
          #Thread pool related configuration
          threadPool:
            Threadcount: 25 \. The default is 10.
            Threadpriority: 5 \
            class: org. quartz. simpl. SimpleThreadPool \\n specifies the thread pool implementation class, which provides a fixed size thread pool for the scheduler

By default, quartz uses memory to store tasks. For persistence, we change it to JDBC and specifyspring.quartz.jdbc.initialize-schema=neverSo that we can create the data table manually. Because the other two options of this value, always and embedded, do not meet our requirements:

  • Always: initialize every time
  • Embedded: only initialize embedded databases, such as H2 and hsql

Where can I find the SQL statement to manually create a data table?

GitHub address:https://github.com/quartz-scheduler/quartz/tree/master/quartz-core/src/main/resources/org/quartz/impl/jdbcjobstore

In order to make it easy for my friends to download, I put it in the source code of this tutorial:

If you use IntelliJ idea ultimate, you will be prompted to specify the data source when opening the SQL file for the first time. In the above figure, I have configured the local MySQL database. After successful import, the following data tables can be viewed in the database:

The core tables of quartz database are as follows:

Table Name Description
QRTZ_CALENDARS Store calendar information for quartz
QRTZ_CRON_TRIGGERS Store crontrigger, including cron expression and time zone information
QRTZ_FIRED_TRIGGERS Stores status information related to triggered triggers and execution information of associated jobs
QRTZ_PAUSED_TRIGGER_GRPS Store information for a paused trigger group
QRTZ_SCHEDULER_STATE Store a small amount of status information about the scheduler and other scheduler instances
QRTZ_LOCKS Stores information about pessimistic locks for programs
QRTZ_JOB_DETAILS Store the details of each configured job
QRTZ_JOB_LISTENERS Stores information about the configured joblistener
QRTZ_SIMPLE_TRIGGERS Store a simple trigger, including the number of repetitions, intervals, and the number of times it has been touched
QRTZ_BLOG_TRIGGERS Trigger is stored as blob type
QRTZ_TRIGGER_LISTENERS Stores information about the configured triggerlistener
QRTZ_TRIGGERS Store information about configured triggers

The rest is the configuration of the scheduler, jobstore and ThreadPool for quartz.

Step 3: create the ischeduleservice interface for task scheduling, and define three methods: scheduling tasks through cron expression, scheduling tasks at a specified time, and canceling tasks.

public interface IScheduleService {
    /**
     *Scheduling tasks through cron expressions
     */
    String scheduleJob(Class extends Job> jobBeanClass, String cron, String data);

    /**
     *Specify a time to schedule tasks
     */
    String scheduleFixTimeJob(Class extends Job> jobBeanClass, Date startTime, String data);

    /**
     *Cancel scheduled task
     */
    Boolean cancelScheduleJob(String jobName);
}

Step 4: create a task scheduling business implementation class, scheduleserviceimpl, and implement the corresponding methods through the APIs of scheduler, crontrigger, and JobDetail.

@Slf4j
@Service
public class ScheduleServiceImpl implements IScheduleService {
    private String defaultGroup = "default_group";

    @Autowired
    private Scheduler scheduler;
    @Override
    public String scheduleJob(Class extends Job> jobBeanClass, String cron, String data) {
        String jobName = UUID.fastUUID().toString();
        JobDetail jobDetail = JobBuilder.newJob(jobBeanClass)
                .withIdentity(jobName, defaultGroup)
                .usingJobData("data", data)
                .build();
        //Create trigger and specify task execution time
        CronTrigger cronTrigger = TriggerBuilder.newTrigger()
                .withIdentity(jobName, defaultGroup)
                .withSchedule(CronScheduleBuilder.cronSchedule(cron))
                .build();
        //Task scheduling by scheduler
        try {
            scheduler.scheduleJob(jobDetail, cronTrigger);
        } catch (SchedulerException e) {
            log. Error ("task scheduling execution failed {}", e.getmessage());
        }
        return jobName;
    }

    @Override
    public String scheduleFixTimeJob(Class extends Job> jobBeanClass, Date startTime, String data) {
        //Date to cron expression
        String startCron = String.format("%d %d %d %d %d ? %d",
                DateUtil.second(startTime),
                DateUtil.minute(startTime),
                DateUtil.hour(startTime, true),
                DateUtil.dayOfMonth(startTime),
                DateUtil.month(startTime) + 1,
                DateUtil.year(startTime));
        return scheduleJob(jobBeanClass, startCron, data);
    }

    @Override
    public Boolean cancelScheduleJob(String jobName) {
        boolean success = false;
        try {
            //Pause trigger
            scheduler.pauseTrigger(new TriggerKey(jobName, defaultGroup));
            //Remove task from trigger
            scheduler.unscheduleJob(new TriggerKey(jobName, defaultGroup));
            //Delete task
            scheduler.deleteJob(new JobKey(jobName, defaultGroup));
            success = true;
        } catch (SchedulerException e) {
            log. Error ("task cancellation failed {}", e.getmessage());
        }
        return success;
    }
}

Step 5: define the task to be executed, inherit the quartzjobbean class, and implement
Executeinternal method, where only a task of publishing articles on a regular basis is defined.

@Slf4j
@Component
public class PublishPostJob extends QuartzJobBean {
    @Autowired
    private IScheduleService scheduleService;
    @Autowired
    private IPostsService postsService;

    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        Trigger trigger = jobExecutionContext.getTrigger();
        JobDetail jobDetail = jobExecutionContext.getJobDetail();
        JobDataMap jobDataMap = jobDetail.getJobDataMap();
        Long data = jobDataMap.getLong("data");
        log. Debug ("regularly publish articles: {}", data);

        //After obtaining the ID of the article, obtain the article, update the article to the published status and the published time
        boolean success = postsService.updatePostByScheduler(data);

        //Delete triggers and tasks when complete
        if (success) {
            log. Debug ("the scheduled task is successfully executed, and the scheduled task is cleared");
            scheduleService.cancelScheduleJob(trigger.getKey().getName());
        }
    }
}

Step 6: add the task scheduling method for scheduled publishing to the postsserviceimpl interface for publishing articles.

@Service
public class PostsServiceImpl extends ServiceImpl implements IPostsService {

    private void handleScheduledAfter(Posts posts) {
        //The article has been saved as a draft and the article ID has been obtained
        //Call scheduled task
        String jobName = scheduleService.scheduleFixTimeJob(PublishPostJob.class, posts.getPostDate(), posts.getPostsId().toString());
        LOGGER. Debug ("scheduled task {} starts to execute", jobname);
    }

}

OK, let’s start the service now, test it through swagger, and pay attention to setting the regular publishing time of articles.

View quartz data table qrtz_ cron_ Triggers, the task has been added.

qrtz_ job_ You can also view specific task details in the details table.

When the time for regular publishing of articles is up, you can also see the quartz execution log in the log.

View the quartz data table qrtz again_ cron_ Triggers and qrtz_ job_ When you click details, you will also find that the scheduled task has been cleared.

On the whole, the integration of spring boot with quartz is very smooth. It has fewer configurations and clear steps. It is more powerful than spring task. It can be both memory oriented and persistent. Therefore, you can try it when you encounter a scheduled task.

The complete function has been realized in the actual combat project of programming meow. You can import the programming meow to the local for a try.

Business arrangement

Simply sort out the business of regularly publishing articles by programming meow.

1) Users can choose to publish regularly when publishing articles. If you choose to publish regularly, you need to set the time for publishing regularly. It is temporarily stipulated that the time can be scheduled after at least ten minutes.

2) When the management end user selects scheduled publishing, the article status should first be set to draft status when saving the article, which is invisible to the front-end user.

3) When saving the article, notify quartz that I have a task that you need to perform at a specified time.

scheduleService.scheduleFixTimeJob(PublishPostJob.class, posts.getPostDate(), posts.getPostsId().toString());

4) After receiving this notification, quartz will write a task in the database. The specific task is to change the status of the article from draft to release at the specified time. At this time, the front-end users can see the article.

//After obtaining the ID of the article, obtain the article, update the article to the published status and the published time
boolean success = postsService.updatePostByScheduler(data);

At the same time, clear the task.

//Pause trigger
scheduler.pauseTrigger(new TriggerKey(jobName, defaultGroup));
//Remove task from trigger
scheduler.unscheduleJob(new TriggerKey(jobName, defaultGroup));
//Delete task
scheduler.deleteJob(new JobKey(jobName, defaultGroup));

The whole process is finished. How does quartz implement regular publishing of articles? In fact, it is also through cron expression.

CronTrigger cronTrigger = TriggerBuilder.newTrigger()
                .withIdentity(jobName, defaultGroup)
                .withSchedule(CronScheduleBuilder.cronSchedule(cron))
                .build();

That is, when we pass in a specified time, we calculate the cron expression by calculation.

String startCron = String.format("%d %d %d %d %d ? %d",
                DateUtil.second(startTime),
                DateUtil.minute(startTime),
                DateUtil.hour(startTime, true),
                DateUtil.dayOfMonth(startTime),
                DateUtil.month(startTime) + 1,
                DateUtil.year(startTime));

In quartz, there are two types of threads: scheduler scheduling threads and task execution threads.

  • Task execution thread: Quartz does not process the user’s job in the main thread (quartzschedulerthread). Quartz delegates the responsibility of thread management to ThreadPool. The general setting uses SimpleThreadPool. SimpleThreadPool creates a certain number of workerthread instances to enable jobs to be processed in threads. Workerthread is an internal class defined in the SimpleThreadPool class, which is essentially a thread.
  • Quartzschedulerthread schedule main thread: create a quartzschedulerthread instance when the quartzscheduler is created.

Source code path

This article has been included in the open source column “Java programmers’ advanced path” of StarMark 2.4k+ on GitHub. It is said that every excellent java programmer likes her. She is humorous and easy to understand. The content includes Java foundation, Java Concurrent Programming, Java virtual machine, Java enterprise development, Java interview and other core knowledge points. Learn Java and find the way for Java programmers to advance😄。

https://github.com/itwanger/toBeBetterJavaer

Star this warehouse means that you have the potential to become an excellent Java Engineer. The open source warehouse has recently been listed on the GitHub trending list, which seems to be highly recognized by everyone!

在这里插入图片描述

Nothing makes me stay – except for the purpose. Even if there are roses, green shade and quiet harbor on the shore, I am not tied to the boat

Recommended Today

Cloud Native Virtualization: Building Edge Computing Instances with Kubevirt

With the popularity of Kubernetes, more and more businesses are running on containers, but there are still some business forms that are more suitable for running on virtual machines. How to control virtual machines and containers at the same time has gradually become a mainstream demand in the cloud-native era. Kubevirt gave came up with […]