Spring boot定时任务@Scheduled

1. 前言:

@Scheduled 参数可以接受两种定时的设置,一种是我们常用的 cron="*/6 * * * * ?",一种是 fixedRate = 6000,两种都表示每隔六秒打印一下内容;

@Scheduled(cron = "0 0 0 * * ?") // 每日凌晨
@Scheduled(cron = "0 0/10 * * * ?") // 每10分钟
@Scheduled(cron = "0 30 8-18 ? * MON-FRI")     // 每周一到周五,8:30到18:30

@Scheduled(fixedDelay = 5000) // 上一次执行完毕时间点之后5秒再执行
# fixedDelay:在上一次执行完之后等xxx毫秒(xxx就是fixedDelay = 5000中的5000)再执行,循环下去,上一次执行多久都没关系 ,反正上一次执行完后xxx毫秒我才执行
@Scheduled(fixedDelay = 5000) // 上一次执行完毕时间点之后5秒再执行
# fixedDelay:在上一次执行完之后等xxx毫秒(xxx就是fixedDelay = 5000中的5000)再执行,循环下去,上一次执行多久都没关系 ,反正上一次执行完后xxx毫秒我才执行
@Scheduled(fixedDelay = 5000, initialDelay=8000)
# initialDelay:第一次运行完要等xxx毫秒才能执行,比如:假设原来是14:00:00 开始执行这个,但是设置了initialDelay=8000,那么要在8000毫秒后才能执行这个方法,也就是14:00:08才真正执行
@Scheduled(fixedRate = 5000) // 上一次开始执行时间点之后5秒再执行

fixedRate:这个有点蛋疼…,举个例子:比如:假设有5个执行时间点 间隔是5000毫秒:分别是:
T1:14:00:00
T2:14:00:05
T3:14:00:10
T4:14:00:15
T5:14:00:20

  如果T1执行时间花了4秒,也就是到了14:00:04,那么你会看到14:00:05分就开始执行了T2,很正常,此时T1结束时间和T2开始时间只差1000毫秒,没毛病。

  如果T1执行时间花了8秒,怎么办?这时T1执行完的时间是14:00:08,已经覆盖了T2的时间,T2在14:00:05到14:00:08是等待状态。现在是14:00:08,看起来接着是T3执行,但实际不是,而是立马执行T2,立马执行T2,立马执行T2(T2说:我不管,T1搞我超时了,我无论如何也要执行),这时你会发现T2的执行时间(也就是第2次执行时间 )是:14:00:08,真的是立马。。。如此类推,只要是执行时间被覆盖了的,到它了就立马执行。

  注意:在实际工作中,有用 Linux Crontab 进行基于大数据环境每半小时的程序调度,有时候有些任务运行时间超过半小时甚至卡了一天,后来发现问题后将这些卡住的任务杀掉后还是会不停的向 Yarn 提交任务,即使将 Linux Crontab 停掉或注释掉也会不断的提交任务,这导致集群资源紧张,即使用手动杀死 Yarn 任务的方式也杀了好几个小时,也是觉了。(现在想想就是基于上面的逻辑,前一个间隔搞我超时了后面的可不管这,真的是立马执行,Springboot 可以用 initialDelay 参数解决,但是 Linux Crontab 好像并不支持该参数,目前没有找到解决办法,有知道的大佬可以探讨下)

@Scheduled(initialDelay=8000, fixedRate=5000) // 第一次延迟8秒后执行,之后按fixedRate的规则每5秒执行一次

2. pom包配置:

  pom 包里面只需要引入 springboot starter 包即可:

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.4.0.RELEASE</version>
        <relativePath/>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
    </dependencies>

注意:如果在 Windows 上运行 SpingBoot 上面的 pom.xml 的配置即可,但是要打成 jar 包上传到 Linux 上运行的话还需要添加如下配置:

    <build>
        <finalName>SpringBootTest</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

3. 启动类启用定时:

  在启动类上面加上 @EnableScheduling 即可开启定时

@SpringBootApplication
@EnableScheduling
public class Application {

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

4. 创建定时任务实现类:

  定时任务1:

@Component
public class test1Task {

    private int count=0;

    @Scheduled(cron="*/6 * * * * ?")
    private void process(){
        System.out.println("this is scheduler task runing  "+(count++));
    }

}

  定时任务2:

@Component
public class test2Task {

    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");

    @Scheduled(fixedRate = 6000)
    public void reportCurrentTime() {
        System.out.println("现在时间:" + dateFormat.format(new Date()));
    }

}

  运行结果:

this is scheduler task runing  0
现在时间:14:23:17
this is scheduler task runing  1
现在时间:14:23:23
this is scheduler task runing  2
现在时间:14:23:29
this is scheduler task runing  3
现在时间:14:23:35

5. 补充:

5.1 cron表达式
字段允许值允许的特殊字符备注
0-59, - * /标准实现不支持此字段。linux crontab 命令就不支持,最小的执行时间是一分钟,如果非想实现秒级别的可参考:linux crontab 实现每秒执行
0-59, - * /
小时0-23, - * /
日期1-31, - * ? / L W C需要考虑月的天数,如有28天或30天的,? L W只有部分软件实现了
月份1-12 或者 JAN-DEC, - * /
星期1-7 或者 SUN-SAT, - * ? / L C #? L W只有部分软件实现了。Linux和Spring的允许值为0-7,0和7为周日;Quartz的允许值为1-7,1为周日
年(可选)留空, 1970-2099, - * /标准实现不支持此字段。linux crontab 命令就不支持
  其中每个元素可以是一个值(如6),一个连续区间(9-12),一个间隔时间(8-18/4)(/表示每隔4小时),一个列表(1,3,5),通配符。由于"月份中的日期"和"星期中的日期"这两个元素互斥的,必须要对其中一个设置?
"0 0 10,14,16 * * ?" 每天上午10点,下午2点,4点
"0 0/30 9-17 * * ?" 朝九晚五工作时间内每半小时
"0 0 12 ? * WED" 表示每个星期三中午12点 
"0 0 12 * * ?" 每天中午12点触发 
"0 15 10 ? * *" 每天上午10:15触发 
"0 15 10 * * ?" 每天上午10:15触发 
"0 15 10 * * ? *" 每天上午10:15触发 
"0 15 10 * * ? 2005" 2005年的每天上午10:15触发 
"0 * 14 * * ?" 在每天下午2点到下午2:59期间的每1分钟触发 
"0 0/5 14 * * ?" 在每天下午2点到下午2:55期间的每5分钟触发 
"0 0/5 14,18 * * ?" 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发 
"0 0-5 14 * * ?" 在每天下午2点到下午2:05期间的每1分钟触发 
"0 10,44 14 ? 3 WED" 每年三月的星期三的下午2:10和2:44触发 
"0 15 10 ? * MON-FRI" 周一至周五的上午10:15触发 
"0 15 10 15 * ?" 每月15日上午10:15触发 
"0 15 10 L * ?" 每月最后一日的上午10:15触发 
"0 15 10 ? * 6L" 每月的最后一个星期五上午10:15触发 
"0 15 10 ? * 6L 2002-2005" 2002年至2005年的每月的最后一个星期五上午10:15触发 
"0 15 10 ? * 6#3" 每月的第三个星期五上午10:15触发

有些子表达式能包含一些范围或列表
例如:子表达式(天(星期))可以为 “MON-FRI”,“MON,WED,FRI”,“MON-WED,SAT”

“*”字符代表所有可能的值
因此,“*”在子表达式(月)里表示每个月的含义,“*”在子表达式(天(星期))表示星期的每一天

“/”字符用来指定数值的增量
例如:在子表达式(分钟)里的“0/15”表示从第0分钟开始,每15分钟
在子表达式(分钟)里的“3/20”表示从第3分钟开始,每20分钟(它和“3,23,43”)的含义一样

“?”字符仅被用于天(月)和天(星期)两个子表达式,表示不指定值
当2个子表达式其中之一被指定了值以后,为了避免冲突,需要将另一个子表达式的值设为“?”

“L”字符仅被用于天(月)和天(星期)两个子表达式,它是单词“last”的缩写
但是它在两个子表达式里的含义是不同的。
在天(月)子表达式中,“L”表示一个月的最后一天
在天(星期)自表达式中,“L”表示一个星期的最后一天,也就是SAT
如果在“L”前有具体的内容,它就具有其他的含义了
例如:“6L”表示一个月的倒数第6天,“FRIL”表示一个月的最后一个星期五
注意:在使用“L”参数时,不要指定列表或范围,因为这会导致问题
5.2 Linux Crontab
例子:
    # 每月的最后1天
    0 0 L * * *

    说明:
    Linux
    *    *    *    *    *
    -    -    -    -    -
    |    |    |    |    |
    |    |    |    |    +----- day of week (0 - 7) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
    |    |    |    +---------- month (1 - 12) OR jan,feb,mar,apr ...
    |    |    +--------------- day of month (1 - 31)
    |    +-------------------- hour (0 - 23)
    +------------------------- minute (0 - 59)

补充:常用命令
# 编辑定时任务
crontab -e
# 显示定时任务
crontab -l
# 查看执行的日志
cat /var/log/cron
  • 星号(*):代表所有可能的值,例如 month 字段如果是星号,则表示在满足其它字段的制约条件后每月都执行该命令操作。
  • 逗号(,):可以用逗号隔开的值指定一个列表范围,例如,“1,2,5,7,8,9”
  • 中杠(-):可以用整数之间的中杠表示一个整数范围,例如 “2-6” 表示 “2,3,4,5,6”
  • 正斜线(/):可以用正斜线指定时间的间隔频率,例如 “0-23/2” 表示每两小时执行一次。同时正斜线可以和星号一起使用,例如 */10,如果用在 minute 字段,表示每十分钟执行一次。

  还可以用如下工具验证下次执行的时间crontab执行时间计算 非常的方便

  注意:* */2 * * * 可并不是每两小时执行一次哈,而是每分钟执行一次,正确的写法应该是 0 */2 * * *

5.3 Crontab 命令 2>&1 含义

  先看一个例子:

0 2 * * * /u01/test.sh >/dev/null 2>&1 &

  这句话的意思就是在后台执行这条命令,并将错误输出 2 重定向到标准输出 1,然后将标准输出 1 全部放到 /dev/null 文件,也就是清空。在这里有有几个数字的意思:

  • 0表示 键盘输入
  • 1表示 标准输出
  • 2表示 错误输出

  我们也可以这样写:

0 2 * * * /u01/test.sh 2>/u01/out.file 2>&1 &
# 将 tesh.sh 命令输出重定向到out.file, 即输出内容不打印到屏幕上,而是输出到out.file文件中。
# 2>&1 是将错误输出重定向到标准输出。 然后将标准输入重定向到文件out.file。

  重定向命令,这里给出几个例子:

command > file 把标准输出重定向到文件
command >> file 把标准输出追加到文件
command 1 > file 把标准输出重定向到文件
command 2 > file 把标准错误重定向到文件
command 2 >> file 把标准输出追加到文件
command 2>&1 把command命令标准错误重定向到标准输出
command > file 2>&1 把标准输出和标准错误一起重定向到文件
command >> file 2>&1 把标准输出和标准错误一起追加到文件
command < file 把command命令以file文件作为标准输入
command < file >file2 把command命令以file文件作为标准输入,以file2文件作为标准输出
command <&- 关闭标准输入

参考:
Crontab命令详解

5.4 知识扫盲:cron 与 crond 的区别

  Cron 是 Linux 系统以后台进行模式周期执行命令或指定程序任务的服务软件名。

  Linux 系统启动后,cron 软件便会启动,对应的进程名字叫做 crond,默认是定期(每分钟检查一次)检查系统中是否有需要执行的任务计划,如果有,则按计划进行,好比我们平时用的用钟。

  • crond定时任务默认最快的频率是每分钟执行
  • 若是需要以秒为单位的计划任务,则编写shell脚本更格式,crond不适用了

参考:
Linux学习——定时任务

6. 遇到的坑:

注:有一个需要每个月的最后一天晚上的九点半执行调度任务,本以为这样写 @Scheduled(cron="0 38 21 L * ?") 就OK了,结果报错 Caused by: java.lang.IllegalStateException: Encountered invalid @Scheduled method 'process': For input string: "L"
原因:错误的根源是 Spring 的表达式只是 cron 表达式的子集,它不包含 year 字段,并且不能使用所有特殊字符,比如 L 和 W,大部分的文档有误导。

解决:

@Scheduled(cron = "0 38 21 28-31 * ?")
public void execute() {
	final Calendar c = Calendar.getInstance();
	if (c.get(Calendar.DATE) == c.getActualMaximum(Calendar.DATE)) {
		//是最后一天
		System.out.println("是最后一天,开始执行调度任务");
	}
}
// 或者:
@Scheduled(cron = "0 38 21 28-31 * ?")
public void reptilian() {
    final Calendar calendar = Calendar.getInstance();
    //如果不是最后一天
    if (!(calendar.get(Calendar.DATE) == calendar.getActualMaximum(Calendar.DATE))) {
        return;
    }
    // 执行业务逻辑代码
}
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小强签名设计

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值