在JAVA开发过程中经常会遇到使用定时任务的情况,现有的实现方式有以下几种:
1. Timer
2. ScheduledExecutorService
3. SpringTask
4. Quartz
5. XXL-JOB
crontab
要执行定时任务就需要对crontab有一定的了解, linux中的crontab命令定期检查是否有要执行的任务,若检测到便自动执行该任务,从而达到实现定时任务的目的
使用语法
# -u user指定用户 crontab [ -u user ] file # -l 列出目前的时程表 | -r 删除目前的时程表 | -e 通过vi文字编辑器设定 crontab [ -u user] { -l | -r | -e }
时间格式
# f1表示分钟,f2表示小时,f3表示一个月份中的第几日,f4表示月份,f5表示一个星期中的第几天,program表示要执行的程序 f1 f2 f3 f4 f5 program # 特别地,指定位置为*表示每个该单位时间都要执行一次,为a-b表示a-b时间段都要执行一次,*/n表示每n个单位时间间隔执行一次 0 7-12/3 * 12 * /bin/ls # 上述例子即12月内每日7点~12点每隔3个小时执行一次/bin/ls
Timer
Timer类位于java.util包下,可用于实现定时任务
/*
1000ms是延迟启动时间,2000ms是定时任务周期,即每2s执行一次
*/
new Timer("testTimer").schedule(new TimeTask() {
@Override
public void run() {
System.out.println("TimerTask");
}
}, 1000, 2000);
/*
timer有两种方法:schedule(任务结束时开始计算时间间隔)
scheduleAtFixedRat(任务开始时开始计算时间间隔)
date即起始时间,2000ms是定时任务周期,即从起始时间开始每2s执行一次
*/
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
try {
Date date = dateFormat.parse("2020-01-19 12:00:00.000");
new Timer("testTimer").scheduleAtFixedRate(new TimerTask () {
@Override
public void run() {
System.out.println("TimerTask");
}
}, date, 2000);
} catch(ParseException e) {
e.printStackTrace();
}
ScheduledExecutorService
ScheduledExecutorService在ExecutorService提供的功能上增加了延迟和定期执行任务的功能。因此,该方法可用于实现定时任务
/*
延迟1s启动,执行一次
*/
scheduledExecutorService.schedule(new Runnable() {
@Override
public void run() {
System.out.println("ScheduledTask");
}
}, 1, TimeUnit.SECONDS);
/*
延迟1s启动,每隔1s执行一次
*/
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
scheduledExectorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("ScheduledTask");
}
}, 1, 1, TimeUnit.SECONDS);
/*
延迟1s启动,在前一个任务执行完成后,延迟1s再执行
*/
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
Executors.newScheduledThreadPool(10);
scheduledExectorService.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
System.out.println("ScheduledTask");
}
}, 1, 1, TimeUnit.SECONDS);
SpringTask
Spring框架同样提供了实现定时任务的方式,可以看做一个轻量级的quartz框架。该实现分为2部分:
1. 任务类
2. 配置文件
定时任务类
@Service public class SpringTask { private static final Logger log = LoggerFactory.getLogger(SpringTask.class); /* cron格式: {秒} {分} {时} {日期} {月} {星期} 每隔5s执行一次 */ @Scheduled(cron = "1/5 * * * * *") public void task1() { log.info("SpringTask 定时任务"); } /* 延迟1s,每隔1s执行一次 */ @Scheduled(initialDelay = 1000, fixedRate = 1*1000) public void task1() { log.info("SpringTask 定时任务"); } }
2. 配置文件
```xml
< !-- 简易版 有且只有一个定时任务可用 -->
< task:annotation-driven />
< !-- 任务池版 有多个定时任务则用任务池,否则定时任务会顺序执行 -->
< task:executor id="executor" pool-size="10" />
< task:scheduler id="scheduler" pool-size="10" />
< task:annotation-driven executor="executor" scheduler="scheduler" />
Quartz
本篇主要介绍定时任务的不同实现方式,因此对于Quartz原理只做简要介绍
- Quartz 是一个完全由 Java 编写的开源作业调度框架,为在 Java 应用程序中进行作业调度提供了简单却强大的机制
- Quartz 可以与 J2EE 与 J2SE 应用程序相结合也可以单独使用
- Quartz 允许程序开发人员根据时间的间隔来调度作业
- Quartz 实现了作业和触发器的多对多的关系,还能把多个作业与不同的触发器关联
Quartz核心概念
- Scheduler:调度容器
- Job:Job接口类,即被调度的任务
- JobDetail :Job的描述类,job执行时的依据此对象的信息反射实例化出Job的具体执行对象
- Trigger:触发器,存放Job执行的时间策略。用于定义任务调度时间规则
- JobStore: 存储作业和调度期间的状态
- Calendar:指定排除的时间点(如排除法定节假日)
Quartz使用
- Maven依赖
<!-- quartz -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>
<!--调度器核心包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>4.3.4.RELEASE</version>
</dependency>
- Job实现
public class HelloWorldJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
String strTime = new SimpleDateFormat("HH-mm-ss").format(new Date());
System.out.println(strTime + ":Hello World! ");
}
}
- 调度器(可用listner在项目启动时执行)
public MyScheduler {
public static void main(String[] args) throws SchedulerException {
// 创建调度器
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Sccheduler scheduler = schedulerFactory.getScheduler();
// 创建JobDetail实例
JobDetail jobDetail = JobBuilder.newJob(HelloWorldJob.class).withIdentity("job1", "jobGroup1").build();
// 创建触发器Trigger实例
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "triggerGroup1")
.startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1).repeatForever)
.build();
// 开始执行
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
}
}
/*
上例是简单触发器,如下是crontab触发器
*/
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger2", "triggerGroup2")
.startNow()
.withSchedule(cronScheduler("0 42 10 * * ?"))
.build();
- 整合Spring 将上方的调度器改写成配置文件即可整合到Spring中
XXL-JOB
如果定时任务维护在每个微服务下,而微服务部署多个实例的情况下,会出现定时任务多次执行的情况。此外定时任务有时希望能实现动态修改任务的定时时间。
XXL-JOB即可满足该需求,它是一个轻量级分布式任务调度平台,内容采用Quartz定时框架实现,服务间通信通过RPC方式实现。
功能方面可参考官方文档
服务端实现过程
XXL-JOB源码下载
github地址
xxl-job-core是核心代码模块,xxl-job-admin为任务调度中心管理模块。仅部署可仅使用xxl-job-admin数据库创建
sql文件为:xxl-job/doc/db/tables_xxl_job.sql
在mysql数据库中执行该sql,能生成新的数据库xxl-job及相关数据库表xxl_job_* 目前仅支持mysql数据库,需支持其他数据库可修改其源码实现xxl-job-admin模块配置修改 修改xxl-job-admin下的配置文件:application.properties
主要需修改以下几处:- 数据库地址 (spring.datasource.url)
- 用户名 (spring.datasource.username)
- 密码 (spring.datasource.password)
- 端口号(server.port)默认为8080,可修改
- 访问地址(server.context-path)默认为xxl-job-admin,可修改
启动xxl-job-admin微服务 本地启动成功后,可通过访问本地地址来访问任务调度中心
用户名密码默认为: admin/123456
客户端实现过程
- Maven依赖
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>2.0.2</version>
</dependency>
- 修改配置文件
ip可不填,会自动识别注册
xxl:
job:
admin:
addresses: http://localhost:8080/xxl-job-admin
executor:
ip:
port: 9999
logpath: /data/applogs/xxl-job/jobhandler
appname: plat-job
accessToken:
- 编写Configuration配置项类 需保证服务端与客户端版本一致
@Configuration
public class XxlJobConfig {
@Value("${xxl.job.admin.addresses}")
private String adminAddresses;
@Value("${xxl.job.executor.appname}")
private String appName;
@Value("${xxl.job.executor.ip}")
private String ip;
@Value("${xxl.job.executor.port}")
private int port;
@Value("${xxl.job.accessToken}")
private String accessToken;
@Value("${xxl.job.executor.logpath}")
private String logPath;
private int logRetentionDays = -1;
@Bean(initMethod = "start", destoryMethod = "destory")
public XxlJobSpringExecutor xxlJobExecutor() {
System.out.println("xxl-job config init.");
XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
xxlJobSpringExecutor.setAppName(appName);
xxlJobSpringExecutorsetIp(ip);
xxlJobSpringExecutor.setPort(port);
xxlJobSpringExecutor.setAccessToken(accessToken);
xxlJobSpringExecutor.setLogPath(logPath);
xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);
return xxlJobSpringExecutor;
}
}
- 编写Handler类(实际处理任务) Handler在下例中命名为"TestHandler",需保证唯一
@JobHandler(value = "TestHandler")
@Component
public class TestHandler extends IJobHandler {
@Override
public ReturnT<String> execute(String param) throws Exception {
System.out.println("执行了TestHandler一次");
return SUCCESS;
}
}
- 启动客户端 启动成功后,服务器中会自动增加一个名为plat-job的执行器,用来执行定时任务
服务端页面配置
修改执行器 对执行器进行编辑,注册方式改为【手动录入】,机器地址写入客户端(微服务)具体的ip:port后,点击保存
任务管理编辑 通过如图方式即可动态更新定时计划
总结
由于每个微服务的ip和port可以在执行器的机器地址中,通过逗号分隔的方式,可增加多个客户端的地址,则解决了项目中多实例的问题。因此XXL-JOB满足了定时任务的额外需求