在线corn表达式
1. 总结常见的实现定时任务的几种方法
- thread实现 【原理:通过创建一个线程,让他在while循环里面一直运行,用sleep() 方法让其休眠从而达到定时任务的效果。】
- Timer类
- ScheduledExcecutorService类
- 使用springboot的 spring-task 实现
- Quartz
- xxl-job
以下演示几种实现方式:每隔一秒打印一次hello world
1.1 thread实现
public static void main(String[] args) {
final long timeInterval = 1000;
Runnable runnable = new Runnable() {
@Override
public void run() {
while (true) {
System.out.println("hello world");
try {
Thread.sleep(timeInterval);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread thread = new Thread(runnable);
thread.start();
}
1.2 Timer类实现
Timer是jdk中自带的一个定时器工具,使用的时候会在主线程之外起一个单独的线程执行指定的计划任务,可以指定执行一次或者反复执行多次。但封装任务的类是TimerTask类(实际该类是一个抽象类,执行任务的代码要放在该类的子类中)。
TimerTask是一个实现了Runnable接口的抽象类,代表一个可以被Timer执行的任务。
构造方法:
成员方法:
public static void main(String[] args) {
TimerTask task = new TimerTask() {
@Override
public void run() {
System.out.println("Hello!Word!");
}
};
Timer timer = new Timer();
long delay = 0;
long intevalPeriod = 1 * 1000;
timer.scheduleAtFixedRate(task, delay, intevalPeriod);
}
schedule 与 scheduleAtFixedRate区别:
schedule会保证任务的间隔是按照定义的period参数严格执行的,如果某一次调度时间比较长,那么后面的时间会顺延,保证调度间隔都是period。
scheduleAtFixedRate是严格按照调度时间来的,如果某次调度时间太长了,那么会通过缩短间隔的方式保证下一次调度在预定时间执行。
线程安全, 但只会单线程执行, 如果执行时间过长, 就错过下次任务了, 抛出异常时, timerWork会终止
启动和取消任务是可以控制的
1.3 ScheduledExcecutorService类实现
ScheduledExecutorService是JDK1.5以后java.util.concurrent中的一个接口, 用于实现定时任务。
public static void main(String[] args) {
Runnable r1 = new Runnable() {
public void run() {
System.out.println("Hello Word");
}
};
ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
// 第二个参数为首次执行的延时时间,第三个参数为定时执行的间隔时间
service.scheduleAtFixedRate(r1, 3, 1, TimeUnit.SECONDS);// 3秒后开始执行
}
他是通过线程池的方式执行任务,可以多线程执行。
启动和取消任务是可以控制的
可以设定第一次的延迟时间
1.4 使用springboot提供的 spring-task 实现【这玩意其实是Spring里面的,不过现在都是SpringBoot开发,所以都是基于注解来使用它】
a) 只需要导入web的starter依赖
b) 在启动类上添加 @EnableScheduling 注解
c) 用 @Scheduled 注解开启一个定时任务。
@Component
public class SchedulerTask {
private int count = 0;
/**
* @Author Smith
* @Description 设置每1秒执行一次
* @Date 14:23 2019/1/24
* @Param
* @return void
**/
@Scheduled(cron = "*/1 * * * * ?") // 基于cron表达式实现
private void process(){
System.out.println("hello world " + (count++));
}
}
@Scheduled 该注解的常用参数说明【不基于cron表达式实现的时候】:
fixedRate 表示任务执行之间的时间间隔,具体是指两次任务的开始时间间隔,即第二次任务开始时,第一次任务可能还没结束。
fixedDelay 表示任务执行之间的时间间隔,具体是指本次任务结束到下次任务开始之间的时间间隔。
initialDelay 表示首次任务启动的延迟时间。
所有时间的单位都是毫秒。
1.5 Quartz
参考源码 Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与J2EE与J2SE应用程序相结合也可以单独使用。Quartz可以用来创建简单或为运行十个,百个,甚至是好几万个Jobs这样复杂的程序。
Quartz默认是多线程异步执行,单个任务时,在上一个调度未完成时,下一个调度时间到时,会另起一个线程开始新的调度。
Quartz支持集群定时任务
Quartz与Spring Task区别
- Quartz默认多线程异步执行,Task默认单线程同步执行。
- Quartz单个任务时,在上一个调度未完成时,下一个调度时间到时,会另起一个线程开始新的调度。Task单个任务时,当前次的调度完成后,再执行下一次任务调度。
- Quartz多个任务时,任务之间没有直接影响,多任务执行的快慢取决于CPU的性能。Task多个任务时,一个任务执行完成后才会执行下一个任务。若需要任务能够并发执行,需手动设置线程池
- Quartz可以采用集群方式,分布式部署到多台机器,分配执行定时任务
两者对比总结:
1、实现,Task注解实现方式,比较简单。Quartz需要手动配置Jobs。
2、任务执行,Task默认单线程串行执行任务,多任务时若某个任务执行时间过长,后续任务会无法及时执行。Quartz采用多线程,无这个问题。
3、调度,Task采用顺序执行,若当前调度占用时间过长,下一个调度无法及时执行;
4、Quartz采用异步,下一个调度时间到达时,会另一个线程执行调度,不会发生阻塞问题,但调度过多时可能导致数据处理异常
5、部署,Quartz可以采用集群方式,分布式部署到多台机器,分配执行定时任务
Quartz 核心概念:
a) Job 表示一个工作,要执行的具体内容。此接口中只有一个方法。Task多个任务时,一个任务执行完成后才会执行下一个任务。若需要任务能够并发执行,需手动设置线程池。
void execute(JobExecutionContext context)
b) JobDetail 表示一个具体的可执行的调度程序,Job 是这个可执行程调度程序所要执行的内容,另外 JobDetail 还包含了这个任务调度的方案和策略。
c) Trigger 代表一个调度参数的配置,什么时候去调。
d) Scheduler 代表一个调度容器,一个调度容器中可以注册多个 JobDetail 和 Trigger。当 Trigger 与 JobDetail 组合,就可以被 Scheduler 容器调度了。
Spring Boot整合Quartz:
- 导入Quartz依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
- 编写任务类
public class Task implements Job {
// 任务类不能直接注入Bean,若想注入Bean需要配置第4步
@Autowired
private TestService service;
private void before(){
System.out.println("定时任务开始");
}
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
service.testDemo();
System.out.println("hello quartz");
}
private void afer(){
System.out.println("定时任务结束");
}
}
★ 任务类不能直接注入Bean,若想注入Bean需要配置第4步 ★
- 编写Quartz配置
@Configuration
public class QuartzConfig {
@Bean
public JobDetailFactoryBean jobDetailFactoryBean() {
JobDetailFactoryBean factory = new JobDetailFactoryBean();
//关联我们自己的Job类
factory.setJobClass(Task.class);
return factory;
}
@Bean
public CronTriggerFactoryBean cronTriggerFactoryBean(JobDetailFactoryBean jobDetailFactoryBean) {
CronTriggerFactoryBean factory = new CronTriggerFactoryBean();
factory.setJobDetail(jobDetailFactoryBean.getObject());
//设置触发时间
factory.setCronExpression("0/2 * * * * ?");
return factory;
}
@Bean
public SchedulerFactoryBean schedulerFactoryBean(CronTriggerFactoryBean cronTriggerFactoryBean, MyAdaptableJobFactory jobFactory) {
SchedulerFactoryBean factory = new SchedulerFactoryBean();
//关联trigger
factory.setTriggers(cronTriggerFactoryBean.getObject());
factory.setJobFactory(jobFactory);
return factory;
}
}
- 编写配置【用于任务类能注入我们的bean】
@Component
public class MyAdaptableJobFactory extends AdaptableJobFactory {
// 可以将一个对象添加到SpringIOC 容器中,并且完成该对象注入
@Autowired
private AutowireCapableBeanFactory autowireCapableBeanFactory;
/**
* 该方法需要将实例化的任务对象手动的添加到springIOC 容器中并且完成对象的注入
*/
@Override
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
Object obj = super.createJobInstance(bundle);
//将obj 对象添加Spring IOC 容器中,并完成注入
this.autowireCapableBeanFactory.autowireBean(obj);
return obj;
}
}
补充:分布式任务调度
如果在分布式集群的模式下,采用集中式的任务调度方式,会带来一些问题。如:
- 多台机器集群部署时,该机器的定时任务如何保证不被重复执行?
- 如何动态的调整定时任务的执行时间(不重启服务器的情况)?
- 若某一台部署定时任务的机器发生故障时,如何转移故障给其他机器?
- 如何对定时任务进行监控?
- 业务量大,单机性能出现瓶颈问题,如何实现扩展
补充:分布式任务调度解决方案
- 数据库唯一约束,避免定时任务重复执行
- 使用分布式锁实现定时任务的调度控制
- 使用配置文件,redis,mysql,全局属性等作为调度的开关
- 使用分布式任务调度平台:XXL-JOB,Elastic-Job...
2 Quartz[群集环境]
使用quartz实现定时任务[单机版],若是部署多台机器,那么到了时间点,多台服务器便会同时均开始执行定时任务。
Quartz是能适用于分布式集群环境的,在同一时间只会有一台机器执行定时任务。
Quartz 中集群如何工作:
一个 Quartz 集群中的每个节点是一个独立的 Quartz 应用,它又管理着其他的节点。意思是你必须对每个节点分别启动或停止。不像许多应用服务器的集群,独立的 Quartz 节点并不与另一其的节点或是管理节点通信。Quartz 应用是通过数据库表来感知到另一应用的。离开了db将无法感知
2.1 导入依赖
<!-- spring boot2.x + quartz -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
2.2 数据库建表
下载之后解压,进入如下目录,创建数据库表:
11张表功能说明:
3 Xxl-Job入门详解
官网文档地址:https://www.xuxueli.com/xxl-job/ 源码:http://gitee.com/xuxueli0323/xxl-job
3.1 基础概念
调度中心:负责管理调度信息,按照调度配置发出调度请求,自身不承担业务代码。
任务执行器:负责接收调度请求并执行任务逻辑。
任务:专注于任务的处理。
调度中心会发出调度请求,任务执行器接收到请求之后会去执行任务,任务则专注于任务业务的处理。
3.2 搭建调度中心
- 创建数据表【请下载项目源码并解压,获取 “调度数据库初始化SQL脚本” 并执行即可。】
- xxl_job_lock:任务调度锁表;
- xxl_job_group:执行器信息表,维护任务执行器信息;
- xxl_job_info:调度扩展信息表: 用于保存XXL-JOB调度任务的扩展信息,如任务分组、任务名、机器地址、执行器、执行入参和报警邮件等等;
- xxl_job_log:调度日志表: 用于保存XXL-JOB任务调度的历史信息,如调度结果、执行结果、调度入参、调度机器和执行器等等;
- xxl_job_logglue:任务GLUE日志:用于保存GLUE更新历史,用于支持GLUE的版本回溯功能;
- xxl_job_registry:执行器注册表,维护在线的执行器和调度中心机器地址信息;
- xxl_job_user:系统用户表;
- 下载调度中心源码
- 配置部署调度中心
调度中心项目:统一管理任务调度平台上调度任务,负责触发调度执行,并且提供任务管理平台。
调度中心配置文件地址:/xxl-job/xxl-job-admin/src/main/resources/application.properties
### 数据库的连接信息修改为自己的数据库
### web
server.port=8080
server.servlet.context-path=/xxl-job-admin
### actuator
management.server.servlet.context-path=/actuator
management.health.mail.enabled=false
### resources
spring.mvc.servlet.load-on-startup=0
spring.mvc.static-path-pattern=/static/**
spring.resources.static-locations=classpath:/static/
### freemarker
spring.freemarker.templateLoaderPath=classpath:/templates/
spring.freemarker.suffix=.ftl
spring.freemarker.charset=UTF-8
spring.freemarker.request-context-attribute=request
spring.freemarker.settings.number_format=0.##########
### mybatis
mybatis.mapper-locations=classpath:/mybatis-mapper/*Mapper.xml
#mybatis.type-aliases-package=com.xxl.job.admin.core.model
### xxl-job, datasource
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/xxl_job?Unicode=true&serverTimezone=Asia/Shanghai&characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
### datasource-pool
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.hikari.minimum-idle=10
spring.datasource.hikari.maximum-pool-size=30
spring.datasource.hikari.auto-commit=true
spring.datasource.hikari.idle-timeout=30000
spring.datasource.hikari.pool-name=HikariCP
spring.datasource.hikari.max-lifetime=900000
spring.datasource.hikari.connection-timeout=10000
spring.datasource.hikari.connection-test-query=SELECT 1
### xxl-job, email
spring.mail.host=smtp.qq.com
spring.mail.port=25
spring.mail.username=xxx@qq.com
spring.mail.password=xxx
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.properties.mail.smtp.starttls.required=true
spring.mail.properties.mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory
### xxl-job, access token
xxl.job.accessToken=
### xxl-job, i18n (default is zh_CN, and you can choose "zh_CN", "zh_TC" and "en")
xxl.job.i18n=zh_CN
## xxl-job, triggerpool max size
xxl.job.triggerpool.fast.max=200
xxl.job.triggerpool.slow.max=100
### xxl-job, log retention days
xxl.job.logretentiondays=30
- 部署项目
可将项目编译打包部署,或者在idea里面启动调度中心。 - 访问调度中心
调度中心访问地址:http://localhost:8080/xxl-job-admin (该地址执行器将会使用到,作为回调地址)
默认登录账号 “admin/123456”, 登录后运行界面如下图所示。 - 配置调度中心的执行器
此处的AppName,会在创建任务时被选择,每个任务必然要选择一个执行器。
6.1 新增执行器:
自动注册和手动注册的区别和配置:
自动注册:执行器自动进行执行器注册,调度中心通过底层注册表可以动态发现执行器机器地址;
手动录入:人工手动录入执行器的地址信息,多地址逗号分隔,供调度中心使用;
6.2 在调度中心新建任务
6.2.1 执行器:
任务的绑定的执行器,任务触发调度时将会自动发现注册成功的执行器, 实现任务自动发现功能; 另一方面也可以方便的进行任务分组。
每个任务必须绑定一个执行器, 可在 "执行器管理" 进行设置。
6.2.2 路由策略:当执行器集群部署时,提供丰富的路由策略,包括
6.2.3 运行模式:
- BEAN模式:任务以JobHandler方式维护在执行器端;需要结合 "JobHandler" 属性匹配执行器中任务;
- GLUE模式(Java):任务以源码方式维护在调度中心;该模式的任务实际上是一段继承自IJobHandler的Java类代码并 "groovy" 源码方式维护,它在执行器项目中运行,可使用@Resource/@Autowire注入执行器里中的其他服务;
- GLUE模式(Shell):任务以源码方式维护在调度中心;该模式的任务实际上是一段 "shell" 脚本;
- GLUE模式(Python):任务以源码方式维护在调度中心;该模式的任务实际上是一段 "python" 脚本;
- GLUE模式(PHP):任务以源码方式维护在调度中心;该模式的任务实际上是一段 "php" 脚本;
- GLUE模式(NodeJS):任务以源码方式维护在调度中心;该模式的任务实际上是一段 "nodejs" 脚本;
- GLUE模式(PowerShell):任务以源码方式维护在调度中心;该模式的任务实际上是一段 "PowerShell" 脚本;
6.2.4 阻塞处理策略: - 单机串行(默认):调度请求进入单机执行器后,调度请求进入FIFO队列并以串行方式运行;
- 丢弃后续调度:调度请求进入单机执行器后,发现执行器存在运行的调度任务,本次请求将会被丢弃并标记为失败;
- 覆盖之前调度:调度请求进入单机执行器后,发现执行器存在运行的调度任务,将会终止运行中的调度任务并清空队列,然后运行本地调度任务;
6.2.5 JobHandler:
运行模式为 "BEAN模式" 时生效,对应执行器中新开发的JobHandler类“@JobHandler”注解自定义的value值;
6.2.6 子任务:
每个任务都拥有一个唯一的任务ID(任务ID可以从任务列表获取),当本任务执行结束并且执行成功时,将会触发子任务ID所对应的任务的一次主动调度。
- 与SpringBoot整合
如果项目中没有找到xxl-job-core
这个依赖,需要把这个依赖安装到本地的maven仓库。
<groupId>com.itheima</groupId>
<artifactId>xxl-job-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 继承Spring boot工程 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- xxl-job -->
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>2.2.0-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
7.1 创建配置文件
### web port
server.port=${port:8801}
### no web
#spring.main.web-environment=false
### log config
logging.config=classpath:logback.xml
### xxl-job admin address list, such as "http://address" or "http://address01,http://address02"
xxl.job.admin.addresses=http://localhost:8888/xxl-job-admin
### xxl-job, access token
xxl.job.accessToken=
### xxl-job executor appname 改成你XXL-JOB调度中心的执行器 AppName
xxl.job.executor.appname=xxl-job-executor-sample
### xxl-job executor registry-address: default use address to registry , otherwise use ip:port if address is null
xxl.job.executor.address=
### xxl-job executor server-info
xxl.job.executor.ip=
xxl.job.executor.port=${executor.port:9999}
### xxl-job executor log-path
xxl.job.executor.logpath=/data/applogs/xxl-job/jobhandler
### xxl-job executor log-retention-days
xxl.job.executor.logretentiondays=30
7.2 添加xxl-job配置
添加配置类:这个类主要是创建了任务执行器,参考官方案例编写,无须改动。
package com.itheima.xxljob.config;
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class XxlJobConfig {
private Logger logger = LoggerFactory.getLogger(XxlJobConfig.class);
@Value("${xxl.job.admin.addresses}")
private String adminAddresses;
@Value("${xxl.job.accessToken}")
private String accessToken;
@Value("${xxl.job.executor.appname}")
private String appName;
@Value("${xxl.job.executor.address}")
private String address;
@Value("${xxl.job.executor.ip}")
private String ip;
@Value("${xxl.job.executor.port}")
private int port;
@Value("${xxl.job.executor.logpath}")
private String logPath;
@Value("${xxl.job.executor.logretentiondays}")
private int logRetentionDays;
@Bean
public XxlJobSpringExecutor xxlJobExecutor() {
logger.info(">>>>>>>>>>> xxl-job config init.");
XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
xxlJobSpringExecutor.setAppName(appName);
xxlJobSpringExecutor.setAddress(address);
xxlJobSpringExecutor.setIp(ip);
xxlJobSpringExecutor.setPort(port);
xxlJobSpringExecutor.setAccessToken(accessToken);
xxlJobSpringExecutor.setLogPath(logPath);
xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);
return xxlJobSpringExecutor;
}
/**
* 针对多网卡、容器内部署等情况,可借助 "spring-cloud-commons" 提供的 "InetUtils" 组件灵活定制注册IP;
*
* 1、引入依赖:
* <dependency>
* <groupId>org.springframework.cloud</groupId>
* <artifactId>spring-cloud-commons</artifactId>
* <version>${version}</version>
* </dependency>
*
* 2、配置文件,或者容器启动变量
* spring.cloud.inetutils.preferred-networks: 'xxx.xxx.xxx.'
*
* 3、获取IP
* String ip_ = inetUtils.findFirstNonLoopbackHostInfo().getIpAddress();
*/
}
7.3 创建任务
@Component
public class HelloJob {
@Value("${server.port}")
private String appPort;
@XxlJob("helloJob")
public ReturnT<String> hello(String param) throws Exception {
System.out.println("helloJob:"+ LocalDateTime.now()+",端口号"+appPort);
return ReturnT.SUCCESS;
}
}
@XxlJob("helloJob")
这个一定要与调度中心新建任务的JobHandler的值保持一致,如下图:
7.4 测试
(1)首先启动调度中心
(2)启动xxl-job-demo项目,为了展示更好的效果,可以同时启动三个项目,用同一个JobHandler,查看处理方式。
在启动多个项目的时候,端口需要切换,连接xxl-job的执行器端口不同相同
服务一:默认启动8801端口,执行器端口为9999
idea中不用其他配置,直接启动项目即可
服务二:项目端口:8802,执行器端口:9998
idea配置如下:
- 编辑配置,Edit Configurations...