• Quartz定时任务


    本文首次发布于My Blog,作者@张琦(Ian),转载请保留原文链接。

    Java - Quartz 定时任务

    Quartz是一个完全由Java编写的开源作业调度框架,为在Java应用程序中进行作业调度提供了简单却强大的机制。Quartz允许开发人员根据时间间隔来调度作业。

    官网:http://www.quartz-scheduler.org

    基础概念

    Job

    • 具体需要处理的业务逻辑。
    • 表示一个工作,要执行的具体内容。此接口中只有一个方法。要创建一个任务,必须得实现这个接口。该接口只有一个execute方法,任务每次被调用的时候都会执行这个execute方法的逻辑,类似TimerTask的run方法,在里面编写业务逻辑。
    public class TestJob implements Job {
          /**把要执行的操作,写在execute方法中  */
          @Override
          public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
              SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
              System.out.println("I can do something...");
              System.out.println(sdf.format(new Date()));
          }
      }
    
    • 生命周期:在每次调度器执行job时,它在调用execute方法前会创建一个新的job实例,当调用完成之后,关联的job对象实例会被释放,释放的实例会被垃圾回收机制回收。
    • JobBuilder:可向任务传递数据,通常情况下,我们使用它就可向任务类发送数据了,如有特别复杂的传递参数,它提供了一个传递递:JobDataMap对象的方法
    JobDetail jobDetail =  JobBuilder.newJob(TestJob.class).withIdentity("testJob","group1").build();
    

    JobDetail

    • 详细的任务描述,包括名称,关联的JobJob运行时所需要的参数等。
    • 用来保存我们任务的详细信息。一个JobDetail可以有多个Trigger,但是一个Trigger只能对应一个JobDetail。下面是JobDetail的一些常用的属性和含义:

    Trigger

    任务调度器,描述什么时候执行Job,多久执行一次。

    • SimpleTrigger 设置重复次数,重复执行间隔时长
    • CronTrigger 可以配置更复杂的触发时刻表,基于日历的作业触发器,而不像SimpleTrigger那样精确指定间隔时间,比SimpleTrigger更加常用。
      • Cron表达式:用于配置CronTrigger实例,是由7个表达式组成的字符串,描述了时间表的详细信息。
        • 格式为:[秒][分][时][日][月][周][年]
        • Cron表达式小技巧:

            1. ‘L’和‘W’可以一起组合使用

            2. 周字段英文字母不区分大小写即MOM与mom相同

            3. 利用工具,在线生成cron表达式:http://cron.qqe2.com/

    Scheduler

      调度容器,JobTrigger都需要在容器中注册,被容器统一管理。

      代表一个Quartz的独立运行容器,Trigger和JobDetail可以注册到Scheduler中,两者在Scheduler中拥有各自的组及名称,组及名称是Scheduler查找定位容器中某一对象的依据,Trigger的组及名称必须唯一,JobDetail的组和名称也必须唯一(但可以和Trigger的组和名称相同,因为它们是不同类型的)。Scheduler定义了多个接口方法,允许外部通过组及名称访问和控制容器中Trigger和JobDetail。

      Scheduler可以将Trigger绑定到某一JobDetail中,这样当Trigger触发时,对应的Job就被执行。一个Job可以对应多个Trigger,但一个Trigger只能对应一个Job。

      可以通过SchedulerFactory创建一个Scheduler实例。Scheduler拥有一个SchedulerContext,它类似于ServletContext,保存着Scheduler上下文信息,Job和Trigger都可以访问SchedulerContext内的信息。SchedulerContext内部通过一个Map,以键值对的方式维护这些上下文数据,SchedulerContext为保存和获取数据提供了多个put()和getXxx()的方法。可以通过Scheduler# getContext()获取对应的SchedulerContext实例;

    SchedulerFactory schedulerfactory=new StdSchedulerFactory();
        Scheduler scheduler = schedulerfactory.getScheduler();
    
        DirectSchedulerFactory factory = DirectSchedulerFactory.getInstance();
        try {
            Scheduler scheduler = factory.getScheduler();
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    

    SchedulerFactory:

    • 使用一组参数(java.util.Properties)来创建和出书啊Quartz调度器
    • 配置参数一般存储在quartz.properties中
    • 调用getScheduler方法就能创建和初始化调度器

    Cron表达式

    范式

    表达式 说明
    */5 * * * * ? 每隔5秒执行一次
    0 */1 * * * ? 每隔1分钟执行一次
    0 0 23 * * ? 每天23点执行一次
    0 0 23 ? * * 每天23点执行一次
    0 0 23 * * ? * 每天23点执行一次
    0 0 23 * * ? 2016 2016年每天23点执行一次
    0 0 1 * * ? 每天1点执行一次
    0 0 1 1 * ? 每月1号1点执行一次
    0 * 14 * * ? 每天14:00点到14:59期间,每隔1分钟执行一次
    0 0-5 14 * * ? 每天14:00点到14:05期间,每隔1分钟执行一次
    0 0 23 L * ? 每月最后一天23点执行一次
    0 0 1 ? * L 每周星期六1点执行一次
    0 26,29,33 * * * ? 在26分、29分、33分执行一次
    0 0 0,13,18,21 * * ? 每天的0点、13点、18点、21点都执行一次
    0 0 0 ? * 6#3 每月第3个星期六

    表达式

    字段 可选值 特殊字符
    0 - 59 , - * /
    0 - 59 , - * /
    0 - 23 , - * /
    1 - 31 , - * / ? L W
    1 - 12 , - * /
    1(Sun) - 7(Sat) , - * / ? L #
    年(可选) 1970 - 2099 , - * /

    特殊字符

    字符 说明
    , 指定多个数值
    - 指定一个范围
    * 代表整个时间段
    / 多长时间执行一次
    ? 不确定的值
    L 每月最后一天(日)/每月最后一个星期六(周)
    W 最近的工作日
    # 每月第N个工作日

    示例

    QuartzManager

    QuartzManager中我封装了很多方法,包括JobTrigger的添加,删除,修改等,这里只列举了其中一个。

    public class QuartzManager {
        /**
         * 添加任务
         *
         * @param jobName      任务名
         * @param jobGroup     任务组
         * @param jobClass     任务实现类
         * @param jobData      任务数据
         * @param triggerName  触发器名
         * @param triggerGroup 触发器组
         * @param triggerCron  触发器Cron表示式
         * @return
         */
        public Boolean addJob(String jobName, String jobGroup, Class<? extends Job> jobClass, Map<String, Object> jobData, 
                                String triggerName, String triggerGroup, String triggerCron) {
            try {
                // 任务名,任务组,任务执行类
                JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroup).build();
                if (jobData != null && jobData.size() > 0)
                    jobDetail.getJobDataMap().putAll(jobData);
    
                // 触发器
                TriggerBuilder<Trigger> triggerBuilder = TriggerBuilder.newTrigger();
                // 触发器名,触发器组
                triggerBuilder.withIdentity(triggerName, triggerGroup);
                // 触发器时间设定
                triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(triggerCron));
                // 创建Trigger对象
                CronTrigger trigger = (CronTrigger) triggerBuilder.build();
    
                // 调度容器设置JobDetail和Trigger
                scheduler.scheduleJob(jobDetail, trigger);
                return true;
            } catch (SchedulerException e) {
                e.printStackTrace();
                return false;
            }
        }
        ...
    }
    

    MyJob

    public class MyJob implements Job {
        private final DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
        @Override
        public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
            JobDetail jobDetail = jobExecutionContext.getJobDetail();
            JobDataMap jobDataMap = jobDetail.getJobDataMap();
            String x = jobDataMap.getString("x");
            String y = jobDataMap.getString("y");
            System.out.println(String.format("%s x=%s, y=%s", df.format(new Date()), x, y));
        }
    }
    

    QuartzTest

    public class QuartzTest {
        @Test
        public void test() throws Exception {
            SchedulerFactory schedulerFactory = new StdSchedulerFactory();
            Scheduler scheduler = schedulerFactory.getScheduler();
            QuartzManager quartzManager = new QuartzManager(scheduler);
    
            // 每1000毫秒执行一次,重复执行3次,共执行4次
            quartzManager.addJob("myJob", "test", MyJob.class, Collections.singletonMap("x", "1"), 1000L, 3);
            quartzManager.startScheduler();
    
            while (Thread.activeCount() > 0)
                Thread.yield();
        }
    }
    

      其实Quartz JAR文件的org.quartz包下就包含了一个quartz.properties属性配置文件并提供了默认设置。如果需要调整默认配置,可以在类路径下建立一个新的quartz.properties,它将自动被Quartz加载并覆盖默认的设置。

    1. 先来了解一下Quartz的默认属性配置文件,默认配置如下:

      ①集群的配置,这里不使用集群
      
      org.quartz.scheduler.instanceName = DefaultQuartzScheduler
      
      org.quartz.scheduler.rmi.export = false
      
      org.quartz.scheduler.rmi.proxy = false
      
      org.quartz.scheduler.wrapJobExecutionInUserTransaction = false
      
      ②配置调度器的线程池
      
      org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
      
      org.quartz.threadPool.threadCount = 10
      
      org.quartz.threadPool.threadPriority = 5
      
      org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true
      
      ③配置任务调度现场数据保存机制
      
      org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
      
      Quartz的属性配置文件主要包括三方面的信息:
      
      1)集群信息;
      
      2)调度器线程池;
      
      3)任务调度现场数据的保存。
      
         如果任务数目很大时,可以通过增大线程池的大小得到更好的性能。默认情况下,Quartz采用org.quartz.simpl.RAMJobStore保存任务的现场数据,顾名思义,信息保存在RAM内存中,我们可以通过以下设置将任务调度现场数据保存到数据库中。
           
      
    2. 使用数据库保存任务调度现场数据配置如下:

      …
      
      org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
      
      org.quartz.jobStore.tablePrefix = QRTZ_①数据表前缀
      
      org.quartz.jobStore.dataSource = qzDS②数据源名称
      
      ③定义数据源的具体属性
      
      org.quartz.dataSource.qzDS.driver = oracle.jdbc.driver.OracleDriver
      
      org.quartz.dataSource.qzDS.URL = jdbc:oracle:thin:@localhost:1521:ora9i
      
      org.quartz.dataSource.qzDS.user = stamen
      
      org.quartz.dataSource.qzDS.password = abc
      
      org.quartz.dataSource.qzDS.maxConnections = 10
      

        要将任务调度数据保存到数据库中,就必须使用org.quartz.impl.jdbcjobstore.JobStoreTX代替原来的org.quartz.simpl.RAMJobStore并提供相应的数据库配置信息。首先①处指定了Quartz数据库表的前缀,在②处定义了一个数据源,在③处具体定义这个数据源的连接信息。

        你必须事先在相应的数据库中创建Quartz的数据表(共8张),在Quartz的完整发布包的docs/dbTables目录下拥有对应不同数据库的SQL脚本。

    仅供参考:

    # Default Properties file for use by StdSchedulerFactory  
    # to create a Quartz Scheduler Instance, if a different  
    # properties file is not explicitly specified.  
    #  
    # ===========================================================================  
    # Configure Main Scheduler Properties 调度器属性  
    # ===========================================================================  
    org.quartz.scheduler.instanceName: DefaultQuartzScheduler  
    #org.quartz.scheduler.instanceid:AUTO  
    org.quartz.scheduler.rmi.export: false  
    org.quartz.scheduler.rmi.proxy: false  
    org.quartz.scheduler.wrapJobExecutionInUserTransaction: false  
    # ===========================================================================    
    # Configure ThreadPool 线程池属性    
    # ===========================================================================  
    #线程池的实现类(一般使用SimpleThreadPool即可满足几乎所有用户的需求)  
    org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool  
    #指定线程数,至少为1(无默认值)(一般设置为1-100直接的整数合适)  
    org.quartz.threadPool.threadCount: 10  
    #设置线程的优先级(最大为java.lang.Thread.MAX_PRIORITY 10,最小为Thread.MIN_PRIORITY 1,默认为5)  
    org.quartz.threadPool.threadPriority: 5  
    #设置SimpleThreadPool的一些属性  
    #设置是否为守护线程  
    #org.quartz.threadpool.makethreadsdaemons = false  
    #org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true  
    #org.quartz.threadpool.threadsinheritgroupofinitializingthread=false  
    #线程前缀默认值是:[Scheduler Name]_Worker  
    #org.quartz.threadpool.threadnameprefix=swhJobThead;  
    # 配置全局监听(TriggerListener,JobListener) 则应用程序可以接收和执行 预定的事件通知  
    # ===========================================================================  
    # Configuring a Global TriggerListener 配置全局的Trigger监听器  
    # MyTriggerListenerClass 类必须有一个无参数的构造函数,和 属性的set方法,目前2.2.x只支持原始数据类型的值(包括字符串)  
    # ===========================================================================  
    #org.quartz.triggerListener.NAME.class = com.swh.MyTriggerListenerClass  
    #org.quartz.triggerListener.NAME.propName = propValue  
    #org.quartz.triggerListener.NAME.prop2Name = prop2Value  
    # ===========================================================================  
    # Configuring a Global JobListener 配置全局的Job监听器  
    # MyJobListenerClass 类必须有一个无参数的构造函数,和 属性的set方法,目前2.2.x只支持原始数据类型的值(包括字符串)  
    # ===========================================================================  
    #org.quartz.jobListener.NAME.class = com.swh.MyJobListenerClass  
    #org.quartz.jobListener.NAME.propName = propValue  
    #org.quartz.jobListener.NAME.prop2Name = prop2Value  
    # ===========================================================================    
    # Configure JobStore 存储调度信息(工作,触发器和日历等)  
    # ===========================================================================  
    # 信息保存时间 默认值60秒  
    org.quartz.jobStore.misfireThreshold: 60000  
    #保存job和Trigger的状态信息到内存中的类  
    org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore  
    # ===========================================================================    
    # Configure SchedulerPlugins 插件属性 配置  
    # ===========================================================================  
    # 自定义插件    
    #org.quartz.plugin.NAME.class = com.swh.MyPluginClass  
    #org.quartz.plugin.NAME.propName = propValue  
    #org.quartz.plugin.NAME.prop2Name = prop2Value  
    #配置trigger执行历史日志(可以看到类的文档和参数列表)  
    org.quartz.plugin.triggHistory.class = org.quartz.plugins.history.LoggingTriggerHistoryPlugin    
    org.quartz.plugin.triggHistory.triggerFiredMessage = Trigger {1}.{0} fired job {6}.{5} at: {4, date, HH:mm:ss MM/dd/yyyy}    
    org.quartz.plugin.triggHistory.triggerCompleteMessage = Trigger {1}.{0} completed firing job {6}.{5} at {4, date, HH:mm:ss MM/dd/yyyy} with resulting trigger instruction code: {9}    
    #配置job调度插件  quartz_jobs(jobs and triggers内容)的XML文档    
    #加载 Job 和 Trigger 信息的类   (1.8之前用:org.quartz.plugins.xml.JobInitializationPlugin)  
    org.quartz.plugin.jobInitializer.class = org.quartz.plugins.xml.XMLSchedulingDataProcessorPlugin  
    #指定存放调度器(Job 和 Trigger)信息的xml文件,默认是classpath下quartz_jobs.xml  
    org.quartz.plugin.jobInitializer.fileNames = my_quartz_job2.xml    
    #org.quartz.plugin.jobInitializer.overWriteExistingJobs = false    
    org.quartz.plugin.jobInitializer.failOnFileNotFound = true    
    #自动扫描任务单并发现改动的时间间隔,单位为秒  
    org.quartz.plugin.jobInitializer.scanInterval = 10  
    #覆盖任务调度器中同名的jobDetail,避免只修改了CronExpression所造成的不能重新生效情况  
    org.quartz.plugin.jobInitializer.wrapInUserTransaction = false  
    # ===========================================================================    
    # Sample configuration of ShutdownHookPlugin  ShutdownHookPlugin插件的配置样例  
    # ===========================================================================  
    #org.quartz.plugin.shutdownhook.class = org.quartz.plugins.management.ShutdownHookPlugin  
    #org.quartz.plugin.shutdownhook.cleanShutdown = true  
    #  
    # Configure RMI Settings 远程服务调用配置  
    #  
    #如果你想quartz-scheduler出口本身通过RMI作为服务器,然后设置“出口”标志true(默认值为false)。  
    #org.quartz.scheduler.rmi.export = false  
    #主机上rmi注册表(默认值localhost)  
    #org.quartz.scheduler.rmi.registryhost = localhost  
    #注册监听端口号(默认值1099)  
    #org.quartz.scheduler.rmi.registryport = 1099  
    #创建rmi注册,false/never:如果你已经有一个在运行或不想进行创建注册  
    # true/as_needed:第一次尝试使用现有的注册,然后再回来进行创建  
    # always:先进行创建一个注册,然后再使用回来使用注册  
    #org.quartz.scheduler.rmi.createregistry = never  
    #Quartz Scheduler服务端端口,默认是随机分配RMI注册表  
    #org.quartz.scheduler.rmi.serverport = 1098  
    #true:链接远程服务调度(客户端),这个也要指定registryhost和registryport,默认为false  
    # 如果export和proxy同时指定为true,则export的设置将被忽略  
    #org.quartz.scheduler.rmi.proxy = false
    

    参考:

  • 相关阅读:
    MySQL、Redis 和 MongoDB 的优缺点
    解决数据库高并发
    数据库事务
    Mysql 数据库存储的原理?
    CSRF
    MVC模型和MVT模型
    AJAX
    正则表达式-re模块
    ddt-数据驱动测试
    python-时间格式化
  • 原文地址:https://www.cnblogs.com/uniquezhangqi/p/9199275.html
Copyright © 2020-2023  润新知