一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第6天,点击查看活动详情。
前言
Quartz是一个完全由Java编写的开源任务调度的框架,通过触发器设置作业定时运行规则,控制作业的运行时间。其中quartz集群通过故障切换和负载平衡的功能,能给调度器带来高可用性和伸缩性。主要用来执行定时任务,如:定时发送信息、定时生成报表等等。
特性
- 强大的调度功能,例如支持丰富多样的调度方法,可以满足各种常规及特殊需求;
- 灵活的应用方式,例如支持任务和调度的多种组合方式,支持调度数据的多种存储方式;
- 分布式和集群能力。
体系结构
- Job:预先定义的希望在未来时间能被调度程序执行的任务类。
- JobDetail:定义任务实例。
- Trigger:触发器,表明认为在什么时候执行。
- Scheduler:用于与调度程序交互的主程序接口。
集成步骤
初始化脚本
Quartz的表一共有11张,从官网从官网下载www.quartz-scheduler.org/downloads/ 下载jar包,从jar包获取脚本执行到数据库即可。
添加相关依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
配置类
也可以通过直接通过yml文件配置
@Configuration
public class ScheduleConfig {
@Bean
public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource) {
SchedulerFactoryBean factory = new SchedulerFactoryBean();
factory.setDataSource(dataSource);
//quartz参数
Properties prop = new Properties();
prop.put("org.quartz.scheduler.instanceName", "RenrenScheduler");
prop.put("org.quartz.scheduler.instanceId", "AUTO");
//线程池配置
prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");
prop.put("org.quartz.threadPool.threadCount", "20");
prop.put("org.quartz.threadPool.threadPriority", "5");
//JobStore方式
prop.put("org.quartz.jobStore.class", "org.quartz.impl.jdbcjobstore.JobStoreTX");
//集群配置
prop.put("org.quartz.jobStore.isClustered", "true");
prop.put("org.quartz.jobStore.clusterCheckinInterval", "15000");
prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "1");
prop.put("org.quartz.jobStore.misfireThreshold", "12000");
//表明开头
prop.put("org.quartz.jobStore.tablePrefix", "QRTZ_");
prop.put("org.quartz.jobStore.selectWithLockSQL", "SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = ?");
factory.setQuartzProperties(prop);
factory.setSchedulerName("RenrenScheduler");
//延时启动
factory.setStartupDelay(30);
factory.setApplicationContextSchedulerContextKey("applicationContextKey");
//可选,QuartzScheduler 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了
factory.setOverwriteExistingJobs(true);
//设置自动启动,默认为true
factory.setAutoStartup(true);
return factory;
}
}
Quartz中有两种存储方式:RAMJobStore,JobStoreSupport RAMJobStore是将trigger和job存储在内存中,存取速度非常快,但是由于其在系统被停止后所有的数据都会丢失。 JDBCJobStore是基于jdbc将trigger和job存储到数据库中,速度慢但是比较安全。
核心实现类
查询了相关网上关于Spring Bood集成Quartz的例子,发现都是以demo形式呈现,在实际的项目中还是会有许多问题。
public class BaseJob extends QuartzJobBean
{
private Logger logger = LoggerFactory.getLogger(getClass());
@Override
protected void executeInternal(JobExecutionContext context)
throws JobExecutionException
{
JobEntity scheduleJob = (JobEntity) context.getMergedJobDataMap()
.get(JobConstant.JOB_PARAM_KEY);
logger.info("scheduleJob:"+JSON.toJSONString(scheduleJob));
long startTime = System.currentTimeMillis();
logger.info("开始定时计时:"+startTime);
try
{
Object target = SpringContextUtils.getBean(scheduleJob.getJobClass());
Method method = target.getClass().getDeclaredMethod("invoke", String.class);
method.invoke(target, scheduleJob.getParams());
long times = System.currentTimeMillis() - startTime;
logger.info("任务执行完毕,任务ID:" + scheduleJob.getJobId() + " 总共耗时:" + times + "毫秒");
}
catch (Exception e)
{
logger.error("invoke job error",e);
}
}
}
@Autowired
private Scheduler scheduler;
@PostConstruct
public void init()
{
//通过数据库定义一张job配置表
List<JobEntity> scheduleJobList = this.list();
JobDataMap data = new JobDataMap();
for (JobEntity job : scheduleJobList)
{
data.put(JobConstant.JOB_PARAM_KEY, job);
JobKey jobKey = getJobKey(job.getJobId());
JobDetail jobDetail = JobBuilder.newJob(BaseJob.class)
.withIdentity(jobKey).usingJobData(data).build();
// 表达式调度构建器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder
.cronSchedule(job.getCronExp())
.withMisfireHandlingInstructionDoNothing();
TriggerKey triggerKey =getTriggerKey(job.getJobId());
// 按新的cronExpression表达式构建一个新的trigger
CronTrigger trigger = TriggerBuilder.newTrigger()
.withIdentity(triggerKey).startNow()
.withSchedule(scheduleBuilder).build();
try
{
if (!scheduler.isShutdown() && scheduler.getJobDetail(jobKey) != null)
{
logger.error("jobKey is already exists." + jobKey.toString());
return;
}
scheduler.deleteJob(jobKey);
scheduler.scheduleJob(jobDetail, trigger);
}
catch (SchedulerException e)
{
logger.error("init job error", e);
}
}
}
public List<JobEntity> list()
{
List<JobEntity> jobEntities = new ArrayList<>();
JobEntity jobEntity =new JobEntity();
//jobid
jobEntity.setJobId(1l);
//表达式
jobEntity.setCronExp("*/5 * * * * ?");
//job名称
jobEntity.setJobName("测试job");
//spring bean 的名称
jobEntity.setJobClass("testTask");
// 参数
jobEntity.setParams("测试");
jobEntities.add(jobEntity);
return jobEntities;
}
public static JobKey getJobKey(Long jobId)
{
return JobKey.jobKey(JobConstant.JOB_NAME + jobId);
}
/**
* 获取触发器key
*/
public static TriggerKey getTriggerKey(Long jobId)
{
return TriggerKey.triggerKey(JobConstant.JOB_NAME + jobId);
}
//所有的job定时任务的实现都需要继承此接口
public interface ITask {
void invoke(String params);
}
@Component("testTask")
public class TestTask implements ITask
{
private Logger log = LoggerFactory.getLogger(ITask.class);
@Override
public void invoke(String params)
{
log.debug("TestTask定时任务正在执行,参数为:{}", params);
}
}
说明:JobEntity 是job的实体类,定义了job的相关属性,可以将其转换为对应的表进行存储。其中 的JobClass只得是注入到spring容器中的的bean的名称。在没有集成spring的时候,是通过加载类的全路径的。
特殊说明
- 通过放射原理,加载定义的job执行类和执行对应的方法。所有的定时任务都是只需要实现ITask接口,这样做的目的是将业务实现类和job的核心类执行类进行分离,让业务类更专注业务实现。
Object target = SpringContextUtils.getBean(scheduleJob.getJobClass());
Method method = target.getClass().getDeclaredMethod("invoke", String.class);
method.invoke(target, scheduleJob.getParams());
2.job只需要加载为核心执行类,而不需要加载所有实现类
JobDetail jobDetail = JobBuilder.newJob(BaseJob.class)
.withIdentity(jobKey).usingJobData(data).build();
执行结果
22-04-07 23:17:30.054 [RenrenScheduler_Worker-20] INFO [] com.fw.service.BaseJob - scheduleJob:{"cronExp":"*/5 * * * * ?","jobClass":"testTask","jobId":1,"jobName":"测试job","params":"测试","runningFlag":false}
2022-04-07 23:17:30.055 [RenrenScheduler_Worker-20] INFO [] com.fw.service.BaseJob - 开始定时计时:1649344650055
2022-04-07 23:17:30.055 [RenrenScheduler_Worker-20] INFO [] com.fw.service.BaseJob - 任务执行完毕,任务ID:1 总共耗时:0毫秒
2022-04-07 23:17:30.177 [RenrenScheduler_Worker-1] INFO [] com.fw.service.BaseJob - scheduleJob:{"cronExp":"*/10 * * * * ?","jobClass":"testTask2","jobId":2,"jobName":"测试job2","params":"测试","runningFlag":false}
2022-04-07 23:17:30.177 [RenrenScheduler_Worker-1] INFO [] com.fw.service.BaseJob - 开始定时计时:1649344650177
2022-04-07 23:17:30.177 [RenrenScheduler_Worker-1] INFO [] com.fw.service.BaseJob - 任务执行完毕,任务ID:2 总共耗时:0毫秒
从结果可以看出,添加了两个job分别都间隔的执行成功。
结尾
本文讲解了Spring Boot 集成 Quartz,只是实现了基础核心功能,在实际的项目中还缺少job的执行操作类、以及job执行日志等功能。