Spring Task's pitfalls when deploying projects in a cluster

1. Execute scheduled tasks through Spring Task

1. Create a scheduled task

We want to implement a task of printing information every 5 seconds in the following code.

package com.qfedu.day85.task;

import org.redisson.api.RedissonClient;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.Date;

@Component
public class PrintTask {

    // 任务默认同步执行的
    @Async  // 表示异步执行的注解
    // 每分钟的第5秒触发定时任务
    @Scheduled(cron = "5 * * * * ?")
    public void printInfo() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SSS");
        System.out.println(sdf.format(new Date()) + ": print info test");
    }
}

2. Start multiple instances of the same project
First, we can add a running instance configuration through "Edit Configurations".


Then start multiple instances of the project, as shown in the following figure:

3. View the execution results
Then we can view the execution results of the above code, as shown below:


By comparing the execution results, we will find that two instances execute the task "simultaneously". In actual development, our projects often deploy clusters, but if we use Spring Task to execute scheduled tasks, we only need one instance to execute the tasks.
So, what do we need to do if we don't want to have multiple instances executing tasks at the same time?
Generally, we can use distributed locks to lock tasks to prevent multiple instances from executing tasks at the same time.
But is it really possible to do this?

2. Use distributed locks in scheduled tasks

1. Lock the task through Redisson.
The code after locking is as follows:

package com.qfedu.day85.task;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;

@Component
public class PrintTask {
    // 注入RedissonClient 对象
    @Resource
    private RedissonClient redissonClient;

    @Async
    @Scheduled(cron = "0/5 * * * * ?")
    public void printInfo2() {
        // 获取锁对象,参数表示redis中key值
        RLock taskLock = redissonClient.getLock("taskLock");

        try {
            // 尝试加锁,
            // 第一个参数,表示加锁的重试时间,如果锁被占用,在指定时间内尝试加锁
            // 第二个参数,表示锁过期时间
            // 返回true/false
            // 本例中,第一个参数是0,表示如果没有加上锁,直接返回false
            if (taskLock.tryLock(0, 10, TimeUnit.SECONDS)) {
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
                System.out.println(sdf.format(new Date()) + ": print info test");
                // Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (taskLock.isLocked()) {
                // 判断是否当前线程拥有该锁
                if (taskLock.isHeldByCurrentThread()) {
                    // 释放锁
                    taskLock.unlock();
                }
            }
        }
    }
}

In this example, the use of Redisson is not discussed.

2. Execution result


Through the execution results in the above figure, we found that although distributed locks are used, at some point in time, such as "17:38:15", two instances still "simultaneously" executed the same task.
Don't distributed locks work? Of course not, the truth is like this...
Our mission was executed too fast! ! !
In the instance of Day85Application2, the task is locked and executed at 17:38:15.008, and the task is executed quickly. In the instance of Day85Application, the task was executed at 17:38:15.029. When the lock was added, it was found that the lock was not occupied, so the task was executed again.
Through the above analysis, we will find that if the execution time of the task is very short, multiple instances can lock and execute the task multiple times within one second, so there is a phenomenon that tasks are executed "simultaneously" within the same second.
 


3. Solution
To solve the above problem, we can change the code slightly:
 


The modification scheme is shown in the code above, and the task is forced to sleep for 1 second. Execute it again, and you will find that the phenomenon of "simultaneous" execution of tasks within the same second will no longer occur.
However, this approach is really degrading. If the project requires cluster deployment, it is best to use a third-party distributed task framework, such as quartz, xxl-job, etc., when using scheduled tasks.
The above is just the opinion of a family. If there is anything inappropriate, veterans are welcome to leave a message in the comment area or send me a private message.

Video tutorial portal: from entry to mastery of Spring tutorial, a set of spring (source code practical explanation)

 

Guess you like

Origin blog.csdn.net/GUDUzhongliang/article/details/131845617