文章目录
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;
}
// 执行业务逻辑代码
}