Spring Boot task scheduling

Posted by ElArZ on Thu, 09 Dec 2021 17:47:57 +0100

Application scenario

Students with loans will receive reminder messages to repay the loans every month, and punch in reminder before going to work and class every day, etc. Similar to this regular repetition function, we can use task scheduling to realize it.

Task scheduling framework

Scheduling frameworkexplain
TimerJDK's own class java util. Timer, the simplest method to realize task scheduling. The advantage of timer is that it is simple and easy to use. However, since all tasks are scheduled by the same thread, all tasks are executed serially. Only one task can be executed at the same time. The delay or exception of the previous task will affect the subsequent tasks.
ScheduledExecutorIn view of the defects of Timer, Java 5 has launched a scheduled executor based on thread pool design. The design idea is that each scheduled task will be executed by a thread in the thread pool, so the tasks are executed concurrently and will not be disturbed. It should be noted that only when the execution time of the task comes, the ScheduledExecutor will really start a thread, and the ScheduledExecutor will poll the status of the task for the rest of the time.
Spring Boot @ Scheduled annotationEasy to use, almost no coding. Meet common task scheduling requirements. Very practical. But it is not suitable for distributed cluster environment
quartzIt should be the most used task scheduling framework at present. It has rich functions and supports distributed clusters. However, it does not support more advanced functions such as task fragmentation
ElasticJobElasticJob is a distributed scheduling solution consisting of two independent projects, ElasticJob lite and ElasticJob cloud. ElasticJob Lite is a lightweight, decentralized solution that provides distributed task fragmentation services; Elastic job cloud uses Mesos to manage and isolate resources. It uses a unified job API for each project. Developers only need code once and can deploy it at will.
XXL-JOBChinese open source distributed task scheduling framework, official Chinese documents, easy to learn. It also supports task segmentation, etc. Some big Internet companies such as public comment are also using it.

There are other emerging task scheduling frameworks such as LTS. Personally, I think it's OK to choose the appropriate framework when using it. If your project can't use so many functions, or it's a single application, or it doesn't need fragmentation at all. Because the more powerful the function is, it will not be so easy to use and implement. It depends on whether the personnel composition of your project team and the development cost of the project support you to choose a more powerful architecture. Otherwise, your project is likely to fall into the risk of passive adjustment or failure to deliver on time.

Spring Boot @ Scheduled annotation

In a single application, we can use the @ Scheduled annotation of Spring Boot to realize simple task scheduling.

First, you need to use the @ enableshcheduling annotation to enable task scheduling

@RestController
@SpringBootApplication(scanBasePackages = "com.yyoo")
// Turn on task scheduling
@EnableScheduling
public class Appliction {

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

}

Use @ Scheduled to annotate the method of task execution

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class TaskDemo1 {

    @Scheduled(cron = "1 * * * * ?")
    public void doTask1(){
        // It's actually once a minute
        System.out.println("Execute once every second of every minute");
    }

    @Scheduled(cron = "1/1 * * * * ?")
    public void doTask2(){
        // The numerator 1 indicates execution from the first second, and the denominator indicates execution every other second.
        // But molecules are often written as *, such as * / 10 every 10 seconds
        // If it needs to be performed every 5 seconds, it is divided by 5
        System.out.println("Once per second");
    }

}

cron expressions

It is inevitable to write cron expressions for task scheduling. All scheduling frameworks, including linux servers, have cron expressions to represent the time and frequency of task scheduling. (Note: the cron expressions supported by various frameworks or linux servers may be slightly different.)
Let's follow the cron expression from left to right

secondbranchTimedaymonthweekyear
0~590~590~231~311~121 ~ 7 1 is Sunday and 7 is Saturday1970~2099

Day and week cannot express specific values at the same time, because No. 1 is not necessarily week 1, so the last week of our expression is used?. Similarly, if we want to express the specific day of the week, then the day here can only be?.

We usually don't use years very much, so there are only 6 digits in our example, not the last year.

Symbols in cron expressions

Symbolexplain
*Represents any time
?Use on days or weeks
-Indicates the range. For example, use 1-15 in the sky, indicating No. 1 to 15.
/Indicates the interval. For example, if 0 / 2 is used in the sky, it means to execute every 2 days
,Indicates enumeration. For example, using 10,30,45,55 on minutes indicates that it is executed at 10 minutes, 30 minutes, 45 minutes and 55 minutes of each hour
LIndicates the last day or week
#Indicates the day of the week, which is used on the week, for example: 7#3 indicates the third Saturday

@Other uses of Scheduled

//    @Scheduled(fixedDelay = 5000)
    // The default unit of time is milliseconds, which can be modified through the timeUnit property
    @Scheduled(fixedDelay = 5,timeUnit = TimeUnit.SECONDS)
    public void doTask3() throws InterruptedException {
        Thread.sleep(10000);// Hibernate for 10 seconds to verify (meaning that it will not be executed again until at least 15 seconds later)
        // Executed 5 seconds after last call
        System.out.println("Executed 5 seconds after last call"+Thread.currentThread().getName());
    }

    @Scheduled(fixedRate = 5,timeUnit = TimeUnit.SECONDS)
    public void doTask4() throws InterruptedException {
        // Execute every 5 seconds, regardless of whether the last execution was successful or not
        System.out.println("Every 5 seconds"+Thread.currentThread().getName());
        Thread.sleep(10000);
    }

After careful students run the program, they will find that we have defined four tasks, which seems to be wrong. For example, the tasks executed per second have not been executed for a long time. why?

If we type the thread name in each task method, you will find that the printed value is: scheduling-1. It indicates that all our tasks are executed by the same thread. This led to the serialization of our mission. At this point, we can solve it through the @ Async annotation.

Configure ThreadPoolTaskScheduler to resolve serialization

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;

@Configuration
public class ScheduleConfig {

    @Bean
    public TaskScheduler taskScheduler(){
        ThreadPoolTaskScheduler tpts = new ThreadPoolTaskScheduler();

        tpts.setPoolSize(5);// Number of thread pools
        tpts.setThreadNamePrefix("my-Task-");// Thread name prefix

        return tpts;
    }

}

@Async asynchronous execution task scheduling

Like @ Scheduled, @ Async needs an annotation to open. Add the @ EnableAsync annotation on the Application class to turn on @ Async annotation support. Finally, our example code is as follows:

import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

@Component
public class TaskDemo1 {

    @Scheduled(cron = "1 * * * * ?")
    @Async
    public void doTask1(){
        // It's actually once a minute
        System.out.println("Execute once every second of every minute"+Thread.currentThread().getName());
    }

    @Scheduled(cron = "1/1 * * * * ?")
    @Async
    public void doTask2(){
        // The numerator 1 indicates execution from the first second, and the denominator indicates execution every other second.
        // But molecules are often written as *, such as * / 10 every 10 seconds
        // If it needs to be performed every 5 seconds, it is divided by 5
        System.out.println("Once per second"+Thread.currentThread().getName());
    }

//    @Scheduled(fixedDelay = 5000)
    // The default unit of time is milliseconds, which can be modified through the timeUnit property
    @Scheduled(fixedDelay = 5,timeUnit = TimeUnit.SECONDS)
    @Async
    public void doTask3() throws InterruptedException {
        Thread.sleep(10000);// Hibernate for 10 seconds to verify (meaning that it will not be executed again until at least 15 seconds later)
        // Executed 5 seconds after last call
        System.out.println("Executed 5 seconds after last call"+Thread.currentThread().getName());
    }

    @Scheduled(fixedRate = 5,timeUnit = TimeUnit.SECONDS)
    @Async
    public void doTask4() throws InterruptedException {
        // Execute every 5 seconds, regardless of whether the last execution was successful or not
        System.out.println("Every 5 seconds"+Thread.currentThread().getName());
        Thread.sleep(10000);
    }

}

After execution, the print result is correct, and the thread name is different.

@Async annotated methods can also define asynchronous Future returns

@Async
Future<String> returnSomething(int i) {
    // this will be run asynchronously
}

After using @ Async to call asynchronously, the ThreadPoolTaskScheduler we configured earlier has no effect. Because you will see the 6th, 7th or even 8th thread appear. It is recommended to use @ Async to handle asynchronous task scheduling.

Previous: Spring Boot logback log
Next: to be continued

Topics: Spring Boot async