SpringBoot | 第二十二章:定时任务的使用
前言
上两章节,我们简单的讲解了关于异步调用和异步请求相关知识点。这一章节,我们来讲讲开发过程也是经常会碰见的定时任务。比如每天定时清理无效数据、定时发送短信、定时发送邮件、支付系统中的定时对账等等,往往都会定义一些定时器,进行此业务的开发。所以,本章节介绍下在
SpringBoot
中定时任务如何使用及一点分布式定时服务的思考总结。
一点知识
在JAVA
开发领域,目前可以通过以下几种方式进行定时任务:
- Timer:jdk中自带的一个定时调度类,可以简单的实现按某一频度进行任务执行。提供的功能比较单一,无法实现复杂的调度任务。
- ScheduledExecutorService:也是jdk自带的一个基于线程池设计的定时任务类。其每个调度任务都会分配到线程池中的一个线程执行,所以其任务是并发执行的,互不影响。
- Spring Task:
Spring
提供的一个任务调度工具,支持注解和配置文件形式,支持Cron
表达式,使用简单但功能强大。 - Quartz:一款功能强大的任务调度器,可以实现较为复杂的调度功能,如每月一号执行、每天凌晨执行、每周五执行等等,还支持分布式调度,就是配置稍显复杂。
题外话:对于Quartz
,早前用过1.6版本的,更新到2.x及以上版本后基本没怎么接触了,原来还有倒腾过结合Kettle
做了一些动态的定时抽取数据啥的还编写过一个Cron
表达式编辑器,现在基本忘记了。。等有机会,再次深入学习后再来单独分享一些关于的Quartz
心得吧。
基于JDK方式实现简单定时
刚刚有介绍过,基于
JDK
方式一共有两种:Timer
和ScheduledExecutorService
。接下来,就简单讲解下这两种方式。
Timer
Timer
是jdk提供的java.util.Timer
类。
简单示例:
@GetMapping("/timer")
public String doTimer() {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
log.info("Timer定时任务启动:" + new Date());
}
}, 1000,1000);//延迟1秒启动,每1秒执行一次
return "timer";
启动后,访问即可看见控制台周期性输出信息了:
2018-08-18 21:30:35.171 INFO 13352 --- [ Timer-0] c.l.l.s.c.controller.TaskController : Timer定时任务启动:Sat Aug 18 21:30:35 CST 2018
2018-08-18 21:30:36.173 INFO 13352 --- [ Timer-0] c.l.l.s.c.controller.TaskController : Timer定时任务启动:Sat Aug 18 21:30:36 CST 2018
2018-08-18 21:30:37.173 INFO 13352 --- [ Timer-0] c.l.l.s.c.controller.TaskController : Timer定时任务启动:Sat Aug 18 21:30:37 CST 2018
2018-08-18 21:30:38.173 INFO 13352 --- [ Timer-0] c.l.l.s.c.controller.TaskController : Timer定时任务启动:Sat Aug 18 21:30:38 CST 2018
2018-08-18 21:30:39.174 INFO 13352 --- [ Timer-0] c.l.l.s.c.controller.TaskController : Timer定时任务启动:Sat Aug 18 21:30:39 CST 2018
......
相关API简单说明:
1、在特定时间执行任务,只执行一次
public void schedule(TimerTask task,Date time)
2、在特定时间之后执行任务,只执行一次
public void schedule(TimerTask task,long delay)
3、指定第一次执行的时间,然后按照间隔时间,重复执行
public void schedule(TimerTask task,Date firstTime,long period)
4、在特定延迟之后第一次执行,然后按照间隔时间,重复执行
public void schedule(TimerTask task,long delay,long period)
5、第一次执行之后,特定频率执行,与3同
public void scheduleAtFixedRate(TimerTask task,Date firstTime,long period)
6、在delay毫秒之后第一次执行,后按照特定频率执行
public void scheduleAtFixedRate(TimerTask task,long delay,long period)
参数:
- delay: 延迟执行的毫秒数,即在delay毫秒之后第一次执行
- period:重复执行的时间间隔
取消任务使用:timer.cancel()
方法即可注销任务。
此类相对用的较少了,简单了解下。
ScheduledExecutorService
ScheduledExecutorService
可以说是Timer
的替代类,因为Timer
不支持多线程,任务是串行的,而且也不捕获异常,假设某个任务异常了,整个Timer
就无法运行了。
简单示例:
@GetMapping("/executor")
public String ScheduledExecutorService() {
//
ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
log.info("ScheduledExecutorService定时任务执行:" + new Date());
}
}, 1, 1, TimeUnit.SECONDS);//首次延迟1秒,之后每1秒执行一次
log.info("ScheduledExecutorService定时任务启动:" + new Date());
return "ScheduledExecutorService!";
}
启动后,可看见控制台按设定的频率输出:
2018-08-18 22:03:24.840 INFO 6752 --- [nio-8080-exec-1] c.l.l.s.c.controller.TaskController : ScheduledExecutorService定时任务启动:Sat Aug 18 22:03:24 CST 2018
2018-08-18 22:03:25.841 INFO 6752 --- [pool-1-thread-1] c.l.l.s.c.controller.TaskController : ScheduledExecutorService定时任务执行:Sat Aug 18 22:03:25 CST 2018
2018-08-18 22:03:26.842 INFO 6752 --- [pool-1-thread-1] c.l.l.s.c.controller.TaskController : ScheduledExecutorService定时任务执行:Sat Aug 18 22:03:26 CST 2018
2018-08-18 22:03:27.841 INFO 6752 --- [pool-1-thread-2] c.l.l.s.c.controller.TaskController : ScheduledExecutorService定时任务执行:Sat Aug 18 22:03:27 CST 2018
2018-08-18 22:03:28.840 INFO 6752 --- [pool-1-thread-1] c.l.l.s.c.controller.TaskController : ScheduledExecutorService定时任务执行:Sat Aug 18 22:03:28 CST 2018
2018-08-18 22:03:29.840 INFO 6752 --- [pool-1-thread-3] c.l.l.s.c.controller.TaskController : ScheduledExecutorService定时任务执行:Sat Aug 18 22:03:29 CST 2018
可同时设置多个任务,只需再次设置scheduleAtFixedRate
即可。
常用方法说明:
- ScheduleAtFixedRate:
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit);
参数说明:
0. command:执行线程
- initialDelay:初始化延时
- period:两次开始执行最小间隔时间
- unit:计时单位
- ScheduleWithFixedDelay:
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit);
参数说明:
0. command:执行线程
- initialDelay:初始化延时
- delay:前一次执行结束到下一次执行开始的间隔时间(间隔执行延迟时间)
- unit:计时单位
其他的方法大家可自行谷歌下。
基于SpingTask实现定时任务
使用
SpringTask
在SpringBoot
是很简单的,使用@Scheduled
注解即可轻松搞定。
0.启动类,加入@EnableScheduling
让注解@Scheduled
生效。
@SpringBootApplication
@EnableScheduling
@Slf4j
public class Chapter22Application {
public static void main(String[] args) {
SpringApplication.run(Chapter22Application.class, args);
log.info("Chapter22启动!");
}
}
1.编写一个调度类,系统启动后自动扫描,自动执行。
@Component
@Slf4j
public class ScheduledTask {
/**
* 自动扫描,启动时间点之后5秒执行一次
*/
@Scheduled(fixedRate=5000)
public void getCurrentDate() {
log.info("Scheduled定时任务执行:" + new Date());
}
}
2.启动后,控制台可就看见每5秒一次输出了:
2018-08-18 22:23:09.735 INFO 13812 --- [pool-1-thread-1] c.l.l.s.c.controller.ScheduledTask : Scheduled定时任务执行:Sat Aug 18 22:23:09 CST 2018
2018-08-18 22:23:14.734 INFO 13812 --- [pool-1-thread-1] c.l.l.s.c.controller.ScheduledTask : Scheduled定时任务执行:Sat Aug 18 22:23:14 CST 2018
2018-08-18 22:23:19.735 INFO 13812 --- [pool-1-thread-1] c.l.l.s.c.controller.ScheduledTask : Scheduled定时任务执行:Sat Aug 18 22:23:19 CST 2018
2018-08-18 22:23:24.735 INFO 13812 --- [pool-1-thread-1] c.l.l.s.c.controller.ScheduledTask : Scheduled定时任务执行:Sat Aug 18 22:23:24 CST 2018
2018-08-18 22:23:29.735 INFO 13812 --- [pool-1-thread-1] c.l.l.s.c.controller.ScheduledTask : Scheduled定时任务执行:Sat Aug 18 22:23:29 CST 2018
......
使用都是简单的,现在我们来看看注解@Scheduled
的参数意思:
- fixedRate:定义一个按一定频率执行的定时任务
- fixedDelay:定义一个按一定频率执行的定时任务,与上面不同的是,改属性可以配合
initialDelay
, 定义该任务延迟执行时间。 - cron:通过表达式来配置任务执行时间
Cron表达式详解
一个
cron
表达式有至少6个(也可能7个)有空格分隔的时间元素。
依次顺序如下表所示:
字段 | 允许值 | 允许的特殊字符 |
---|---|---|
秒 | 0~59 | , - * / |
分 | 0~59 | , - * / |
小时 | 0~23 | , - * / |
日期 | 1-31 | , - * ? / L W C |
月份 | 112或者JANDEC | , - * / |
星期 | 17或者SUNSAT | , - * ? / L C # |
年(可选) | 留空,1970~2099 | , - * / |
简单举例:
- 0/1 * * * * ?:每秒执行一次
- 0 0 2 1 * ? : 表示在每月的1日的凌晨2点调整任务
- 0 0 10,14,16 ? :每天上午10点,下午2点,4点
- 0 0 12 * * ? : 每天中午12点触发
- 0 15 10 ? * MON-FRI : 周一至周五的上午10:15触发
更多表达式,可访问:http://cron.qqe2.com/ 进行在线表达式编写。简单明了。
自定义线程池
从控制台输出可以看见,多任务使用的是同一个线程。可结合上章节的异步调用来实现不同任务使用不同的线程进行任务执行。
0.编写配置类,同时启用@Async
注解:
@Configuration
@EnableAsync
public class Config {
/**
* 配置线程池
* @return
*/
@Bean(name = "scheduledPoolTaskExecutor")
public ThreadPoolTaskExecutor getAsyncThreadPoolTaskExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(20);
taskExecutor.setMaxPoolSize(200);
taskExecutor.setQueueCapacity(25);
taskExecutor.setKeepAliveSeconds(200);
taskExecutor.setThreadNamePrefix("oKong-Scheduled-");
// 线程池对拒绝任务(无线程可用)的处理策略,目前只支持AbortPolicy、CallerRunsPolicy;默认为后者
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//调度器shutdown被调用时等待当前被调度的任务完成
taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
//等待时长
taskExecutor.setAwaitTerminationSeconds(60);
taskExecutor.initialize();
return taskExecutor;
}
}
1.调度类上加入@Async
。
@Component
@Slf4j
public class ScheduledTask {
/**
* 自动扫描,启动时间点之后5秒执行一次
*/
@Async("scheduledPoolTaskExecutor")
@Scheduled(fixedRate=5000)
public void getCurrentDate() {
log.info("Scheduled定时任务执行:" + new Date());
}
}
再次启动程序,可看见控制台输出,任务已经是不同线程下执行了:
2018-08-18 22:47:13.313 INFO 14212 --- [ong-Scheduled-1] c.l.l.s.c.controller.ScheduledTask : Scheduled定时任务执行:Sat Aug 18 22:47:13 CST 2018
2018-08-18 22:47:13.343 INFO 14212 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2018-08-18 22:47:13.348 INFO 14212 --- [ main] c.l.l.s.chapter22.Chapter22Application : Started Chapter22Application in 2.057 seconds (JVM running for 2.855)
2018-08-18 22:47:13.348 INFO 14212 --- [ main] c.l.l.s.chapter22.Chapter22Application : Chapter22启动!
2018-08-18 22:47:18.308 INFO 14212 --- [ong-Scheduled-2] c.l.l.s.c.controller.ScheduledTask : Scheduled定时任务执行:Sat Aug 18 22:47:18 CST 2018
动态添加定时任务
使用注解的方式,无法实现动态的修改或者添加新的定时任务的,这个使用就需要使用编程的方式进行任务的更新操作了。可直接使用
ThreadPoolTaskScheduler
或者SchedulingConfigurer
接口进行自定义定时任务创建。
ThreadPoolTaskScheduler
ThreadPoolTaskScheduler
是SpringTask
的核心实现类,该类提供了大量的重载方法进行任务调度。这里简单示例下,具体的大家自行搜索下,用的少不太了解呀。
0.创建一个ThreadPoolTaskScheduler
类。
@Bean("taskExecutor")
public TaskScheduler taskExecutor() {
ThreadPoolTaskScheduler executor = new ThreadPoolTaskScheduler();
executor.setPoolSize(20);
executor.setThreadNamePrefix("oKong-taskExecutor-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//调度器shutdown被调用时等待当前被调度的任务完成
executor.setWaitForTasksToCompleteOnShutdown(true);
//等待时长
executor.setAwaitTerminationSeconds(60);
return executor;
}
1.编写一个控制类,动态设置定时任务:
@Autowired
TaskScheduler taskScheduler;
@GetMapping("/poolTask")
public String threadPoolTaskScheduler() {
taskScheduler.schedule(new Runnable() {
@Override
public void run() {
log.info("ThreadPoolTaskScheduler定时任务:" + new Date());
}
}, new CronTrigger("0/3 * * * * ?"));//每3秒执行一次
return "ThreadPoolTaskScheduler!";
}
2.启动后,访问接口,即可看见控制台每3秒输出一次:
2018-08-18 23:20:39.002 INFO 9120 --- [Kong-Executor-1] c.l.l.s.c.controller.TaskController : ThreadPoolTaskScheduler定时任务:Sat Aug 18 23:20:39 CST 2018
2018-08-18 23:20:42.000 INFO 9120 --- [Kong-Executor-1] c.l.l.s.c.controller.TaskController : ThreadPoolTaskScheduler定时任务:Sat Aug 18 23:20:42 CST 2018
2018-08-18 23:20:45.002 INFO 9120 --- [Kong-Executor-2] c.l.l.s.c.controller.TaskController : ThreadPoolTaskScheduler定时任务:Sat Aug 18 23:20:45 CST 2018
2018-08-18 23:20:48.001 INFO 9120 --- [Kong-Executor-1] c.l.l.s.c.controller.TaskController : ThreadPoolTaskScheduler定时任务:Sat Aug 18 23:20:48 CST 2018
SchedulingConfigurer
此类十个接口,直接实现其
configurerTasks
方法即可。
0.编写配置类:
@Configuration
@Slf4j
public class ScheduleConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setTaskScheduler(taskExecutor());
taskRegistrar.getScheduler().schedule(new Runnable() {
@Override
public void run() {
log.info("SchedulingConfigurer定时任务:" + new Date());
}
}, new CronTrigger("0/3 * * * * ?"));//每3秒执行一次
}
@Bean("taskExecutor")
public TaskScheduler taskExecutor() {
ThreadPoolTaskScheduler executor = new ThreadPoolTaskScheduler();
executor.setPoolSize(20);
executor.setThreadNamePrefix("oKong-Executor-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//调度器shutdown被调用时等待当前被调度的任务完成
executor.setWaitForTasksToCompleteOnShutdown(true);
//等待时长
executor.setAwaitTerminationSeconds(60);
return executor;
}
}
1.启动后,控制台也可以看见每3秒输出一次:
2018-08-18 23:24:39.001 INFO 868 --- [Kong-Executor-1] c.l.l.s.chapter22.config.ScheduleConfig : SchedulingConfigurer定时任务:Sat Aug 18 23:24:39 CST 2018
2018-08-18 23:24:42.001 INFO 868 --- [Kong-Executor-1] c.l.l.s.chapter22.config.ScheduleConfig : SchedulingConfigurer定时任务:Sat Aug 18 23:24:42 CST 2018
2018-08-18 23:24:45.000 INFO 868 --- [Kong-Executor-2] c.l.l.s.chapter22.config.ScheduleConfig : SchedulingConfigurer定时任务:Sat Aug 18 23:24:45 CST 2018
基于Quartz实现定时调度
由于本章节是基于
SpringBoot 1.x
版本的,所以没有基于Quartz
的starter
配置,这里直接引入了Quartz
相关依赖包来集成。
题外话:原本使用SpringMvc
时,一般上都是通过xml
文件,配置其org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean
类进行具体执行任务的配置,指定执行的对象和方法。然后通过设置CronTriggerFactoryBean
或者SimpleTriggerFactoryBean
设置定时器,最后通过org.springframework.scheduling.quartz.SchedulerFactoryBean
加入调度的trigger
。所以,我们就使用javaConfig
方式进行简单集成下。
0.加入pom依赖
<!-- quartz -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.3</version>
</dependency>
<!-- spring集成quartz -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<!-- 因为SchedulerFactoryBean中依赖了org.springframework.transaction.PlatformTransactionManager,所以需依赖tx相关包,其实还是quartz有个分布式功能,是使用数据库完成的。 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
</dependency>
1.编写配置类。
@Configuration
@Slf4j
public class QuartzConfig {
/**
* 通过工厂类,创建job实例
* @return
*/
@Bean
public MethodInvokingJobDetailFactoryBean customJobDetailFactoryBean() {
MethodInvokingJobDetailFactoryBean jobDetail = new MethodInvokingJobDetailFactoryBean();
//设置执行任务的bean
jobDetail.setTargetBeanName("quartzTask");
//设置具体执行的方法
jobDetail.setTargetMethod("quartzTask");
//同步执行,上一任务未执行完,下一任务等待
//true 任务并发执行
//false 下一个任务必须等待上一任务完成
jobDetail.setConcurrent(false);
return jobDetail;
}
/**
* 通过工厂类创建Trigger
* @param jobDetailFactoryBean
* @return
* @throws ParseException
*/
@Bean(name = "cronTriggerBean")
public Trigger cronTriggerBean(MethodInvokingJobDetailFactoryBean jobDetailFactoryBean) throws ParseException {
CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean();
cronTriggerFactoryBean.setJobDetail(jobDetailFactoryBean.getObject());
cronTriggerFactoryBean.setCronExpression("0/3 * * * * ?");//每3秒执行一次
cronTriggerFactoryBean.setName("customCronTrigger");
cronTriggerFactoryBean.afterPropertiesSet();
return cronTriggerFactoryBean.getObject();
}
/**
* 调度工厂类,自动注入Trigger
* @return
*/
@Bean
public SchedulerFactoryBean schedulerFactoryBean(Trigger... triggers) {
SchedulerFactoryBean bean = new SchedulerFactoryBean();
//也可以直接注入 ApplicationContext,利于 getBeansOfType获取trigger
// Map<String,Trigger> triggerMap = appContext.getBeansOfType(Trigger.class);
// if(triggerMap != null) {
// List<Trigger> triggers = new ArrayList<>(triggerMap.size());
// //
// triggerMap.forEach((key,trigger)->{
// triggers.add(trigger);
// });
// bean.setTriggers(triggers.toArray(new Trigger[triggers.size()]));
// }
//这里注意 对应的trigger 不能为null 不然会异常的
bean.setTriggers(triggers);
return bean;
}
@Component("quartzTask")
public class QuartzTask {
public void quartzTask() {
log.info("Quartz定时任务:" + new Date());
}
}
}
2.启动后,可以看见控制台以每3秒执行一次输出:
2018-08-18 23:42:03.019 INFO 772 --- [ryBean_Worker-2] c.l.l.s.chapter22.config.QuartzConfig : Quartz定时任务:Sun Aug 18 23:42:03 CST 2018
2018-08-18 23:42:06.002 INFO 772 --- [ryBean_Worker-3] c.l.l.s.chapter22.config.QuartzConfig : Quartz定时任务:Sun Aug 18 23:42:06 CST 2018
2018-08-18 23:42:09.002 INFO 772 --- [ryBean_Worker-4] c.l.l.s.chapter22.config.QuartzConfig : Quartz定时任务:Sun Aug 18 23:42:09 CST 2018
关于Quartz
的详细用法,再次不表了。好久没有使用过了。有机会再来详细阐述吧。
分布式调度服务浅谈
在单机模式下,定时任务是没什么问题的。但当我们部署了多台服务,同时又每台服务又有定时任务时,若不进行合理的控制在同一时间,只有一个定时任务启动执行,这时,定时执行的结果就可能存在混乱和错误了。
这里简单的说说相关的解决方案吧,一家之言,希望大家能提出自己的见解,共同进步!
- 剥离所有定时任务到一个工程:此方案是最简单的,在定时任务相对较小,并发任务不多时,可以使用此方案。简单也容易维护。当定时任务牵扯的业务越来越多,越来越杂时,维护量就成本增加了,工程会越来越臃肿,此方案就不实用了。
- 利用
Quartz
集群方案:本身Quartz
是支持通过数据库实现集群的,以下是其集群架构图:
其实现原理也相对简单:通过数据库实现任务的持久化,保存定时任务的相关配置信息,以保证下次系统启动时,定时任务能自动启动。同时,通过数据库行锁(for update)
机制,控制一个任务只能被一个实例运行,只有获取锁的实例才能运行任务,其他的只能等待,直到锁被释放。这种方式有些弊端,就是依赖了数据库,同时也需要保证各服务器之间的时间需要同步,不然也是会混乱的。
现在Quartz
也有基于Redis
的集群方案,有兴趣的可以搜索下。
分布式锁:可通过使用
Redis
或者ZooKeeper
实现一个分布式锁的机制,使得只有获取到锁的实例方能运行定时任务,避免任务重复执行。可查看下开源的基于Redis
实现的分布式锁项目:redisson
。github地址:https://github.com/redisson/redisson有兴趣的同学可以了解下。统一调度中心:
可构建一个纯粹
的定时服务,只有定时器
相关配置,比如定时时间
,定时调度的api接口
或者http
服务,甚至是统一注册中心下的服务类,如dubbo服务等。而具体的任务执行操作都在各自业务方系统中,调度中心只负责接口的调用
,具体实现还是在业务方。这种方案相对来说比较通用,实现起来也简单。就是需要业务方进行约定编程,或者对外提供一个api接口。
当然,为了实现定时任务的自动发现和注册功能,还是需要规范一套规则来实现自动注册功能。简单来说,以Dubbo
服务为例,可以定义一个定时任务接口类
,调度中心只需要获取所有实现此接口的服务,同时通过服务的相关配置(调度时间、失败策略等)进行相关定时操作。或者编写一个服务注册与发现的客户端,通过Spring
获取到实现此接口的所有实现类,上送到调度中心。
而且,统一调度中心,还可以对所有的定时任务的调度情况进行有效监控,日志记录等,也可以约定接口,让定时任务回传定时结果,做到全局把控的目的。
以上就是对分布式调度的一点理解,有错误的地方还望指正,有更好的方案也希望能分享下。
参考资料
- https://www.cnblogs.com/yank/p/3955322.html
- https://blog.csdn.net/tsyj810883979/article/details/8481621
- https://www.cnblogs.com/javahr/p/8318728.html
- http://www.quartz-scheduler.org/documentation/quartz-2.1.x/quick-start.html
- https://spring.io/guides/gs/scheduling-tasks/
总结
本章节主要是讲解了通过不同的方式实现定时任务。对于定时任务而言,本身是门大学问,一俩篇文章是讲不完的。像
SpringTask
和Quartz
都是很强大的调度器,两者很相似,像如何实现任务的动态修改调度周期,动态停止相关任务,调度任务的监控,这些本文章都没有涉及。还希望有相关需求的同学自行搜索相关资料了。
最后
目前互联网上很多大佬都有
SpringBoot
系列教程,如有雷同,请多多包涵了。本文是作者在电脑前一字一句敲的,每一步都是自己实践的。若文中有所错误之处,还望提出,谢谢。
老生常谈
- 个人QQ:
499452441
- 微信公众号:
lqdevOps
个人博客:http://blog.lqdev.cn
完整示例:chapter-22
原文地址:http://blog.lqdev.cn/2018/08/19/springboot/chapter-twenty-two/
SpringBoot | 第二十二章:定时任务的使用的更多相关文章
- 《Linux命令行与shell脚本编程大全》 第二十二章 学习笔记
第二十二章:使用其他shell 什么是dash shell Debian的dash shell是ash shell的直系后代,ash shell是Unix系统上原来地Bourne shell的简化版本 ...
- 第二十二章 Django会话与表单验证
第二十二章 Django会话与表单验证 第一课 模板回顾 1.基本操作 def func(req): return render(req,'index.html',{'val':[1,2,3...]} ...
- Gradle 1.12用户指南翻译——第二十二章. 标准的 Gradle 插件
其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Github上的地址: https://g ...
- “全栈2019”Java多线程第二十二章:饥饿线程(Starvation)详解
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...
- “全栈2019”Java异常第二十二章:try-with-resources语句详解
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java异 ...
- “全栈2019”Java第二十二章:控制流程语句中的决策语句if-else
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...
- python 教程 第二十二章、 其它应用
第二十二章. 其它应用 1) Web服务 ##代码 s 000063.SZ ##开盘 o 26.60 ##最高 h 27.05 ##最低 g 26.52 ##最新 l1 26.66 ##涨跌 c ...
- 第二十二章 跳出循环-shift参数左移-函数的使用 随堂笔记
第二十二章 跳出循环-shift参数左移-函数的使用 本节所讲内容: 22.1 跳出循环 22.2 Shift参数左移指令 22.3 函数的使用 22.4 实战-自动备份mysql数据库和nginx服 ...
- 20190925 On Java8 第二十二章 枚举
第二十二章 枚举 基本 enum 特性 创建 enum 时,编译器会为你生成一个相关的类,这个类继承自 Java.lang.Enum. valueOf() 是在 Enum 中定义的 static 方法 ...
随机推荐
- java验证,”支持6-20个字母、数字、下划线或减号,以字母开头“这个的正则表达式怎么写?
转自:https://yq.aliyun.com/wenzhang/show_96854 问题描述 java验证,”支持6-20个字母.数字.下划线或减号,以字母开头“这个的正则表达式怎么写? 验证” ...
- [51nod1272]最大距离(贪心)
解题关键:对num进行排序,从前往后扫id,及时更新 #include<cstdio> #include<cstring> #include<algorithm> ...
- Spring开发包介绍
-----------------siwuxie095 核心开发包 建立 Spring 工程时,需要引入 Spring 的开发包,否则无 ...
- ubuntu 13.04 安装 JDK
ubuntu 13.04 安装 JDK 具体步骤参详了如下链接: http://blog.csdn.net/yang_hui1986527/article/details/6677450 1.到 Su ...
- 第 2 章 Python 语言入⻔
目录 2.1低而长的学习曲线 2.2Python的优势 2.3在你的计算机中安装Python 2.4如何运行Python程序 2.5文本编辑器 2.6寻求帮助 Python语言是一种流行的编程语言,在 ...
- hdu1079
#include<cstdio> #include<iostream> #include<cstring> using namespace std; int mai ...
- Flash builder发布Air程序时设备配置文件supportedProfiles的配置
1. 发布的程序:需要访问本地进程,那么只能发布为exe程序才可以. 此时supportedProfiles 配置为 extendedDesktop desktop desktop保证能发布a ...
- windows配置换行符
CR.LF.CR/LF为不同操作系统上使用的换行符: Windows/DOS系统:采用CR/LF表示下一行: Unix/Linux系统:采用LF表示下一行: Mac OS系统:采用CR表示下一行: M ...
- spark svm
首先spark上的svm只能处理线性的,不能处理非线性的.其次spark上的svm求解过程与普通的不同.普通的是通过拉格朗日对偶,然后通过SMO方法求. 但是在spark上,则没有通过拉格朗日,而是直 ...
- linq分页扩展
直接上代码了 public static List<T> ToPagedList<T>(this IEnumerable<T> allItems, int page ...