定时任务总结(Linux crontab / Spring Task / Quartz)
——by zhengkai
什么时候需要使用定时任务?比如你遇到这样的问题:
想每月25号,信用卡自动还款?
想每年情人节自己给当年暗恋的女神发一封匿名贺卡?。
想每隔1小时,备份一下自己最爱的精彩影片、学习笔记到自己的云盘中 ?。
这些问题总结起来就是:在某一个有规律的(周期性的)时间点干某件事。并且时间的触发的条件可以非常复杂(比如每月最后一个工作日的17:50),复杂到需要一个专门的框架来干这个事。本次主要介绍Linux中的定时任务、java原生的定时任务、以及两个任务调度框架:Spring Task和Quartz。
1.Linux中的定时任务
1.1 crond简介
crond是linux下用来周期性的执行某种任务或等待处理某些事件的一个守护进程,与windows下的计划任务类似,当安装完成操作系统后,默认会安装此服务工具, crond进程每分钟会定期检查是否有要执行的任务,如果有要执行的任务,则自动执行该任务。Linux系统上面原本就有非常多的计划性工作,因此这个系统服务是默认启动的。另外, 由于使用者自己也可以设置计划任务,所以,Linux 系统也提供了使用者控制计划任务的命令:crontab命令。
1.1.1 系统任务调度
系统任务调度:系统周期性所要执行的工作,比如写缓存数据到硬盘、日志清理等。在/etc目录下有一个crontab文件,这个就是系统任务调度的配置文件。
cat /etc/crontab |
1.1.2 用户任务调度
用户定期要执行的工作,比如用户数据备份、定时邮件提醒等。用户可以使用 crontab 工具来定制自己的计划任务。所有用户定义的crontab 文件都被保存在 /var/spool/cron目录中。其文件名与用户名一致。即:/var/spool/cron/[用户名]。
用户所建立的crontab文件中,每一行都代表一项任务,每行的每个字段代表一项设置,它的格式共分为六个字段,前五段是时间设定段,第六段是要执行的命令段,格式如下:
minute hour day month week command
其中:
minute: 表示分钟,可以是从0到59之间的任何整数。
hour:表示小时,可以是从0到23之间的任何整数。
day:表示日期,可以是从1到31之间的任何整数。
month:表示月份,可以是从1到12之间的任何整数。
week:表示星期几,可以是从0到7之间的任何整数,这里的0或7代表星期日。
command:要执行的命令,可以是系统命令,也可以是自己编写的脚本文件。
在以上各个字段中,还可以使用以下特殊字符:
* : 表示任意的时刻;如小时位 * 则表示每个小时
n : 表示特定的时刻;如小时位 5 就表示5时
n,m : 表示特定的几个时刻;如小时位 1,10 就表示1时和10时
n-m : 表示一个时间段;如小时位 1-5 就表示1到5点
*/n : 表示每隔多少个时间单位执行一次;如小时位 */1 就表示每隔1个小时执行一次命令,也可以写成 1-23/1。
1.1.3 crontab配置实例
* 1 * * * /opt/script/backup.sh //从1:0到1:59 每隔1分钟 执行 15 05 * * * /opt/script/backup.sh //05:15 执行 */10 * * * * /opt/script/backup.sh //每隔10分 执行 0 17 * * 1 /opt/script/backup.sh //每周一的 17:00 执行 2 8-20/3 * * * /opt/script/backup.sh //8:02,11:02,14:02,17:02,20:02 执行
30 21 * * * /etc/init.d/nginx restart //每晚的21:30重启 nginx。 45 4 1,10,22 * * /etc/init.d/nginx restart //每月1、 10、22日的4 : 45重启nginx。 10 1 * * 6,0 /etc/init.d/nginx restart //每周六、周日的1 : 10重启nginx。 0,30 18-23 * * * /etc/init.d/nginx restart //每天18 : 00至23 : 00之间每隔30分钟重启nginx。 0 23 * * 6 /etc/init.d/nginx restart //每星期六的11 : 00 pm重启nginx。 * */1 * * * /etc/init.d/nginx restart //每一小时重启nginx * 23-7/1 * * * /etc/init.d/nginx restart //晚上11点到早上7点之间,每 隔一小时重启nginx 0 11 4 * mon-wed /etc/init.d/nginx restart //每月的4号与每周一到周三 的11点重启nginx 0 4 1 jan * /etc/init.d/nginx restart //一月一号的4点重启nginx */30 * * * * /usr/sbin/ntpdate 210.72.145.20 //每半小时同步一下时间 |
1.2 crond服务管理
安装crontab:
yum install crontabs |
注意:CentOs中默认已经安装了crontabs服务,并已经设置了开机启动,无需再次安装和配置。
服务操作说明:
/sbin/service crond start //启动服务 /sbin/service crond stop //关闭服务 /sbin/service crond restart //重启服务 /sbin/service crond reload //重新载入配置 /sbin/service crond status //启动服务 |
1.3 crontab命令详解
crontab [ -e | -l | -r ] |
命令功能:
通过crontab 命令,我们可以直接打开并编辑当前登录用户的crond配置文件而不必再自己查找配置文件所在的位置,大大方便了用户操作。
-e:编辑某个用户的crontab文件内容。如果不指定用户,则表示编辑当前用户的crontab文件。
-l:显示某个用户的crontab文件内容,如果不指定用户,则表示显示当前用户的crontab文件内容。
-r:从/var/spool/cron目录中删除某个用户的crontab文件,如果不指定用户,则默认删除当前用户的crontab文件。
-i:在删除用户的crontab文件时给确认提示。
1.4 crontab实战
1.准备运行脚本
echo "٩(๑❛ᴗ❛๑)۶ ====== `date +"%Y-%m-%d %H:%M:%S"`" >> /root/script/test/list.txt |
注意:脚本需要有运行权限,需要使用chmod 755 [文件名]命令赋予运行权限。
2.使用crontab -i 命令打开自己的配置文件。
3.加入自己的配置
4.查看效果
2.java原生定时任务
2.1使用Timer
Timer由于其固有缺陷(Timer不支持多线程,任务是串行的,而且也不捕获异常,假设某个任务异常了,整个Timer就无法运行了。)现在使用较少,了解即可。
2.1.1 代码示例
public class TimerTest { @Test public void testTimer() throws IOException { //准备定时任务 TimerTask timerTask1 = new TimerTask() { @Override public void run() { System.out.println(Thread.currentThread().getName() + "------> ٩(๑❛ᴗ❛๑)۶ ===timerTask1=== " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); } }; TimerTask timerTask2 = new TimerTask() { @Override public void run() { System.out.println(Thread.currentThread().getName() + "------> ٩(๑❛ᴗ❛๑)۶ ===timerTask2=== " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); } }; //创建定时器 Timer timer = new Timer(); //执行定时任务,同一个Timer的任务使用同一个线程 timer.schedule(timerTask1,0,1000); timer.schedule(timerTask2,0,1000); //保存当前窗口 System.in.read(); } } |
2.1.2 运行效果
可以看到,两个任务是在同一个线程中执行的。
2.2使用ScheduledExecutorService
ScheduledExecutorService是基于线程池设计的定时任务类,每个调度任务都会分配到线程池中的一个线程去执行,也就是说,任务是并发执行,互不影响。需要注意,只有当调度任务来的时候,ScheduledExecutorService才会真正启动一个线程,其余时间ScheduledExecutorService都是出于轮询任务的状态。
2.2.1 代码示例
public class ExecutorServiceTest { @Test public void testExecutorService() throws IOException { //准备支持线程池的定时任务服务 ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5); //将任务加入服务 executorService.scheduleAtFixedRate(()->{ System.out.println(Thread.currentThread().getName() + "------> ٩(๑❛ᴗ❛๑)۶ ====== " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); },0,1, TimeUnit.SECONDS); executorService.scheduleAtFixedRate(()->{ System.out.println(Thread.currentThread().getName() + "------> ٩(๑❛ᴗ❛๑)۶ ====== " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); },0,1, TimeUnit.SECONDS);
//保存当前窗口 System.in.read(); } } |
2.2.2 运行效果
3.SpringTask
3.1 pom依赖配置
pom文件里面并不需要引入什么依赖,Spring默认提供。
3.2 单线程执行
3.2.1定义单线程测试启动类,使用@EnableScheduling注解启用定时功能
@SpringBootApplication(scanBasePackages = {"com.zk.springtask.singlethreadtask"}) @EnableScheduling public class SingleThreadSchedulerConfig { public static void main( String[] args ) { SpringApplication.run(SingleThreadSchedulerConfig.class,args); } } |
3.2.2 创建单线程定时任务测试类
@Component public class SingleThreadTestTask { @Scheduled(cron = "0/5 * * * * ?") private void process1() { System.out.println( Thread.currentThread().getName() + "٩(๑❛ᴗ❛๑)۶ ===使用cron=== " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); }
@Scheduled(fixedRate = 5000) private void process2() { System.out.println( Thread.currentThread().getName() + "٩(๑❛ᴗ❛๑)۶ ===使用fixedRate=== " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); }
@Scheduled(fixedDelay = 5000) private void process3() { System.out.println( Thread.currentThread().getName() + "٩(๑❛ᴗ❛๑)۶ ===使用fixedDelay=== " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); }
} |
3.2.3 运行效果
3.3 多线程执行
3.3.1 定义多线程测试启动类,需提供一个用于运行定时任务的线程池。
Spring Task默认单线程同步执行,如果需要使用多线程执行需要提供一个自定义线程池或者是一个Spring提供的TaskScheduler类的对象。ThreadPoolTaskScheduler是SpringTask的核心实现类,该类提供了大量的重载方法进行任务调度。
@SpringBootApplication(scanBasePackages = {"com.zk.springtask.multithreadtask"}) @EnableScheduling public class MultiThreadSchedulerConfig { public static void main(String[] args) { SpringApplication.run(MultiThreadSchedulerConfig.class, args); }
@Bean public TaskScheduler provideTaskScheduler() { ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); //线程池大小 scheduler.setPoolSize(10); //线程名字前缀 scheduler.setThreadNamePrefix("spring-multithreadtask-thread"); return scheduler; } } |
3.3.2 创建多线程定时任务测试类
@Component public class MultiThreadTestTask { @Scheduled(cron="* * * * * ?") private void process(){ System.out.println( Thread.currentThread().getName() + "٩(๑❛ᴗ❛๑)۶ ====== " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); } } |
3.3.3 运行效果
3.4 执行时间配置
在上面的定时任务中,我们在方法上使用@Scheduled注解来设置任务的执行时间,并且使用三种属性配置方式:
(1)cron:cron表达式,指定任务在特定时间执行;
(2)fixedDelay:表示上一次任务执行完成后多久再次执行,参数类型为long,单位ms;
(3)fixedDelayString:与fixedDelay含义一样,只是参数类型变为String;
(4)fixedRate:表示按一定的频率执行任务,参数类型为long,单位ms;
(5)fixedRateString: 与fixedRate的含义一样,只是将参数类型变为String;
(6)initialDelay:表示延迟多久再第一次执行任务,参数类型为long,单位ms;
(7)initialDelayString:与initialDelay的含义一样,只是将参数类型变为String;
(8)zone:时区,默认为当前时区,一般没有用到。
3.5 SpringTask中的cron表达式详解
一个cron表达式有至少6个(也可能7个)有空格分隔的时间元素。按顺序依次为:
- 秒(0~59)
- 分钟(0~59)
- 小时(0~23)
- 天(0~31)
- 月(0~11)
- 星期(1~7 1=SUN 或 SUN,MON,TUE,WED,THU,FRI,SAT)
- 年份(1970-2099)
注意:springboot中不支持年份的配置
注意:cron表达式可以使用工具生成。在线cron表达式生成:http://qqe2.com/cron/index
4. quartz
Quartz是一个任务调度框架,通过触发器设置作业的定时运行规则,来执行定时任务。如果SpringBoot版本是2.0.0以后的,则在spring-boot-starter中已经包含了quart的依赖,则可以直接使用spring-boot-starter-quartz依赖。由于SpringBoot 2.0版本之前的配置过于复杂,并且已经过时,这里不在介绍。
4.1 核心对象和简单架构
Quartz中主要包括以下核心对象:
1.Scheduler核心调度器:负责定期定时定频率的去执行任务
2.Job任务:包括了业务逻辑
3.JobDetail任务描述
4.Trigger-触发器:定义让任务生效的时间
核心对象之间的关系如下图所示:
4.2 pom依赖配置
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> </dependency> |
4.3创建任务类QuartzTask,该类主要是继承了QuartzJobBean
public class QuartzTask extends QuartzJobBean{ /** * 定时任务配置 * @param jobExecutionContext * @throws JobExecutionException */ @Override protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException { System.out.println( Thread.currentThread().getName() + "٩(๑❛ᴗ❛๑)۶ ====== " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); } } |
4.4创建配置类QuartzConfig
@SpringBootApplication @EnableScheduling public class QuartzConfig { /** * 创建一个job * @return */ @Bean public JobDetail testQuartzTask(){ return JobBuilder.newJob(QuartzTask.class).withIdentity("quartzTask").storeDurably().build(); }
/** * 创建一个触发器构建者 * @return */ @Bean public Trigger testQuartzTrigger(){ SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule() .withIntervalInSeconds(1) //设置时间周期单位秒 .repeatForever(); return TriggerBuilder.newTrigger().forJob(testQuartzTask()) .withIdentity("quartzTask") .withSchedule(scheduleBuilder) .build(); }
public static void main(String[] args) { SpringApplication.run(QuartzConfig.class, args); } } |
4.5运行效果
5. Quartz与Spring Task对比总结
5.1 Quartz 和 Spring Task执行时间对比
1. Quartz设置同步模式时:一个任务的两次执行的时间间隔是:“执行时间”和“trigger的设定间隔”的最大值。
2. Task默认同步模式:一个任务的两次执行的时间间隔是:“执行时间”+“trigger的设定间隔”,即一个任务完成执行后,才开始trigger计时。
5.2 Quartz 特点
1. 默认多线程异步执行。
2. 一个任务在上一次调度未完成执行,下一次调度时间到时,会另起一个线程开始新的调度。在业务繁忙时,一个任务或许会有多个线程在执行,导致数据处理异常。
3. 单任务同步:配置属性,可以使一个任务的一次调度在未完成时,而不会开启下一次调度。
4. 多个任务同时运行,任务之间没有直接的影响,多任务执行的快慢取决于CPU的性能。
5. SchedulerFactoryBean不能使用注解来配置。还是我没找到注解的方法。
5.3 Spring Task特点
1. 默认单线程同步执行。
2. 一个任务执行完上一次之后,才会执行下一次调度。
3. 多任务之间按顺序执行,一个任务执行完成之后才会执行另一个任务。
4. 多任务并行执行需要设置线程池。
5. 全程可以通过注解配置。
声明:本文参考了大量前辈们的博客文章,感谢他们的分享让我学到了更多。由于参考内容繁杂,如您发现您的文章并未出现在参考列表中,请通知我添加,谢谢!。
参考列表:
https://www.cnblogs.com/intval/p/5763929.html