Scheduled 定时任务的问题
定时任务,是很多业务系统都需要用到的东西,在Springboot中,我们通常用@Scheduled注解去定义一个单体应用定时任务。然而在微服务的场景下还使用这个东西,定时任务就会重复执行了。就比如我向下面这样定义了一个定时任务(每分钟的0,5,10…秒都会执行),然后修改端口启动两次应用,不用猜,自然是两个进程都重复执行了。
@Scheduled(cron = "*/5 * * * * ?")
public void MySchedule2(){
try{
logger.info("[DistributedScheduled service port:{}] 执行定时任务 ....",port);
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
在某些业务场景下定时任务重复执行,是会有问题的。比如说一个信用卡系统,每天早上八点扫描自己的欠钱的用户来发催还款的短信。大家可以想想,如果你是一个信用卡用户,它每次催缴短信都是三连发,是不是烦的一批(至少我是这样)。当然,我们可以给业务表加一些字段来标志它是否推送过短信解决这个问题,但是这么做,定时任务的逻辑侵入业务了,增加了业务表的复杂性,这种事情当然是不想干的。
有没有什么好办法呢?
我选择的方案是zookeeper给定时任务枷锁,来保证同一时刻只有一个服务会触发定时任务。每个服务在执行定时任务之前都要去判断自己能不能执行,具体而言,就是用定时任务名去zookeeper加锁,加锁成功才能执行。时序图如下:
实现代码
下面部分主要是代码,首先是启动类,注意要加上EnableScheduling 开启定时任务,否则不会执行:
@SpringBootApplication
@EnableScheduling // 2.开启定时任务
public class ApplicationBoot {
public static void main(String[] args) {
new SpringApplication(ApplicationBoot.class).run(args);
}
}
下面是定义了一个包含定时任务信息的注解,主要可以拿来获取定时任务名。
import org.springframework.core.annotation.AliasFor;
import org.springframework.scheduling.annotation.Scheduled;
import java.lang.annotation.*;
@Target({
ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface