zoukankan      html  css  js  c++  java
  • 定时任务框架Quartz的使用

    一、什么是Quartz?

    Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,完全由Java开发,可以用来执行定时任务,类似于java.util.Timer。但是相较于Timer, Quartz增加了很多功能:

    • 持久性作业 - 就是保持调度定时的状态;
    • 作业管理 - 对调度作业进行有效的管理;

    二、java定时任务调度的实现方式

    (1)Timer

    特点是:简单易用,但由于所有任务都是由同一个线程来调度,因此所有任务都是串行执行的,同一时间只能有一个任务在执行,前一个任务的延迟或异常都将会影响到之后的任务;能实现简单的定时任务,稍微复杂点(或要求高一些)的定时任务却不好实现。

    (2)ScheduledExecutor

    鉴于Timer的缺陷,Java 5推出了基于线程池设计的ScheduledExecutor;
    特点:每一个被调度的任务都会由线程池中一个线程去执行,因此任务是并发执行的,相互之间不会受到干扰。需要注意的是,只有当任务的执行时间到来时,ScheduedExecutor 才会真正启动一个线程,其余时间 ScheduledExecutor 都是在轮询任务的状态。
    虽然用ScheduledExecutor和Calendar能够实现复杂任务调度,但实现起来还是比较麻烦,对开发还是不够友善。

    (3)Spring Scheduler

    spring对任务调度的实现支持,可以指定任务的执行时间,但对任务队列和线程池的管控较弱;一般集成于项目中,小任务很方便。

    (4)开源工具包 JCronTab

    JCronTab则是一款完全按照crontab语法编写的java任务调度工具。
    特点:

    • 可指定任务的执行时间;
    • 提供完全按照Unix的UNIX-POSIX crontab的格式来规定时间;
    • 支持多种任务调度的持久化方法,包括普通文件、数据库以及 XML 文件进行持久化;
    • JCronTab内置了发邮件功能,可以将任务执行结果方便地发送给需要被通知的人;
    • 设计和部署是高性能并可扩展。

    (5)开源工具包 Quartz

    • 具有强大的调度功能,很容易与spring集成,形成灵活可配置的调度功能;
    • 调度环境的持久化机制:可以保存并恢复调度现场,即使系统因为故障关闭,任务调度现场的数据并不会丢失;timer没有这些特点;
    • 灵活的应用方式:可以灵活的定义触发器调度的时间表,并可以对触发器与任务进行关联映射;
    • 分布式与集群能力;

    三、quartz的相关概念

    Scheduler:调度器,进行任务调度;quartz的大脑
    Job:业务job,亦可称业务组件;定时任务的具体执行业务需要实现此接口,调度器会调用此接口的execute方法完成我们的定时业务
    JobDetail:用来定义业务Job的实例,我们可以称之为quartz job,很多时候我们谈到的job指的是JobDetail
    Trigger:触发器,用来定义一个指定的Job何时被执行
    JobBuilder:Job构建器,用来定义或创建JobDetail的实例;JobDetail限定了只能是Job的实例
    TriggerBuilder:触发器构建器,用来定义或创建触发器的实例

    四、如何在springboot中集成Quartz

    (1)在pom.xml中添加依赖

     <!--quartz依赖-->
     <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-quartz</artifactId>
    </dependency>
    

    (2)appliaction.yml中添加配置(不可用,不知为何……问了同事,只说quartz官方推荐quartz.properties的配置方式)

    spring:
      quartz:
          # 将任务等保存化到数据库
          job-store-type: jdbc
          # 程序结束时会等待quartz相关的内容结束
          wait-for-jobs-to-complete-on-shutdown: true
          # QuartzScheduler启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录
          overwrite-existing-jobs: true
          # 这里居然是个map,搞得智能提示都没有,佛了
          properties:
            org:
              quartz:
              	# scheduler相关
                scheduler:
                  # scheduler的实例名
                  instanceName: scheduler
                  instanceId: AUTO
                # 持久化相关
                jobStore:
                  class: org.quartz.impl.jdbcjobstore.JobStoreTX
                  driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
                  # 表示数据库中相关表是QRTZ_开头的
                  tablePrefix: QRTZ_
                  useProperties: false
                # 线程池相关
                threadPool:
                  class: org.quartz.simpl.SimpleThreadPool
                  # 线程数
                  threadCount: 10
                  # 线程优先级
                  threadPriority: 5
                  threadsInheritContextClassLoaderOfInitializingThread: true
    

    (2')添加配置quartz.properties

    #quartz集群配置
    # ===========================================================================
    # Configure Main Scheduler Properties 调度器属性
    # ===========================================================================
    #调度标识名 集群中每一个实例都必须使用相同的名称
    org.quartz.scheduler.instanceName=DefaultQuartzScheduler
    #ID设置为自动获取 每一个必须不同
    org.quartz.scheduler.instanceid=AUTO
    #============================================================================
    # Configure ThreadPool
    #============================================================================
    #线程池的实现类(一般使用SimpleThreadPool即可满足几乎所有用户的需求)
    org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
    #指定线程数,至少为1(无默认值)(一般设置为1-100直接的整数合适)
    org.quartz.threadPool.threadCount = 25
    #设置线程的优先级(最大为java.lang.Thread.MAX_PRIORITY 10,最小为Thread.MIN_PRIORITY 1,默认为5)
    org.quartz.threadPool.threadPriority = 5
    #============================================================================
    # Configure JobStore
    #============================================================================
    # 信息保存时间 默认值60秒
    org.quartz.jobStore.misfireThreshold = 60000
    #数据保存方式为数据库持久化
    org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
    #数据库代理类,一般org.quartz.impl.jdbcjobstore.StdJDBCDelegate可以满足大部分数据库
    org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
    #JobDataMaps是否都为String类型
    org.quartz.jobStore.useProperties = true
    #数据库别名 随便取
    org.quartz.jobStore.dataSource = myDS
    #表的前缀,默认QRTZ_
    org.quartz.jobStore.tablePrefix = QRTZ_
    #是否加入集群
    org.quartz.jobStore.isClustered = true
    #调度实例失效的检查时间间隔
    org.quartz.jobStore.clusterCheckinInterval = 50000
    #============================================================================
    # Configure Datasources
    #============================================================================
    #数据库引擎
    #org.quartz.dataSource.myDS.driver = org.postgresql.Driver
    ##数据库连接
    #org.quartz.dataSource.myDS.URL = jdbc:postgresql://20.20.1.205:5432/quartz
    ##数据库用户
    #org.quartz.dataSource.myDS.user = postgres
    ##数据库密码
    #org.quartz.dataSource.myDS.password = passwd
    ##允许最大连接
    #org.quartz.dataSource.myDS.maxConnections = 5
    ##验证查询sql,可以不设置
    #org.quartz.dataSource.myDS.validationQuery=select 0 from dual
    

    (3) 直接上代码

    这次我们写的高级些,将调度任务持久化到数据库,对前端暴露接口,通过接口来操作定时任务,更加灵活且便于管理。
    https://blog.csdn.net/qq_37339399/article/details/108141744
    (i)新建Quartz相关的数据库表,SQL在哪里找?
    依赖包中找到Maven:org.quartz-scheduler:quartz:2.3.0 ==> org ==> quartz ==> impl ==> jdbcjobstore 文件夹下对应各个方言的sql文件,找到合适的就可以了

    各个表的含义:
    qrtz_fired_triggers; ------ 存储以触发的Trigger相关的状态信息,以及关联的JOb信息
    qrtz_paused_trigger_grps; ------ 存储已暂停的Trigger组的信息
    qrtz_scheduler_state; ------ 存储少量的有关Scheduler的状态信息
    qrtz_locks; ------ 存储程序的悲观锁信息
    qrtz_simple_triggers; ------ 存储simpleTrigger,包括重复次数,间隔,以及已执行次数
    qrtz_simprop_triggers; ------ 存储CalendarIntervalTrigger等其他类的trigger
    qrtz_cron_triggers; ------ 存储cronTrigger的基本信息,包括表达式,时区
    qrtz_blob_triggers; ------ 将Trigger作为Blob类型存储,用于用户自定义Trigger类型
    qrtz_triggers; ------ 存储已配置trigger的信息
    qrtz_job_details; ------ 存储每一个已配置的Job的详细信息
    qrtz_calendars; ------ 以Blob形式存储Quartz的calendar信息
    注:Blob数据类型:是一个变长的二进制字符串(二进制大对象)

    (ii)项目启动时加载

    /**
     * Quartz的相关配置,注册JobDetail和Trigger
     * 注意JobDetail和Trigger是org.quartz包下的,不是spring包下的,不要导入错误
     */
    @Component
    public class QuartzConfig {
       /**
         * 将job的实例化交给IOC去进行
         */
        public static class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements ApplicationContextAware {
            private transient AutowireCapableBeanFactory beanFactory;
            @Override
            public void setApplicationContext(final ApplicationContext context) {
               beanFactory = context.getAutowireCapableBeanFactory();
            }
            protected Object creatJobInstance(final TriggerFiredBundle bundle) throws Exception {
               final Object job = super.createJobInstance(bundle);
               //进行注入
               beanFactory.autowireBean(job);
               return job;
             }
         }
            //从quartz.properties文件中读取Quartz配置属性
            @Bean
            public Properties quartzProperties() throws IOException {
                PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
                propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
                propertiesFactoryBean.afterPropertiesSet();
                return propertiesFactoryBean.getObject();
            }
         /**
           * 配置JobFactory
           * @param applicationContext
           * @return
           */
          @Bean
          public JobFactory jobFactory(ApplicationContext applicationContext) {
              AutowiringSpringBeanJobFactory jobFactory = new AutowiringSpringBeanJobFactory();
              jobFactory.setApplicationContext(applicationContext);
              return jobFactory;
          }
         /**
           * 自定义配置任务工厂
           * @param jobFactory
           * @param dataSource
           * @return
           * @throws Exception
           */
          @Bean
          public SchedulerFactoryBean schedulerFactoryBean(JobFactory jobFactory, DataSource dataSource) throws  Exception {
              SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
              schedulerFactoryBean.setJobFactory(jobFactory);
              //设置覆盖已存在的任务
              schedulerFactoryBean.setOverwriteExistingJobs(true);
              //schedulerFactoryBean.setStartupDelay(2);
              //设置数据源,使用与项目统一数据源
              schedulerFactoryBean.setDataSource(dataSource);
              //设置上下文spring bean name
              schedulerFactoryBean.setApplicationContextSchedulerContextKey("applicationContext");
              //设置项目启动时开始任务
              schedulerFactoryBean.setAutoStartup(true);
              schedulerFactoryBean.setQuartzProperties(quartzProperties());
              return schedulerFactoryBean;
           }
    }
    

    (iii)controller层

    @RestController
    public class QuartzController {
        @Autowired
        private QuartzService quartzService;
       /**
         * 新增定时任务
         * @param jobDTO
         */ 
         @RequestMapping("/qz/addJob")
         public void addJob(@RequestBody JobDTO jobDTO) {
             quartzService.addJob(jobDTO);
         }
    }
    

    入参实体:

    @Data
    public class JobDTO {
        private String jobId;
        private String jobName;
        private String groupName;
        private String cron;
        private String description;
        private String status;
        private String parameter;
        private String type;
        private Class T;
        private String jobClass;
    }
    

    (iv)service层

    public interface QuartzService {
        void addJob(@RequestBody JobDTO jobDTO);
    }
    

    (v)serviceImpl层

    @Slf4j
    @Service
    public class QuartzServiceImpl implements QuartzService {
        @Autowired
        private SchedulerFactoryBean schedulerFactoryBean;
        @Override
        public void addJob(JobDTO jobDTO) {
        try {
            // 获取调度器
            Scheduler scheduler = schedulerFactoryBean.getScheduler();
            JobDetail jobDetail = JobBuilder.newJob(DynamicJob.class).withIdentity(jobDTO.getJobName(), jobDTO.getGroupName())
                                                                 //  .setJobData(castMap(jobDTO))
                                                                     .build();
            // 基于表达式构建触发器
            CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(jobDTO.getCron());
            // TriggerBuilder 用于构建触发器实例
            CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(jobDTO.getJobName(), jobDTO.getGroupName())
           .withSchedule(cronScheduleBuilder).build();
    
           //调度任务
           scheduler.scheduleJob(jobDetail, cronTrigger);
           scheduler.start();
           } catch (SchedulerException e) {
                e.printStackTrace();
           }
        }
    }
    

    (vi)创建个DynamicJob类实现Job接口,来写需要的定时业务逻辑

    @Slf4j
    @DisallowConcurrentExecution
    public class DynamicJob implements Job {
        @Override
        public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        // 根据不同的type 执行不同任务
        //JobDetail中的JobDataMap是共用的,从getMergedJobDataMap获取的JobDataMap是全新的对象
        JobDataMap map = jobExecutionContext.getMergedJobDataMap();
        String type = SpringUtil.objectToString(map.get("type"));
           switch (type){
              case "one":
                    this.doOne(map);
                    break;
              case "two":
                    this.doTwo(map);
                    break;
              case "three":
                    this.doThree(map);
                    break;
              default:
                    log.info("nothing todo");
                    break;
            }
         }
         private void doOne(JobDataMap map){
            log.info("do one,description:{},jobId:{}",map.get("jobDescription"),map.get("jobId"));
         }
         private void doTwo(JobDataMap map){
            log.info("do two,description:{},jobId:{}",map.get("jobDescription"),map.get("jobId"));
         }
         private void doThree(JobDataMap map){
             log.info("do three,description:{},jobId:{}",map.get("jobDescription"),map.get("jobId"));
         }
    }
    

    (vii)使用postman调用添加job接口

    localhost:8070/qz/addJob
    

    入参格式:

     {  
        "jobId":"26",
        "jobName":"job名称26",
        "cron": "*/5 * * * * ?",
        "type":"one"
      }
    

    可以在控制台看到每五秒钟打印一条定时日志,且入参的job信息已自动添加到数据库quartz相关表中。

  • 相关阅读:
    JAVA文件操作类和文件夹的操作代码示例
    java去除表达符号的正则表达式
    正则表达式以过滤特殊字符
    eclipse与myeclipse恢复已删除的文件和代码
    Windows 2003 Server R2 x64 IIS6.0 eWebEditor无法显示的问题
    获得每日,每周,每月的0点和24点的时间戳
    Access查询时间段 .
    java连接Access数据库的两种方法
    移动App专项测试
    linux性能评估-内存基础理解篇
  • 原文地址:https://www.cnblogs.com/zyzyBlog/p/13754321.html
Copyright © 2011-2022 走看看