Spring Boot 集成 Quartz一

377 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第6天,点击查看活动详情

前言

Quartz是一个完全由Java编写的开源任务调度的框架,通过触发器设置作业定时运行规则,控制作业的运行时间。其中quartz集群通过故障切换和负载平衡的功能,能给调度器带来高可用性和伸缩性。主要用来执行定时任务,如:定时发送信息、定时生成报表等等。

特性

  • 强大的调度功能,例如支持丰富多样的调度方法,可以满足各种常规及特殊需求;
  • 灵活的应用方式,例如支持任务和调度的多种组合方式,支持调度数据的多种存储方式;
  • 分布式和集群能力。

体系结构

图片.png

  • Job:预先定义的希望在未来时间能被调度程序执行的任务类。
  • JobDetail:定义任务实例。
  • Trigger:触发器,表明认为在什么时候执行。
  • Scheduler:用于与调度程序交互的主程序接口。

集成步骤

初始化脚本

Quartz的表一共有11张,从官网从官网下载www.quartz-scheduler.org/downloads/ 下载jar包,从jar包获取脚本执行到数据库即可。

图片.png

添加相关依赖

 <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的时候,是通过加载类的全路径的。

特殊说明

  1. 通过放射原理,加载定义的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执行日志等功能。