背景
一套软件,总会有各种各样的bug发生,后续需要业务补偿;又或业务需要,在某个时刻才触发逻辑,那么定时任务就理所当然的出现了。
比如在《Java JVM对象实例个数和空间占用大小》篇章中,我们用@Scheduled来实现简单的定时任务,感兴趣的可以前往滑一下。
什么是定时任务
定时任务是指在特定时间或时间间隔内执行的任务。在软件开发和系统运维中,定时任务经常用于自动化、批处理和数据处理等场景。
常见实现方式
Cron表达式:一种用于调度任务的时间表达式,广泛应用于各种计划任务、定时任务等场景。
Cron表达式由6个或7个字段构成,用空格分隔,分别表示秒、分、时、日、月、周、年(可选)。以下是一个示例Cron表达式:每个字段都有特定的取值范围和支持的特殊字符。例如,"*"代表所有可能的值,"/"用于指定字段的间隔值,"?"用于日和周字段中表示不指定值等。
举几个常见的例子:
0 0 12 * * ?:每天中午 12 点执行。
0 0/5 * * * ?:每 5 分钟执行一次。
0 0 8-18 ? * MON-FRI:周一至周五的 8 点到 18 点之间每小时执行一次
Timer类:Java标准库中提供的一种定时任务实现方式。它可以在指定时间执行任务,也可以在指定时间间隔内重复执行任务。以下是一个示例Timer类的代码:
//delay表示延迟多少毫秒开始执行任务,period表示执行任务的时间间隔
Timer timer = new Timer();
timer.schedule(new TimerTask() {
public void run() {
//执行任务
}
}, delay, period);
ScheduledThreadPoolExecutor:Java标准库中提供的另一种定时任务实现方式。它可以在指定时间执行任务,也可以在指定时间间隔内重复执行任务。与Timer类不同的是,ScheduledThreadPoolExecutor类使用线程池执行任务,可以避免单线程执行任务的问题。以下是一个示例ScheduledThreadPoolExecutor类的代码:
//delay表示延迟多少毫秒开始执行任务,period表示执行任务的时间间
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1);
executor.scheduleAtFixedRate(new Runnable() {
public void run() {
//执行任务
}
}, delay, period, TimeUnit.MILLISECONDS);
Quartz框架:Quartz框架是一种广泛使用的定时任务框架。它提供了丰富的功能和灵活的配置选项,可以满足各种定时任务需求。Quartz框架支持基于Cron表达式和时间间隔两种方式执行任务。以下是一个示例Quartz框架的代码:
JobDetail job = newJob(MyJob.class).withIdentity("job", "group").build();
Trigger trigger = newTrigger().withIdentity("trigger", "group").startNow().withSchedule(simpleSchedule().withIntervalInSeconds(5).repeatForever()).build();
Scheduler scheduler = new StdSchedulerFactory().getScheduler();
scheduler.scheduleJob(job, trigger);
scheduler.start();
在大型的分布式业务系统中,用上述几种方式来完成Job的话,先不论优点,缺点很明显:a.控制Job执行的时间(频率)是硬编码到应用中,无法灵活调整 b.部署的多个实例会同时触发逻辑,有可能出现bug c.多实例资源无法均衡使用 。。。what shall we do?
XXL_JOB
项目地址:https://github.com/xuxueli/xxl-job/tree/master
调度中心(xxl-job-admin):统一管理任务调度平台上调度任务,负责触发调度执行,并且提供任务管理平台。该模块可以参考:https://www.xuxueli.com/xxl-job/#源码仓库地址,部署比较简单,按照步骤走就行:执行DDL脚本、编译、启动。
客户端集成:
<!--maven依赖-->
<!-- http://repo1.maven.org/maven2/com/xuxueli/xxl-job-core/ -->
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>${最新稳定版本}</version>
</dependency>
#配置添加
xpilot.tools.xxl.job.executor.port = 1${server.port}
xpilot.tools.xxl.job.executor.ip =
xpilot.tools.xxl.job.executor.address =
xpilot.tools.xxl.job.executor.log-retention-days = 30
xpilot.tools.xxl.job.executor.log-path = /data/applogs/xxl-job/${spring.application.name}
xpilot.tools.xxl.job.executor.app-name = api-job-executor
# xxl_job admin addr
xpilot.tools.xxl.job.admin.addresses = http://ip:port/xxl-job-admin
xpilot.tools.xxl.job.enable = true
xpilot.tools.xxl.job.accessToken = 8ko6S2JmcHgS
//执行器配置
public class XxlJobConfig {
private String adminAddresses;
private String accessToken;
private String appname;
private String address;
private String ip;
private int port;
private String logPath;
private int logRetentionDays;
public XxlJobSpringExecutor xxlJobExecutor() {
log.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;
}
}
Job定义:使用@XxlJob 注解定义一个任务,交给调度中心管理。
@Component
public class SyncVideoJob {
@XxlJob("checkJobRunStateJob")
public void checkJobRunStateJob() {
XxlJobHelper.log("checkJobRunStateJob start...");
String param = XxlJobHelper.getJobParam();
XxlJobHelper.log("checkJobRunStateJob config job param:{}", param);
StopWatch watch = new StopWatch();
watch.start("checkJobRunStateJob");
//your biz
watch.stop();
XxlJobHelper.log("checkJobRunStateJob end...");
}
}
调度配置
打开任务调度中心,默认登录账号“admin/123456”,按照上述配置中的xxl.job.executor.app-name指定的名字,配置执行器和任务管理。
后期
上述步骤完成后,如果有配置报警邮件,剩下的多数就是每日关注下有没有邮件、任务执行的日志是什么,都可以通过登录平台查看,多多使用自然就会了。
关于传递的参数可能使用以下代码获取:
String param = XxlJobHelper.getJobParam();
//字符串转换为对象 方便运行时传递给业务
private SyncVideoReq initConfig(String param) {
return JacksonUtil.parse(param, SyncVideoReq.class);
}
学如逆水行舟,不进则退。
——《增广贤文》