精华内容
参与话题
问答
  • 任务调度

    2018-09-25 12:15:50
    任务调度 定时任务调度:基于给定的时间点、给定的时间间隔、给定的执行次数自动执行的任务。     unix crontab命令 crontab [-u user] file crontab [-u user] [ -e | -l | -r]       ...
      1. 任务调度

    定时任务调度:基于给定的时间点、给定的时间间隔、给定的执行次数自动执行的任务。

     

     

        1. unix crontab命令

    crontab [-u user] file

    crontab [-u user] [ -e | -l | -r]

     

     

     

        1. Timer

    介绍

    Timer,简单无门槛,一般也没人用。单线程。

     

    Timer位于java.util包下,其内部包含且仅包含一个后台线程(TimeThread)对多个业务任务(TimeTask)进行定时定频率的调度。

     

    https://pic4.zhimg.com/80/v2-4fb47836439037bec04cadbb07c548f3_hd.jpg

    参数说明:

    task:所要执行的任务,需要extends TimeTask override run()

    time/firstTime:首次执行任务的时间

    period:周期性执行Task的时间间隔,单位是毫秒

    delay:执行task任务前的延时时间,单位是毫秒

    很显然,通过上述的描述,我们可以实现:

    延迟多久后执行一次任务;指定时间执行一次任务;延迟一段时间,并周期性执行任务;指定时间,并周期性执行任务;

     

    注意点

    1:如果time/firstTime指定的时间,在当前时间之前,会发生什么呢?

    在时间等于或者超过time/firstTime的时候,会执行task!也就是说,如果time/firstTime指定的时间在当前时间之前,就会立即得到执行。

    延时问题!

     

    2:schedule和scheduleAtFixedRate有什么区别?

    scheduleAtFixedRate:每次执行时间为上一次任务开始起向后推一个period间隔,也就是说下次执行时间相对于上一次任务开始的时间点,因此执行时间不会延后,但是存在任务并发执行的问题。
    schedule:每次执行时间为上一次任务结束后推一个period间隔,也就是说下次执行时间相对于上一次任务结束的时间点,因此执行时间会不断延后。

     

    3:如果执行task发生异常,是否会影响其他task的定时调度?

    如果TimeTask抛出RuntimeException,那么Timer会停止所有任务的运行!

     

    4:Timer的一些缺陷?

    前面已经提及到Timer背后是一个单线程,因此Timer存在管理并发任务的缺陷:所有任务都是由同一个线程来调度,所有任务都是串行执行,意味着同一时间只能有一个任务得到执行,而前一个任务的延迟或者异常会影响到之后的任务。
    其次,Timer的一些调度方式还算比较简单,无法适应实际项目中任务定时调度的复杂度。

     

    5:停止和移除

    cancel():终止Timer计时器,丢弃所有当前已安排的任务(TimeTask也存在cancel()方法,不过终止的是TimeTask)

    purge():从计时器的任务队列中移除已取消的任务,并返回个数

     

     

    代码示例

     

    package schedule.timer;

     

    import java.text.SimpleDateFormat;

    import java.util.Calendar;

    import java.util.Timer;

    import java.util.TimerTask;

     

    /**

     * Timer背后是一个单线程,因此Timer存在管理并发任务的缺陷:所有任务都是由同一个线程来调度,所有任务都是串行执行,意味着同一时间只能有一个任务得到执行,而前一个任务的延迟或者异常会影响到之后的任务。

     * 其次,Timer的一些调度方式还算比较简单,无法适应实际项目中任务定时调度的复杂度。

     *

     *

     */

    public class TimerTest {

        public static void main(String[] args) {

            Timer timer = new Timer();

           

            timer.schedule(new MyTask(), Calendar.getInstance().getTime(), 1000);

        }

    }

     

    class MyTask extends TimerTask{

        @Override

        public void run() {

            System.out.println("start at :"+new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(this.scheduledExecutionTime()));

           

            try {

                Thread.sleep(2000);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

        }

    }

     

        1. ScheduledExecutorService

    介绍

    JDK5之后便提供了基于线程池的定时任务调度:ScheduledExecutorService。

    设计理念:每一个被调度的任务都会被线程池中的一个线程去执行,因此任务可以并发执行,而且相互之间不受影响。

     

    示例

     

    package schedule.scheduledExecutor;

     

    import java.util.Date;

    import java.util.concurrent.Executors;

    import java.util.concurrent.ScheduledExecutorService;

    import java.util.concurrent.TimeUnit;

     

    public class ScheduledExecutorServiceTest {

        public static void main(String[] args) {

            ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);

            scheduledExecutorService.scheduleAtFixedRate(new ExecutorDemo(), 1000, 2000, TimeUnit.MILLISECONDS);

        }

    }

     

    class ExecutorDemo implements Runnable{

        @Override

        public void run() {

            System.out.println("执行"+new Date());

        }

    }

     

     

        1. spring task

    spring @Scheduled注解,一般集成于项目中,小任务很方便。

     

    fixedRate和fixedDelay的区别

    fixedRate :每隔多少毫秒执行一次该方法, 以上一次执行开始时间计算

    fixedDelay:当一次方法执行完毕之后,延迟多少毫秒再执行该方法

     

     

    例子1 注解方式

    package schedule.springTask1;

     

    import org.springframework.beans.BeansException;

    import org.springframework.context.ApplicationContext;

    import org.springframework.context.support.ClassPathXmlApplicationContext;

     

    public class SpringTaskAnnotationTest {

        public static void main(String[] args) throws BeansException {

            ApplicationContext ctx = new ClassPathXmlApplicationContext("/schedule/springTask1/spring-mvc.xml");

        }

    }

     

     

    package schedule.springTask1;

     

    import java.util.Date;

     

    import org.springframework.scheduling.annotation.Scheduled;

    import org.springframework.stereotype.Component;

     

    /**

     * 基于注解的定时器

     */

    @Component

    public class SpringTaskAnnotation {

     

        // 定时计算。每一秒执行一次

        @Scheduled(cron = "0/1 * * * * *")

        public void show() {

            System.out.println(new Date() + " : Annotationis show run");

        }

     

        /**

         * 心跳更新。启动时执行一次,之后每隔2秒执行一次

         */

        @Scheduled(fixedRate = 1000 * 2)

        public void print() {

            System.out.println(new Date() + " : Annotationis print run");

        }

     

        // 启动加载缓存, 以上一次执行完为准

        @Scheduled(fixedDelay = 1 * 1000)

        public void init() {

            System.out.println(new Date() + ": Annotationis init run");

        }

    }

     

    <beans xmlns="http://www.springframework.org/schema/beans"

        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

        xmlns:p="http://www.springframework.org/schema/p"

        xmlns:task="http://www.springframework.org/schema/task"

        xmlns:context="http://www.springframework.org/schema/context"

        xmlns:aop="http://www.springframework.org/schema/aop"

        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd 

            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd   

            http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd">

     

        <!-- 定时器注解开关, 可以配置 scheduler参数 -->

        <task:annotation-driven />

        <!-- bean注解开关 -->

        <context:annotation-config />

        <!-- 自动扫描的包名 -->

        <context:component-scan base-package="schedule.springTask1" />

     

    </beans>   

     

     

    例子2 xml方式

    package schedule.springTask2;

     

    import org.springframework.beans.BeansException;

    import org.springframework.context.ApplicationContext;

    import org.springframework.context.support.ClassPathXmlApplicationContext;

     

    public class SpringTaskXmlTest {

     

        public static void main(String[] args) throws BeansException {

            ApplicationContext ctx = new ClassPathXmlApplicationContext("/schedule/springTask2/spring-mvc.xml");

        }

    }

     

     

    package schedule.springTask2;

     

    import java.util.Date;

     

    /**

     * 基于xml的定时器

     */

    public class SpringTaskXml {

     

        public void show() {

            System.out.println(new Date() + " : XMl is show run");

        }

     

        public void print() {

            System.out.println(new Date() + " : XMl print run");

        }

    }

     

     

    <beans xmlns="http://www.springframework.org/schema/beans"

        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

        xmlns:p="http://www.springframework.org/schema/p"

        xmlns:task="http://www.springframework.org/schema/task"

        xmlns:context="http://www.springframework.org/schema/context"

        xmlns:aop="http://www.springframework.org/schema/aop"

        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd 

            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd   

            http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd">

     

        <bean id="task" class="schedule.springTask2.SpringTaskXml"></bean>

     

        <task:scheduled-tasks>

            <!-- 这里表示的是每隔五秒执行一次 -->

            <task:scheduled ref="task" method="show"

                cron="*/5 * * * * ?" />

            <task:scheduled ref="task" method="print"

                cron="*/10 * * * * ?" />

        </task:scheduled-tasks>

     

    </beans>

     

     

     

     

     

     

     

        1. Spring Quartz

    开源工具 Quartz,分布式集群开源工具,可以说是中小型公司必选,当然也视自身需求而定。

     

    介绍

    https://pic3.zhimg.com/80/v2-4b73fe0a6efe3484883b16a9bb347ab9_hd.jpg

     

    两种方式实现Spirng定时任务:

    1.继承自org.springframework.scheduling.quartz.QuartzJobBean
    2.作业类即普通的java类,不需要继承自任何基类。

     

    定时器任务有两种触发器:

    1.按照一定频度调用任务,在Spring Quartz中对应的触发器为:org.springframework.scheduling.quartz.SimpleTriggerBean
    2.按照指定时间调用任务,在Spring Quartz中对应的调度器为:org.springframework.scheduling.quartz.CronTriggerBean

     

     

    说明:
    1、从代码上来看,有XxxBuilder、XxxFactory,说明Quartz用到了Builder、Factory模式,还有非常易懂的链式编程风格。
    2、Quartz有3个核心概念:调度器(Scheduler)、任务(Job&JobDetail)、触发器(Trigger)。(一个任务可以被多个触发器触发,一个触发器只能触发一个任务)
    3、注意当Scheduler调度Job时,实际上会通过反射newInstance一个新的Job实例(待调度完毕后销毁掉),同时会把JobExecutionContext传递给Job的execute方法,Job实例通过JobExecutionContext访问到Quartz运行时的环境以及Job本身的明细数据。
    4、JobDataMap可以装载任何可以序列化的数据,存取很方便。需要注意的是JobDetail和Trigger都可以各自关联上JobDataMap。JobDataMap除了可以通过上述代码获取外,还可以在YourJob实现类中,添加相应setter方法获取。
    5、Trigger用来告诉Quartz调度程序什么时候执行,常用的触发器有2种:SimpleTrigger(类似于Timer)、CronTrigger(类似于Linux的Crontab)。
    6、实际上,Quartz在进行调度器初始化的时候,会加载quartz.properties文件进行一些属性的设置,比如Quartz后台线程池的属性(threadCount)、作业存储设置等。它会先从工程中找,如果找不到那么就是用quartz.jar中的默认的quartz.properties文件。
    7、Quartz存在监听器的概念,比如任务执行前后、任务的添加等,可以方便实现任务的监控。

     

     

    cronExpression配置说明

    字段

    允许值

    允许的特殊字符

    0-59

    , - * /

    0-59

    , - * /

    小时

    0-23

    , - * /

    日期

    1月31日

    , - * ? / L W C

    月份

    1-12 或者 JAN-DEC

    , - * /

    星期

    1-7 或者 SUN-SAT

    , - * ? / L C #

    年(可选)

    留空, 1970-2099

    , - * /

     

     

    特殊字符说明:

    字符

    意义

    *

    表示所有值;

    ?

    表示未说明的值,即不关心它为何值;

    -

    表示一个指定的范围;

    ,

    表示附加一个可能值;

    /

    符号前表示开始时间,符号后表示每次递增的值;

    L(“last”)

    (“last”) “L” 用在day-of-month字段意思是

    W(“weekday”)

    只能用在day-of-month字段。用来描叙最接近指定天的工作日(周一到周五)。例如:在day-of-month字段用“15W”指“最接近这个月第15天的工作日”,即如果这个月第15天是周六,那么触发器将会在这个月第14天即周五触发;如果这个月第15天是周日,那么触发器将会在这个月第16 天即周一触发;如果这个月第15天是周二,那么就在触发器这天触发。注意一点:这个用法只会在当前月计算值,不会越过当前月。“W”字符仅能在day- of-month指明一天,不能是一个范围或列表。也可以用“LW”来指定这个月的最后一个工作日。

    #

    只能用在day-of-week字段。用来指定这个月的第几个周几。例:在day-of-week字段用”6#3”指这个月第3个周五(6指周五,3指第3个)。如果指定的日期不存在,触发器就不会触发。

    C

    指和calendar联系后计算过的值。例:在day-of-month 字段用“5C”指在这个月第5天或之后包括calendar的第一天;在day-of-week字段用“1C”指在这周日或之后包括calendar的第一天。

     

     

    Cron表达式教程

    这里给出一些常用的示例:
    0 15 10 ? * 每天10点15分触发
    0 15 10 ? 2017 2017年每天10点15分触发
    0 14 * ? 每天下午的 2点到2点59分每分触发
    0 0/5 14 ? 每天下午的 2点到2点59分(整点开始,每隔5分触发)
    0 0/5 14,18 ? 每天下午的 2点到2点59分、18点到18点59分(整点开始,每隔5分触发)
    0 0-5 14 ? 每天下午的 2点到2点05分每分触发
    0 15 10 ? * 6L 每月最后一周的星期五的10点15分触发
    0 15 10 ? * 6#3 每月的第三周的星期五开始触发
    我们可以通过一些Cron在线工具非常方便的生成,比如http://www.pppet.net/等。

     

     

    CronTrigger

    CronTriggers往往比SimpleTrigger更有用,如果您需要基于日历的概念,而非SimpleTrigger完全指定的时间间隔,复发的发射工作的时间表。 CronTrigger,你可以指定触发的时间表如“每星期五中午”,或“每个工作日9:30时”,甚至“每5分钟一班9:00和10:00逢星期一上午,星期三星期五“。 即便如此,SimpleTrigger一样,CronTrigger拥有的startTime指定的时间表时生效,指定的时间表时,应停止(可选)结束时间。

    Cron表达式

    cron的表达式被用来配置CronTrigger实例。 cron的表达式是字符串,实际上是由七子表达式,描述个别细节的时间表。这些子表达式是分开的空白,代表:

    1. Seconds

    2. Minutes

    3. Hours

    4. Day-of-Month

    5. Month

    6. Day-of-Week

    7. Year (可选字段)

    例 "0 0 12 ? * WED" 在每星期三下午12:00 执行,

    个别子表达式可以包含范围, 例如,在前面的例子里("WED")可以替换成 "MON-FRI", "MON, WED, FRI"甚至"MON-WED,SAT". “*” 代表整个时间段.

    每一个字段都有一套可以指定有效值,如

    Seconds (秒) :可以用数字0-59 表示,

    Minutes(分) :可以用数字0-59 表示,

    Hours(时) :可以用数字0-23表示,

    Day-of-Month(天) :可以用数字1-31 中的任一一个值,但要注意一些特别的月份

    Month(月) :可以用0-11 或用字符串 “JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV and DEC” 表示

    Day-of-Week(每周):可以用数字1-7表示(1 = 星期日)或用字符口串“SUN, MON, TUE, WED, THU, FRI and SAT”表示

    “/”:为特别单位,表示为“每”如“0/15”表示每隔15分钟执行一次,“0”表示为从“0”分开始, “3/20”表示表示每隔20分钟执行一次,“3”表示从第3分钟开始执行

    “?”:表示每月的某一天,或第周的某一天

    “L”:用于每月,或每周,表示为每月的最后一天,或每个月的最后星期几如“6L”表示“每月的最后一个星期五”

    “W”:表示为最近工作日,如“15W”放在每月(day-of-month)字段上表示为“到本月15日最近的工作日”

    ““#”:是用来指定“的”每月第n个工作日,例 在每周(day-of-week)这个字段中内容为"6#3" or "FRI#3" 则表示“每月第三个星期五”

     

     

    例子 1 cron简单和复杂例子

    可指定执行类和指定方法,可以注入service

     

    package schedule.quartz1;

     

    import org.springframework.beans.BeansException;

    import org.springframework.context.ApplicationContext;

    import org.springframework.context.support.ClassPathXmlApplicationContext;

     

    public class SpringTaskAnnotationTest {

        public static void main(String[] args) throws BeansException {

            ApplicationContext ctx = new ClassPathXmlApplicationContext("/schedule/quartz1/spring-mvc.xml");

        }

    }

     

     

    package schedule.quartz1;

     

    public class SimpleJob {

        protected void execute() {

            System.out.println("简单定时任务执行中…");

        }

    }

     

     

    package schedule.quartz1;

     

    import org.quartz.JobExecutionContext;

    import org.quartz.JobExecutionException;

    import org.springframework.scheduling.quartz.QuartzJobBean;

     

    public class ComplexJob extends QuartzJobBean {

     

        private int timeout;

     

        // 调度工厂实例化后,经过timeout时间开始执行调度

        public void setTimeout(int timeout) {

            this.timeout = timeout;

        }

     

        /**

         * 要调度的具体任务

         */

        @Override

        protected void executeInternal(JobExecutionContext context) throws JobExecutionException {

            System.out.println("复杂定时任务执行中…");

        }

    }

     

     

     

    <beans xmlns="http://www.springframework.org/schema/beans"

        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

        xmlns:p="http://www.springframework.org/schema/p"

        xmlns:task="http://www.springframework.org/schema/task"

        xmlns:context="http://www.springframework.org/schema/context"

        xmlns:aop="http://www.springframework.org/schema/aop"

        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd 

            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd   

            http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd">

           

        <bean id="simpleJob" class="schedule.quartz1.SimpleJob"></bean>

       

        <!-- 使用 MethodInvokingJobDetailFactoryBean, 指定特定方法 -->

        <bean id="simpleJobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">

            <property name="targetObject" ref="simpleJob" />

            <property name="targetMethod" value="execute" />

            <property name="concurrent" value="false" /><!-- 作业不并发调度 -->

        </bean>

       

        <!-- 简单触发器,使用 SimpleTriggerFactoryBean -->

        <bean id="simpleTrigger"  class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">

            <property name="jobDetail" ref="simpleJobDetail" />

            <property name="startDelay" value="1000" /><!-- 调度工厂实例化后,经过1000秒开始执行调度 -->

            <property name="repeatInterval" value="2000" /><!-- 2秒调度一次 -->

        </bean>

     

     

        <!-- 高级设置, 给作业传递数据 -->

        <bean id="complexJobDetail" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">

            <property name="jobClass" value="schedule.quartz1.ComplexJob" />

     

            <property name="jobDataMap">

                <map>

                    <entry key="timeout" value="1" />

                </map>

            </property>

     

            <property name="durability" value="true" />

        </bean>

     

        <!-- 计划触发器,使用 CronTriggerFactoryBean -->

        <bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">

            <property name="jobDetail" ref="complexJobDetail" />

            <!--<property name="cronExpression" value="0/5 * * ? * SAT-SUN" /> -->

            <property name="cronExpression" value="0/5 * * ? * *" />

        </bean>

     

        <!-- 调度器 -->

        <bean

            class="org.springframework.scheduling.quartz.SchedulerFactoryBean">

            <property name="triggers">

                <list>

                    <ref bean="simpleTrigger" />

                    <ref bean="cronTrigger" />

                </list>

            </property>

        </bean>

     

    </beans>

     

     

    例子 2 Scheduler start/stop manager配置方式

    可以指定执行类(继承job),但不能注入service

     

    package schedule.quartz2;

     

    import org.quartz.SchedulerException;

    import org.springframework.beans.BeansException;

    import org.springframework.context.ApplicationContext;

    import org.springframework.context.support.ClassPathXmlApplicationContext;

     

    public class SpringTaskAnnotationTest {

        public static void main(String[] args) throws BeansException, InterruptedException, SchedulerException {

            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/schedule/quartz2/spring-mvc.xml");

           

            QuartzManager quartzManager = applicationContext.getBean(QuartzManager.class);

            quartzManager.initJobs();

           

           

            quartzManager.addJob("xxxx", "yyyy", "zzzz", "gggg",MyJob.class, "0/3 * * ? * *");

           

           

            Thread.sleep(2000);

            System.out.println("--change to 2s--");

           

            quartzManager.trigger("xxxxcc", "yyyy", "zzzzx", "ggggx",MyJob2.class, "0/1 * * ? * *");

           

           

            quartzManager.modifyJobTime("xxxx", "yyyy", "zzzz", "gggg", "0/2 * * ? * *");

           

    //      Thread.sleep(20000);

           

            System.out.println("--stop--");

           

    //      quartzManager.shutdownJobs();

            Thread.sleep(20000);

        }

    }

     

     

    package schedule.quartz2;

     

    import java.util.HashSet;

    import java.util.List;

     

    import org.apache.commons.lang3.StringUtils;

    import org.quartz.CronScheduleBuilder;

    import org.quartz.CronTrigger;

    import org.quartz.JobBuilder;

    import org.quartz.JobDetail;

    import org.quartz.JobKey;

    import org.quartz.Scheduler;

    import org.quartz.SchedulerException;

    import org.quartz.Trigger;

    import org.quartz.TriggerBuilder;

    import org.quartz.TriggerKey;

    import org.springframework.beans.factory.annotation.Autowired;

     

    public class QuartzManager {

     

        private Scheduler scheduler;

       

        @Autowired

        private IJobService jobService;

       

        //初始化所有的jobs

        public void initJobs() {

            Long count = jobService.listQuartzEntity(null);

           

            if(0!=count) {

                System.out.println(".....");

            }

        }

     

        /**

         * @Description: 添加一个定时任务

         *

         * @param jobName          任务名

         * @param jobGroupName     任务组名

         * @param triggerName      触发器名

         * @param triggerGroupName 触发器组名

         * @param jobClass         任务

         * @param cron             时间设置,参考quartz说明文档

         */

        @SuppressWarnings({ "unchecked", "rawtypes" })

        public void addJob(String jobName, String jobGroupName, String triggerName, String triggerGroupName, Class jobClass, String cron) {

            try {

                // 任务名,任务组,任务执行类

                JobKey jobKey = JobKey.jobKey(jobName, jobGroupName);

                JobDetail jobDetail = null;

                if(!scheduler.checkExists(jobKey)){

                    jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroupName).build();

                }else{

                    jobDetail = scheduler.getJobDetail(jobKey);

                }

               

                TriggerKey triggerKey = new TriggerKey(triggerName, triggerGroupName);

                if(!scheduler.checkExists(triggerKey)){

                    // 触发器

                    TriggerBuilder<Trigger> triggerBuilder = TriggerBuilder.newTrigger();

                    // 触发器名,触发器组

                    triggerBuilder.withIdentity(triggerName, triggerGroupName);

                    triggerBuilder.startNow();

                    // 触发器时间设定

                    triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(cron));

                    // 创建Trigger对象

                    CronTrigger trigger = (CronTrigger) triggerBuilder.build();

                   

                    // 调度容器设置JobDetailTrigger

                    scheduler.scheduleJob(jobDetail, trigger);

    //              scheduler.rescheduleJob(triggerKey, trigger);

                   

                    //https://stackoverflow.com/questions/34176482/unable-to-store-job-because-one-already-exists-with-this-identification

                   

                    // 启动

                    if (!scheduler.isShutdown()) {

                        scheduler.start();

                    }

                }

            } catch (Exception e) {

                throw new RuntimeException(e);

            }

        }

       

        public void addJobs(String jobName, String jobGroupName, List<SingleTrigger> triggers, Class jobClass, String cron) {

            try {

                // 任务名,任务组,任务执行类

                JobDetail jobDetail = scheduler.getJobDetail(JobKey.jobKey(jobName, jobGroupName));

                if(null==jobDetail){

                    jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroupName).build();

                }

               

                if(null!=triggers && !triggers.isEmpty()){

                    HashSet<Trigger> triggerSet = new HashSet<Trigger>();

                   

                    for (SingleTrigger singleTrigger : triggers) {

                        if(null==scheduler.getTrigger(new TriggerKey(singleTrigger.getTriggerName(), singleTrigger.getTriggerGroup()))){

                            // 触发器

                            TriggerBuilder<Trigger> triggerBuilder = TriggerBuilder.newTrigger();

                            // 触发器名,触发器组

                            triggerBuilder.withIdentity(singleTrigger.getTriggerName(), singleTrigger.getTriggerGroup());

                            triggerBuilder.startNow();

                            // 触发器时间设定

                            triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(singleTrigger.getTriggerCron()));

                            // 创建Trigger对象

                            CronTrigger trigger = (CronTrigger) triggerBuilder.build();

                           

                            triggerSet.add(trigger);

                        }

                    }

                   

                    // 调度容器设置JobDetailTrigger

                    scheduler.scheduleJob(jobDetail, triggerSet, true);

                   

                    // 启动

                    if (!scheduler.isShutdown()) {

                        scheduler.start();

                    }

                }

            } catch (Exception e) {

                throw new RuntimeException(e);

            }

        }

       

       

       

        /**

         * 触发任务

         * @throws SchedulerException

         */

        public void trigger(String jobName, String jobGroupName, String triggerName, String triggerGroupName, Class jobClass, String cron) throws SchedulerException {

            JobDetail jobDetail = scheduler.getJobDetail(JobKey.jobKey(jobName, jobGroupName));

           

            //没有对应的jobtrigger

            if(null==jobDetail && null==scheduler.getTrigger(new TriggerKey(triggerName, triggerGroupName))) {

                addJob(jobName, jobGroupName, triggerName, triggerGroupName, jobClass, cron);

            }else {

                JobKey key = new JobKey(jobName, jobGroupName);

                scheduler.triggerJob(key);

            }

        }

       

        /**

         * 停止任务

         * @throws SchedulerException

         */

        public void pauseJob(String jobName, String jobGroupName, String triggerName, String triggerGroupName, Class jobClass, String cron) throws SchedulerException {

            JobDetail jobDetail = scheduler.getJobDetail(JobKey.jobKey(jobName, jobGroupName));

           

            //没有对应的jobtrigger

            if(null!=jobDetail && null!=scheduler.getTrigger(new TriggerKey(triggerName, triggerGroupName))) {

                JobKey key = new JobKey(jobName, jobGroupName);

                scheduler.pauseJob(key);

            }

        }

       

        /**

         * 恢复任务

         * @throws SchedulerException

         */

        public void resumeJob(String jobName, String jobGroupName, String triggerName, String triggerGroupName, Class jobClass, String cron) throws SchedulerException {

            JobDetail jobDetail = scheduler.getJobDetail(JobKey.jobKey(jobName, jobGroupName));

           

            //没有对应的jobtrigger

            if(null!=jobDetail && null!=scheduler.getTrigger(new TriggerKey(triggerName, triggerGroupName))) {

                JobKey key = new JobKey(jobName, jobGroupName);

                scheduler.resumeJob(key);

            }

        }  

     

        /**

         * @Description: 修改一个任务的触发时间

         *

         * @param jobName

         * @param jobGroupName

         * @param triggerName      触发器名

         * @param triggerGroupName 触发器组名

         * @param cron             时间设置,参考quartz说明文档

         */

        public void modifyJobTime(String jobName, String jobGroupName, String triggerName, String triggerGroupName, String cron) {

            try {

                TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroupName);

                CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);

                if (trigger == null) {

                    return;

                }

     

                String oldTime = trigger.getCronExpression();

                if (!oldTime.equalsIgnoreCase(cron)) {

                    /** 方式一 :调用 rescheduleJob 开始 */

                    // 触发器

                    TriggerBuilder<Trigger> triggerBuilder = TriggerBuilder.newTrigger();

                    // 触发器名,触发器组

                    triggerBuilder.withIdentity(triggerName, triggerGroupName);

                    triggerBuilder.startNow();

                    // 触发器时间设定

                    triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(cron));

                    // 创建Trigger对象

                    trigger = (CronTrigger) triggerBuilder.build();

                    // 方式一 :修改一个任务的触发时间

                    scheduler.rescheduleJob(triggerKey, trigger);

                    /** 方式一 :调用 rescheduleJob 结束 */

     

                    /** 方式二:先删除,然后在创建一个新的Job */

                    // JobDetail jobDetail =

                    // scheduler.getJobDetail(JobKey.jobKey(jobName, jobGroupName));

                    // Class<? extends Job> jobClass = jobDetail.getJobClass();

                    // removeJob(jobName, jobGroupName, triggerName,

                    // triggerGroupName);

                    // addJob(jobName, jobGroupName, triggerName, triggerGroupName,

                    // jobClass, cron);

                    /** 方式二 :先删除,然后在创建一个新的Job */

                }

            } catch (Exception e) {

                throw new RuntimeException(e);

            }

        }

       

       

        /**

         * @Description: 移除一个trigger

         *

         * @param jobName

         * @param jobGroupName

         * @param triggerName

         * @param triggerGroupName

         */

        public void removeTrigger(String jobName, String jobGroupName, String triggerName, String triggerGroupName) {

            try {

                TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroupName);

     

                scheduler.pauseTrigger(triggerKey);// 停止触发器

                scheduler.unscheduleJob(triggerKey);// 移除触发器

            } catch (Exception e) {

                throw new RuntimeException(e);

            }

        }

       

     

        /**

         * @Description: 移除一个任务

         *

         * @param jobName

         * @param jobGroupName

         * @param triggerName

         * @param triggerGroupName

         */

        public void removeJob(String jobName, String jobGroupName, String triggerName, String triggerGroupName) {

            try {

                TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroupName);

     

                scheduler.pauseTrigger(triggerKey);// 停止触发器

                scheduler.unscheduleJob(triggerKey);// 移除触发器

                scheduler.deleteJob(JobKey.jobKey(jobName, jobGroupName));// 删除任务

            } catch (Exception e) {

                throw new RuntimeException(e);

            }

        }

     

        /**

         * @Description:启动所有定时任务

         */

        public void startJobs() {

            try {

                scheduler.start();

            } catch (Exception e) {

                throw new RuntimeException(e);

            }

        }

     

        /**

         * @Description:关闭所有定时任务

         */

        public void shutdownJobs() {

            try {

                if (!scheduler.isShutdown()) {

                    scheduler.shutdown();

                }

            } catch (Exception e) {

                throw new RuntimeException(e);

            }

        }

     

        public Scheduler getScheduler() {

            return scheduler;

        }

     

        public void setScheduler(Scheduler scheduler) {

            this.scheduler = scheduler;

        }

     

        public String transCron(String time) {

            String seconds = StringUtils.substringAfterLast(time, ":");

            String minute = StringUtils.substringAfter(time, ":").substring(0, 2);

            String hour = StringUtils.substringAfter(time, " ").substring(0, 2);

            String day = StringUtils.substringAfterLast(time, "-").substring(0, 2);

            String month = StringUtils.substringAfter(time, "-").substring(0, 2);

            return seconds + " " + minute + " " + hour + " " + day + " " + month + " ?";

        }

    }

     

     

    package schedule.quartz2;

     

    import lombok.Data;

     

    @Data

    public class QuartzEntity{

        private String jobName;//任务名称

        private String jobGroup;//任务分组

        private String description;//任务描述

        private String jobClassName;//执行类

        private String cronExpression;//执行时间

        private String triggerName;//执行时间

        private String triggerState;//任务状态

       

        private String oldJobName;//任务名称 用于修改

        private String oldJobGroup;//任务分组 用于修改

    }

     

     

    package schedule.quartz2;

     

    import org.quartz.Job;

    import org.quartz.JobExecutionContext;

    import org.quartz.JobExecutionException;

    import org.slf4j.Logger;

    import org.slf4j.LoggerFactory;

     

    public class MyJob implements Job {

     

        private Logger logger = LoggerFactory.getLogger(MyJob.class);

     

        @Override

        public void execute(JobExecutionContext context) throws JobExecutionException {

            try {

                logger.info("test】动态定时调度测试");

            } catch (Exception e) {

                e.printStackTrace();

            }

        }

    }

     

    package schedule.quartz2;

     

    import org.quartz.Job;

    import org.quartz.JobExecutionContext;

    import org.quartz.JobExecutionException;

    import org.slf4j.Logger;

    import org.slf4j.LoggerFactory;

     

    public class MyJob2 implements Job {

     

        private Logger logger = LoggerFactory.getLogger(MyJob2.class);

     

        @Override

        public void execute(JobExecutionContext context) throws JobExecutionException {

            try {

                logger.info("test】动态定时调度测试 ------------2");

            } catch (Exception e) {

                e.printStackTrace();

            }

        }

    }

     

    package schedule.quartz2;

     

    import java.util.List;

     

    public interface IJobService {

        List<QuartzEntity> listQuartzEntity(QuartzEntity quartz,Integer pageNo,Integer pageSize);

        Long listQuartzEntity(QuartzEntity quartz); 

    }

     

     

    package schedule.quartz2;

    import java.util.List;

     

    import org.springframework.beans.factory.annotation.Autowired;

    import org.springframework.stereotype.Service;

    import org.springframework.util.StringUtils;

     

    @Service("jobService")

    public class JobServiceImpl implements IJobService {

     

        @Autowired

        private DynamicQuery dynamicQuery;

     

        @Override

        public List<QuartzEntity> listQuartzEntity(QuartzEntity quartz,

                Integer pageNo, Integer pageSize) {

            StringBuffer nativeSql = new StringBuffer();

            nativeSql.append("SELECT job.JOB_NAME as jobName,job.JOB_GROUP as jobGroup,job.DESCRIPTION as description,job.JOB_CLASS_NAME as jobClassName,");

            nativeSql.append("cron.CRON_EXPRESSION as cronExpression,tri.TRIGGER_NAME as triggerName,tri.TRIGGER_STATE as triggerState,");

            nativeSql.append("job.JOB_NAME as oldJobName,job.JOB_GROUP as oldJobGroup ");

            nativeSql.append("FROM qrtz_job_details AS job LEFT JOIN qrtz_triggers AS tri ON job.JOB_NAME = tri.JOB_NAME ");

            nativeSql.append("LEFT JOIN qrtz_cron_triggers AS cron ON cron.TRIGGER_NAME = tri.TRIGGER_NAME ");

            nativeSql.append("WHERE tri.TRIGGER_TYPE = 'CRON'");

            Object[] params = new  Object[]{};

            if(!StringUtils.isEmpty(quartz.getJobName())){//加入JobName搜索其他条件自行实现

                nativeSql.append(" AND job.JOB_NAME = ?");

                params = new Object[]{quartz.getJobName()};

            }

            return null;

        }

     

        @Override

        public Long listQuartzEntity(QuartzEntity quartz) {

            return 0L;

           

    //      StringBuffer nativeSql = new StringBuffer();

    //      nativeSql.append("SELECT COUNT(*)");

    //      nativeSql.append("FROM qrtz_job_details AS job LEFT JOIN qrtz_triggers AS tri ON job.JOB_NAME = tri.JOB_NAME ");

    //      nativeSql.append("LEFT JOIN qrtz_cron_triggers AS cron ON cron.TRIGGER_NAME = tri.TRIGGER_NAME ");

    //      nativeSql.append("WHERE tri.TRIGGER_TYPE = 'CRON'");

    //      return dynamicQuery.nativeQueryCount(nativeSql.toString(), new Object[]{});

        }

    }

     

     

    package schedule.quartz2;

    import java.util.List;

    /**

     * 扩展SpringDataJpa, 支持动态jpql/nativesql查询并支持分页查询

     * 使用方法:注入ServiceImpl

     */

    public interface DynamicQuery {

       

       

    }

     

     

    package schedule.quartz2;

    import org.springframework.stereotype.Repository;

     

    @Repository

    public class DynamicQueryImpl implements DynamicQuery {

       

       

    }

     

     

    <beans xmlns="http://www.springframework.org/schema/beans"

        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

        xmlns:p="http://www.springframework.org/schema/p"

        xmlns:task="http://www.springframework.org/schema/task"

        xmlns:context="http://www.springframework.org/schema/context"

        xmlns:aop="http://www.springframework.org/schema/aop"

        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd 

            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd   

            http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd">

        <context:component-scan base-package="schedule.quartz2"></context:component-scan>

     

        <bean id="startQuartz" lazy-init="true" autowire="no"

            class="org.springframework.scheduling.quartz.SchedulerFactoryBean">

            <property name="triggers">

                <list>

                    <!--

                    <ref bean="cronTrigger" />

                     -->

                </list>

            </property>

        </bean>

     

        <bean id="quartzManager" class="schedule.quartz2.QuartzManager" lazy-init="false" init-method="startJobs">

            <!--这个对象一定要注入,这样类才能进行管理,还有在类型要用get set方法,不然会报错。 -->

            <property name="scheduler" ref="startQuartz" />

        </bean>

     

     

        <!-- 高级设置, 给作业传递数据 -->

        <bean id="complexJobDetail" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">

            <property name="jobClass" value="schedule.quartz2.MyJob" />

     

            <property name="jobDataMap">

                <map>

                    <entry key="timeout" value="1" />

                </map>

            </property>

     

            <property name="durability" value="true" />

        </bean>

     

        <!-- 计划触发器,使用 CronTriggerFactoryBean -->

        <bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">

            <property name="jobDetail" ref="complexJobDetail" />

            <!--<property name="cronExpression" value="0/5 * * ? * SAT-SUN" /> -->

            <property name="cronExpression" value="0/5 * * ? * *" />

        </bean>

    </beans>

     

     

     

     

     

     

     

    package schedule.quartz2;

     

    import java.util.ArrayList;

    import java.util.Arrays;

    import java.util.List;

     

    import org.quartz.SchedulerException;

    import org.springframework.beans.BeansException;

    import org.springframework.context.ApplicationContext;

    import org.springframework.context.support.ClassPathXmlApplicationContext;

     

    public class SpringTaskAnnotationTest {

        public static void main(String[] args) throws BeansException, InterruptedException, SchedulerException {

            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/schedule/quartz2/spring-mvc.xml");

           

            QuartzManager quartzManager = applicationContext.getBean(QuartzManager.class);

            quartzManager.initJobs();

           

            List<SingleTrigger> triggers = new ArrayList<SingleTrigger>();

            triggers.add(new SingleTrigger("a1","b1","0/1 * * ? * *"));

            triggers.add(new SingleTrigger("a2","b2","0/2 * * ? * *"));

            triggers.add(new SingleTrigger("a3","b3","0/3 * * ? * *"));

            triggers.add(new SingleTrigger("a4","b4","0/4 * * ? * *"));

           

            quartzManager.addJobs("xxxx", "yyyy", triggers ,MyJob.class, "0/3 * * ? * *");

           

            Thread.sleep(7000);

            System.out.println("--- start to change cron ---");

           

            quartzManager.removeTrigger("xxxx", "yyyy", "a1","b1");

            quartzManager.modifyJobTime("xxxx", "yyyy", "a2","b2", "0/5 * * ? * *");

           

            Thread.sleep(7000);

           

            System.out.println("--- start to replace cron ---");

            triggers = new ArrayList<SingleTrigger>();

            triggers.add(new SingleTrigger("a5","b5","0/1 * * ? * *"));

            quartzManager.addJobs("xxxx", "yyyy", triggers ,MyJob.class, "0/3 * * ? * *");

           

            System.out.println(quartzManager.getScheduler().getTriggerGroupNames());

           

           

            Thread.sleep(200000);

        }

    }

     

     

     

     

     

    例子 3 Scheduler start/stop java方式

    java方式初始化

     

    package schedule.quartz3;

     

    import org.quartz.JobBuilder;

    import org.quartz.JobDetail;

    import org.quartz.Scheduler;

    import org.quartz.SimpleScheduleBuilder;

    import org.quartz.Trigger;

    import org.quartz.TriggerBuilder;

    import org.quartz.impl.StdSchedulerFactory;

     

    public class QuartzTest {

        public static void main(String[] args) throws Exception {

            // 从调度程序工厂获取一个调度程序的实例

            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

     

            // 显示调度程序的名称(这里会展示我们在quartz.properties文件中的名称)

            System.out.println("scheduleName = " + scheduler.getSchedulerName());

     

            /**

             * 重要: 定义一个job,并绑定到我们自定义的HelloJobclass对象

             * 这里并不会马上创建一个HelloJob实例,实例创建是在scheduler安排任务触发执行时创建的 这种机制也为后面使用Spring集成提供了便利

             */

            JobDetail job = JobBuilder.newJob(MyJob.class).withIdentity("job1", "group1").build();

     

            // 声明一个触发器,现在就执行(schedule.start()方法开始调用的时候执行);并且每间隔2秒就执行一次

            // 触发器

            TriggerBuilder<Trigger> triggerBuilder = TriggerBuilder.newTrigger();

            // 触发器名,触发器组

            triggerBuilder.withIdentity("trigger1", "group1");

            triggerBuilder.startNow();

            // 触发器时间设定

    //      triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule("0/3 * * ? * *"));

        triggerBuilder.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3).repeatForever());

           

           

            Trigger trigger = triggerBuilder.build();

     

            // 告诉quartz使用定义的触发器trigger安排执行任务job

            scheduler.scheduleJob(job, trigger);

     

            // 启动任务调度程序,内部机制是线程的启动

            scheduler.start();

     

            // 关闭任务调度程序,如果不关闭,调度程序schedule会一直运行着

            // scheduler.shutdown();

     

        }

    }

     

    package schedule.quartz2;

     

    import org.quartz.Job;

    import org.quartz.JobExecutionContext;

    import org.quartz.JobExecutionException;

    import org.quartz.impl.triggers.CronTriggerImpl;

    import org.quartz.impl.triggers.SimpleTriggerImpl;

    import org.slf4j.Logger;

    import org.slf4j.LoggerFactory;

     

    public class MyJob implements Job {

        private Logger logger = LoggerFactory.getLogger(MyJob.class);

     

        @Override

        public void execute(JobExecutionContext context) throws JobExecutionException {

            try {

                if(context.getTrigger() instanceof CronTriggerImpl){

                    CronTriggerImpl xxx = ((CronTriggerImpl)context.getTrigger());

                   

                    logger.info("动态定时调度测试 job group:"+xxx.getJobGroup()+", job name:"+xxx.getJobName() +", trigger group:"+xxx.getGroup()+", trigger name:"+xxx.getName()+",  cronExpression "+xxx.getCronExpression());

                }else if(context.getTrigger() instanceof SimpleTriggerImpl){

                    SimpleTriggerImpl xxx = ((SimpleTriggerImpl)context.getTrigger());

                   

                    logger.info("动态定时调度测试 job group:"+xxx.getJobGroup()+", job name:"+xxx.getJobName() +", trigger group:"+xxx.getGroup()+", trigger name:"+xxx.getName()+",  cronExpression "+xxx);

                }

               

            } catch (Exception e) {

                e.printStackTrace();

            }

        }

    }

     

    例子4 注入service

    SchedulerFactoryBean+AdaptableJobFactory+QuartzJobBean

     

     

     

    Caused by: java.lang.NoSuchMethodError: org.springframework.util.ReflectionUtils.accessibleConstructor(Ljava/lang/Class;[Ljava/lang/Class;)Ljava/lang/reflect/Constructor;

     

    一般是引入了两个重复的spring Jar包,如既引入了spring.framework 又引入了spring.core,去掉一个就好

    用tree方式查看

     

     

     

     

     

     

     

     

        1. XXL-JOB

     

    分布式任务 XXL-JOB,是一个轻量级分布式任务调度框架,支持通过 Web 页面对任务进行 CRUD 操作,支持动态修改任务状态、暂停/恢复任务,以及终止运行中任务,支持在线配置调度任务入参和在线查看调度结果。

     

    https://github.com/xuxueli/xxl-job

     

    https://www.cnblogs.com/xuxueli/p/5021979.html

     

     

     

     

     

     

     

     

     

     

     

        1. Elastic-Job

    分布式任务 Elastic-Job,是一个分布式调度解决方案,由两个相互独立的子项目 Elastic-Job-Lite 和 Elastic-Job-Cloud 组成。定位为轻量级无中心化解决方案,使用 jar 包的形式提供分布式任务的协调服务。支持分布式调度协调、弹性扩容缩容、失效转移、错过执行作业重触发、并行调度、自诊。

     

     

    https://gitee.com/52itstyle/spring-boot-quartz

     

     

    展开全文
  • Quartz,水晶、石英,一个简单朴素有美丽的名字,在Java程序界,Quartz大名鼎鼎,很多Java应用几乎都集成或构建了一个定时任务调度系统,Quartz是一个定时任务调度框架。 何为定时任务调度框架?简而言之,它可以...

    Quartz,水晶、石英,一个简单朴素有美丽的名字,在Java程序界,Quartz大名鼎鼎,很多Java应用几乎都集成或构建了一个定时任务调度系统,Quartz是一个定时任务调度框架。

    何为定时任务调度框架?简而言之,它可以领会我们的意图在未来某个时刻做我们想要做的事情,比如,女友生日那天定时发送短信讨好下(当然,除此之外,你还要买买买…)。

    (本文章分享在CSDN平台,更多精彩请阅读 东陆之滇的csdn博客:http://blog.csdn.net/zixiao217)

    我们的应用程序有些定时任务(例如想在凌晨十二点半统计某个互联网金融公司一款借款APP前一天的借款、还款以及逾期情况)需要在指定时间内执行或者周期性执行某个任务(比如每月最后一天统计这个月的财务报表给财务部门等),这时候我们就需要用到任务调度框架了。Quartz正是一个炙手可热的任务调度框架,它简单易上手,并且可以与Spring集成(这才是重点)。

    现在,我们带着疑问开始认识Quartz…

    基本问题

    Quartz是什么?

    Quartz是一个任务调度框架(库),它几乎可以集成到任何应用系统中。术语”job schedule”似乎为不同的人提供了不同的想法。当你阅读该教程时,你应该能够得到一个坚定的想法关于我们使用这个术语时表达含义,但总之,作业调度是负责执行(或通知)其他软件组件在预定时间执行的服务组件。

    Quartz是非常灵活的,并包含多个使用范例,它们可以单独或一起使用,以实现您所期望的行为,并使您能够以最“自然”的方式来编写您的项目的代码。

    Quartz是非常轻量级的,只需要非常少的配置 —— 它实际上可以被跳出框架来使用,如果你的需求是一些相对基本的简单的需求的话。

    Quartz具有容错机制,并且可以在重启服务的时候持久化(”记忆”)你的定时任务,你的任务也不会丢失。

    虽然通过schedule可以简单实现一些系统任务定时执行,当您学习如何使用它来驱动应用程序的业务流程的流程时,Quartz的全部潜力是可以实现的。

    Quartz又不是什么?

    Quartz不是一个任务队列——虽然它确实可以在一些小规模应用中合理的作为一个任务队列来使用。

    Quartz不是一个网格计算引擎——虽然在某些小规模应用中,这样做确实可以达到应用的要求(定时计算、统计一些数据)。

    Quartz不是一个提供给业务人员的执行服务——它是一个库,很容易集成到应用程序中去做一些未来某时刻可能会一直循环执行的相关的任务。

    从一个软件组件角度来看Quartz是什么?

    Quartz是一个很轻量级的java库,几乎包含了所有定时的功能。主要接口是Schedule,提供了一些简单的操作:安排任务或取消任务,启动或者停止任务。

    如果你想在应用中使用Quartz,应该实现Job接口,包含了一个execute()方法。如果你想在一个任务执行时间到了的时候通知你,组件应该实现TriggerListener 或者JobListener 接口。

    Quartz任务可以在你的应用中启动和执行,可以作为一个独立的应用程序(通过RMI接口),也可是在一个J2EE应用中执行。

    为什么不简单的使用just use java.util.Timer就行了呢?

    从JDK1.3开始,Java通过java.util.Timerjava.util.TimerTask可以实现定时器。为什么要使用Quartz而不是使用Java中的这些标准功能呢?

    原因太多了,这里列举几个:

    • Timers没有持久化机制.
    • Timers不灵活 (只可以设置开始时间和重复间隔,不是基于时间、日期、天等(秒、分、时)的)
    • Timers 不能利用线程池,一个timer一个线程
    • Timers没有真正的管理计划

    还有什么是可以替代Quartz的?

    商业上,你还可以使用 Flux scheduler

    其他问题

    Quartz可以运行多少任务?

    这是一个很难回答的问题…答案基本上是“它取决于使用情况”
    我知道你讨厌这样的答案,所以这里的一些信息:

    首先,你使用的JobStore扮演着一个重要的因素。基于RAM的JobStore(100x)比基于JDBC的JobStore更快近乎100倍。JDBC JobStore速度几乎完全取决于您的数据库的连接速度,使用的数据库系统,以及数据库运行的硬件设备,Quartz几乎全部时间花在数据库上了。当然RAMJobStore存储多少JobTriggers也是有限制的,你使用的空间肯定比数据库的硬盘空间要小了。你可以查看“如何提升JDBC-JobStore的性能”的问题。

    因此,限制JobTriggers可以存储或监听的数量的因素是存储空间的大小(RAM的数量和磁盘空间大小)。

    关于通过RMI使用Quartz的问题

    RMI是有问题的,特别是你如果不清楚通过RMI机制时类是如何加载的话。强烈建议读读所有关于RMI的java API。强烈建议您阅读以下的参考资料:
    一个关于RMI和代码库的极好描述: http://www.kedwards.com/jini/codebase.html。很重要的一点是要意识到“代码”是由客户端使用!

    关于安全管理器的一些信息: http://gethelp.devx.com/techtips/java_pro/10MinuteSolutions/10min0500.asp.
    重要的一点:
    RMI的类装载器将不会从远程位置下载任何类如果没有设置安全管理器的话。

    关于Job的一些问题

    如何控制Job的实例?

    可以查看org.quartz.spi.JobFactoryorg.quartz.Scheduler.setJobFactory(..) 的方法。

    当一个Job完成并移除之后,还能保存吗?

    设置属性:JobDetail.setDurability(true)——当job不再有trigger引用它的时候,Quartz也不要删除job。

    如何保证一个job并发执行?

    实现StatefulJob 而不是Job,查看更多关于StatefulJob 的信息。

    如何停止一个正在执行的Job?

    查看org.quartz.InterruptableJob接口和Scheduler.interrupt(String, String)方法。

    关于Trigger的一些问题

    怎么执行任务链?或者说怎么创建工作流?

    目前还没有直接的或自由的方式通过Quartz链式触发任务。然而,有几种方法,你可以轻易的达到目标。

    方法一:
    使用监听器(TriggerListener,JobListener 或者SchedulerListener),可以通知到某个工作完成,然后可以开始另一个任务。这种方式有一点复杂,你必须告知监听器哪个任务是接着哪个任务的,你可能还会担心这些信息的持久性。可以查看org.quartz.listeners.JobChainingJobListener,已经具有一些这样的功能了。
    方法二:
    创建一个Job,它的JobDataMap 包含下一个Job的名字,当这一个job执行完毕再执行下一个任务。大多数的做法是创建一个基类(通常是抽象类或接口)作为Job,并拥有获得job名称的方法,使用预定义的key从JobDataMap 进行分组。抽象类实现execute()方法委托模板方法例如”doWork()”去执行,它包含了调度后续作业的代码。之后子类要做的只是简单的扩展这个类,包括做自己应该做的工作。持久化job的使用,或者重载addJob(JobDetail, boolean, boolean) 方法(Qartz2.2新增的)帮助应用程序使用适当的数据来定义所有的工作,并没有创建触发器来激发他们(除了一个触发器触发外链中的第一个job)。
    以后,Quartz 将会提供一个更简洁的方式处理这个流程,但是现在你可以考虑前面两种处理方式或其他更好的方式处理工作流。

    为什么我的触发器trigger没有执行?

    常见的原因可能是没有调用Scheduler.start()方法,这个方法它告诉调度程序启动触发器。还有一种可能是trigger或者trigger group被暂停了。

    夏令时和触发器

    CronTrigger SimpleTrigger以自己的方式处理夏令时——每一个方式,都是直观的触发类型。

    首先,了解下什么是夏令时,可以查看:https://secure.wikimedia.org/wikipedia/en/wiki/Daylight_saving_time_around_the_world。一些读者可能没意识到,不同的国家/内容的规则是不同的。举个例子,2005年的夏令时起始于4月3日(美国)而埃及是4月29。同样重要的是要知道,不同地区不仅仅是日期不同,日期转换(前一天和后一天的中国是零点)也是不同的。许多地方移在凌晨两点,但其他地方可能是凌晨1:00,凌晨3点等。

    SimpleTrigger可以让你安排一个任务在任何毫秒级执行。可以每N毫秒执行一次任务。总是每N秒就发生一次,与一天中的时间没有关系。
    CronTrigger可以让你在某些时刻执行任务,是按”公历”时间计算的。在指定的一天中的时间触发,然后计算下一次触发的时间。

    关于JDBCJobStore的一些问题

    怎么提升JDBC-JobStore的性能?

    下面有一些提升JDBC-JobStore性能的方法,其中只有一种是有效的:

    • 使用更快更好的网络
    • 买一个更好的机器
    • 买一个更好的RDBMS

    现在,提供一种简单的但有效的方式:在Quartz表建立索引。

    很多数据库系统都会自动把索引放在主键字段上,许多数据库系统也会对外键也建立索引。确保你的数据库是这样做的,或者手动为表的关键字建立索引。
    最重要的索引的TRIGGER 表的next_fire_timestate字段。最后但不是重要的,为FIRED_TRIGGERS 表的每一个字段设置索引。

    create index idx_qrtz_t_next_fire_time on qrtz_triggers(NEXT_FIRE_TIME);
    create index idx_qrtz_t_state on qrtz_triggers(TRIGGER_STATE);
    create index idx_qrtz_t_nf_st on qrtz_triggers(TRIGGER_STATE,NEXT_FIRE_TIME);
    create index idx_qrtz_ft_trig_name on qrtz_fired_triggers(TRIGGER_NAME);
    create index idx_qrtz_ft_trig_group on qrtz_fired_triggers(TRIGGER_GROUP);
    create index idx_qrtz_ft_trig_name on qrtz_fired_triggers(TRIGGER_NAME);
    create index idx_qrtz_ft_trig_n_g on \
        qrtz_fired_triggers(TRIGGER_NAME,TRIGGER_GROUP);
    create index idx_qrtz_ft_trig_inst_name on qrtz_fired_triggers(INSTANCE_NAME);
    create index idx_qrtz_ft_job_name on qrtz_fired_triggers(JOB_NAME);
    create index idx_qrtz_ft_job_group on qrtz_fired_triggers(JOB_GROUP);
    create index idx_qrtz_t_next_fire_time_misfire on \
        qrtz_triggers(MISFIRE_INSTR,NEXT_FIRE_TIME);
    create index idx_qrtz_t_nf_st_misfire on \
        qrtz_triggers(MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_STATE);
    create index idx_qrtz_t_nf_st_misfire_grp on \
        qrtz_triggers(MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_GROUP,TRIGGER_STATE);

    集群功能最适合于长时间运行和/或密集的工作(在多个节点上分配工作负载),如果你需要扩展到支持成千上万的短运行(例如1秒)的工作,考虑工作集分割使用多个不同的调度器(因此多套表(有不同的前缀))。当你添加多个客户端的时候,使用一个调度程序将会强制使用一个集群锁,一个模式,降低性能。

    如果数据库服务器重新启动,我的数据库连接不会恢复正常

    如果您正在创建连接数据源(通过指定在Quartz属性文件中的连接参数),请确保您有一个指定的连接验证查询:

    org.quartz.dataSource.myDS.validationQuery=select 0 from dual

    这个专门的查询语句对Orable数据库是非常有效的。其他的数据库,可以使用合适的sql。

    如果你的数据源是由您的应用程序服务器管理,确保数据源配置在这样一种方式,它可以检测连接失败。

    关于Transactions事务的一些问题

    使用JobStoreCMT,并且遇到死锁,该怎么办?

    JobStoreCMT 在大量使用,滥用可以导致死锁。不管怎样,我们总是抱怨死锁。到目前为止,该问题已被证明是“用户错误”。如果遇到死锁,下面的列表可能是你需要检查的事情了:

    • 当一个事务执行很长时间时,有些数据库会把它当成死锁。 确保你已经设置了索引 。
    • 确保在你的线程池中至少有两个以上数据库连接。
    • 确保你有一个托管的和非托管的数据源供Quartz使用。
    • 确保你在一个任务中处理的业务是在一个事务中。 处理完记得提交事务。
    • 如果你的 Jobs的 execute()方法 使用schedule, 确保使用UserTransaction或者设置Quartz属性:"org.quartz.scheduler.wrapJobExecutionInUserTransaction=true".

    集群、规模化和高可用特性

    Quartz有什么集群能力?

    Quartz具有可扩展和高可用性功能。你可以在Quartz配置资料中找到答案:http://www.quartz-scheduler.org/documentation/quartz-2.2.x/configuration/

    其他的集群配置,不依赖后台数据库,Terracotta

    展开全文
  • 4种任务调度

    2011-10-26 21:16:25
    任务调度是指基于给定时间点,给定时间间隔或者给定执行次数自动执行任务。本文由浅入深介绍四种任务调度的 Java 实现: Timer ScheduledExecutor 开源工具包 Quartz 开源工具包 JCronTab 此外,为结...
    [size=medium][size=large][/size][size=small][/size][size=xx-small]前言

    任务调度是指基于给定时间点,给定时间间隔或者给定执行次数自动执行任务。本文由浅入深介绍四种任务调度的 Java 实现:

    Timer
    ScheduledExecutor
    开源工具包 Quartz
    开源工具包 JCronTab
    此外,为结合实现复杂的任务调度,本文还将介绍 Calendar 的一些使用方法。


    --------------------------------------------------------------------------------
    回页首
    Timer

    相信大家都已经非常熟悉 java.util.Timer 了,它是最简单的一种实现任务调度的方法,下面给出一个具体的例子:


    清单 1. 使用 Timer 进行任务调度

    package com.ibm.scheduler;
    import java.util.Timer;
    import java.util.TimerTask;

    public class TimerTest extends TimerTask {

    private String jobName = "";

    public TimerTest(String jobName) {
    super();
    this.jobName = jobName;
    }

    @Override
    public void run() {
    System.out.println("execute " + jobName);
    }

    public static void main(String[] args) {
    Timer timer = new Timer();
    long delay1 = 1 * 1000;
    long period1 = 1000;
    // 从现在开始 1 秒钟之后,每隔 1 秒钟执行一次 job1
    timer.schedule(new TimerTest("job1"), delay1, period1);
    long delay2 = 2 * 1000;
    long period2 = 2000;
    // 从现在开始 2 秒钟之后,每隔 2 秒钟执行一次 job2
    timer.schedule(new TimerTest("job2"), delay2, period2);
    }
    }
    Output:
    execute job1
    execute job1
    execute job2
    execute job1
    execute job1
    execute job2



    使用 Timer 实现任务调度的核心类是 Timer 和 TimerTask。其中 Timer 负责设定 TimerTask 的起始与间隔执行时间。使用者只需要创建一个 TimerTask 的继承类,实现自己的 run 方法,然后将其丢给 Timer 去执行即可。

    Timer 的设计核心是一个 TaskList 和一个 TaskThread。Timer 将接收到的任务丢到自己的 TaskList 中,TaskList 按照 Task 的最初执行时间进行排序。TimerThread 在创建 Timer 时会启动成为一个守护线程。这个线程会轮询所有任务,找到一个最近要执行的任务,然后休眠,当到达最近要执行任务的开始时间点,TimerThread 被唤醒并执行该任务。之后 TimerThread 更新最近一个要执行的任务,继续休眠。

    Timer 的优点在于简单易用,但由于所有任务都是由同一个线程来调度,因此所有任务都是串行执行的,同一时间只能有一个任务在执行,前一个任务的延迟或异常都将会影响到之后的任务。


    --------------------------------------------------------------------------------
    回页首
    ScheduledExecutor

    鉴于 Timer 的上述缺陷,Java 5 推出了基于线程池设计的 ScheduledExecutor。其设计思想是,每一个被调度的任务都会由线程池中一个线程去执行,因此任务是并发执行的,相互之间不会受到干扰。需要注意的是,只有当任务的执行时间到来时,ScheduedExecutor 才会真正启动一个线程,其余时间 ScheduledExecutor 都是在轮询任务的状态。


    清单 2. 使用 ScheduledExecutor 进行任务调度

    package com.ibm.scheduler;
    import java.util.concurrent.Executors;
    import java.util.concurrent.ScheduledExecutorService;
    import java.util.concurrent.TimeUnit;

    public class ScheduledExecutorTest implements Runnable {
    private String jobName = "";

    public ScheduledExecutorTest(String jobName) {
    super();
    this.jobName = jobName;
    }

    @Override
    public void run() {
    System.out.println("execute " + jobName);
    }

    public static void main(String[] args) {
    ScheduledExecutorService service = Executors.newScheduledThreadPool(10);

    long initialDelay1 = 1;
    long period1 = 1;
    // 从现在开始1秒钟之后,每隔1秒钟执行一次job1
    service.scheduleAtFixedRate(
    new ScheduledExecutorTest("job1"), initialDelay1,
    period1, TimeUnit.SECONDS);

    long initialDelay2 = 1;
    long delay2 = 1;
    // 从现在开始2秒钟之后,每隔2秒钟执行一次job2
    service.scheduleWithFixedDelay(
    new ScheduledExecutorTest("job2"), initialDelay2,
    delay2, TimeUnit.SECONDS);
    }
    }
    Output:
    execute job1
    execute job1
    execute job2
    execute job1
    execute job1
    execute job2




    清单 2 展示了 ScheduledExecutorService 中两种最常用的调度方法 ScheduleAtFixedRate 和 ScheduleWithFixedDelay。ScheduleAtFixedRate 每次执行时间为上一次任务开始起向后推一个时间间隔,即每次执行时间为 :initialDelay, initialDelay+period, initialDelay+2*period, …;ScheduleWithFixedDelay 每次执行时间为上一次任务结束起向后推一个时间间隔,即每次执行时间为:initialDelay, initialDelay+executeTime+delay, initialDelay+2*executeTime+2*delay。由此可见,ScheduleAtFixedRate 是基于固定时间间隔进行任务调度,ScheduleWithFixedDelay 取决于每次任务执行的时间长短,是基于不固定时间间隔进行任务调度。


    --------------------------------------------------------------------------------
    回页首
    用 ScheduledExecutor 和 Calendar 实现复杂任务调度

    Timer 和 ScheduledExecutor 都仅能提供基于开始时间与重复间隔的任务调度,不能胜任更加复杂的调度需求。比如,设置每星期二的 16:38:10 执行任务。该功能使用 Timer 和 ScheduledExecutor 都不能直接实现,但我们可以借助 Calendar 间接实现该功能。


    清单 3. 使用 ScheduledExcetuor 和 Calendar 进行任务调度

    package com.ibm.scheduler;

    import java.util.Calendar;
    import java.util.Date;
    import java.util.TimerTask;
    import java.util.concurrent.Executors;
    import java.util.concurrent.ScheduledExecutorService;
    import java.util.concurrent.TimeUnit;

    public class ScheduledExceutorTest2 extends TimerTask {

    private String jobName = "";

    public ScheduledExceutorTest2(String jobName) {
    super();
    this.jobName = jobName;
    }

    @Override
    public void run() {
    System.out.println("Date = "+new Date()+", execute " + jobName);
    }

    /**
    * 计算从当前时间currentDate开始,满足条件dayOfWeek, hourOfDay,
    * minuteOfHour, secondOfMinite的最近时间
    * @return
    */
    public Calendar getEarliestDate(Calendar currentDate, int dayOfWeek,
    int hourOfDay, int minuteOfHour, int secondOfMinite) {
    //计算当前时间的WEEK_OF_YEAR,DAY_OF_WEEK, HOUR_OF_DAY, MINUTE,SECOND等各个字段值
    int currentWeekOfYear = currentDate.get(Calendar.WEEK_OF_YEAR);
    int currentDayOfWeek = currentDate.get(Calendar.DAY_OF_WEEK);
    int currentHour = currentDate.get(Calendar.HOUR_OF_DAY);
    int currentMinute = currentDate.get(Calendar.MINUTE);
    int currentSecond = currentDate.get(Calendar.SECOND);

    //如果输入条件中的dayOfWeek小于当前日期的dayOfWeek,则WEEK_OF_YEAR需要推迟一周
    boolean weekLater = false;
    if (dayOfWeek < currentDayOfWeek) {
    weekLater = true;
    } else if (dayOfWeek == currentDayOfWeek) {
    //当输入条件与当前日期的dayOfWeek相等时,如果输入条件中的
    //hourOfDay小于当前日期的
    //currentHour,则WEEK_OF_YEAR需要推迟一周
    if (hourOfDay < currentHour) {
    weekLater = true;
    } else if (hourOfDay == currentHour) {
    //当输入条件与当前日期的dayOfWeek, hourOfDay相等时,
    //如果输入条件中的minuteOfHour小于当前日期的
    //currentMinute,则WEEK_OF_YEAR需要推迟一周
    if (minuteOfHour < currentMinute) {
    weekLater = true;
    } else if (minuteOfHour == currentSecond) {
    //当输入条件与当前日期的dayOfWeek, hourOfDay,
    //minuteOfHour相等时,如果输入条件中的
    //secondOfMinite小于当前日期的currentSecond,
    //则WEEK_OF_YEAR需要推迟一周
    if (secondOfMinite < currentSecond) {
    weekLater = true;
    }
    }
    }
    }
    if (weekLater) {
    //设置当前日期中的WEEK_OF_YEAR为当前周推迟一周
    currentDate.set(Calendar.WEEK_OF_YEAR, currentWeekOfYear + 1);
    }
    // 设置当前日期中的DAY_OF_WEEK,HOUR_OF_DAY,MINUTE,SECOND为输入条件中的值。
    currentDate.set(Calendar.DAY_OF_WEEK, dayOfWeek);
    currentDate.set(Calendar.HOUR_OF_DAY, hourOfDay);
    currentDate.set(Calendar.MINUTE, minuteOfHour);
    currentDate.set(Calendar.SECOND, secondOfMinite);
    return currentDate;

    }

    public static void main(String[] args) throws Exception {

    ScheduledExceutorTest2 test = new ScheduledExceutorTest2("job1");
    //获取当前时间
    Calendar currentDate = Calendar.getInstance();
    long currentDateLong = currentDate.getTime().getTime();
    System.out.println("Current Date = " + currentDate.getTime().toString());
    //计算满足条件的最近一次执行时间
    Calendar earliestDate = test
    .getEarliestDate(currentDate, 3, 16, 38, 10);
    long earliestDateLong = earliestDate.getTime().getTime();
    System.out.println("Earliest Date = "
    + earliestDate.getTime().toString());
    //计算从当前时间到最近一次执行时间的时间间隔
    long delay = earliestDateLong - currentDateLong;
    //计算执行周期为一星期
    long period = 7 * 24 * 60 * 60 * 1000;
    ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
    //从现在开始delay毫秒之后,每隔一星期执行一次job1
    service.scheduleAtFixedRate(test, delay, period,
    TimeUnit.MILLISECONDS);

    }
    }
    Output:
    Current Date = Wed Feb 02 17:32:01 CST 2011
    Earliest Date = Tue Feb 8 16:38:10 CST 2011
    Date = Tue Feb 8 16:38:10 CST 2011, execute job1
    Date = Tue Feb 15 16:38:10 CST 2011, execute job1




    清单 3 实现了每星期二 16:38:10 调度任务的功能。其核心在于根据当前时间推算出最近一个星期二 16:38:10 的绝对时间,然后计算与当前时间的时间差,作为调用 ScheduledExceutor 函数的参数。计算最近时间要用到 java.util.calendar 的功能。首先需要解释 calendar 的一些设计思想。Calendar 有以下几种唯一标识一个日期的组合方式:

    YEAR + MONTH + DAY_OF_MONTH
    YEAR + MONTH + WEEK_OF_MONTH + DAY_OF_WEEK
    YEAR + MONTH + DAY_OF_WEEK_IN_MONTH + DAY_OF_WEEK
    YEAR + DAY_OF_YEAR
    YEAR + DAY_OF_WEEK + WEEK_OF_YEAR



    上述组合分别加上 HOUR_OF_DAY + MINUTE + SECOND 即为一个完整的时间标识。本例采用了最后一种组合方式。输入为 DAY_OF_WEEK, HOUR_OF_DAY, MINUTE, SECOND 以及当前日期 , 输出为一个满足 DAY_OF_WEEK, HOUR_OF_DAY, MINUTE, SECOND 并且距离当前日期最近的未来日期。计算的原则是从输入的 DAY_OF_WEEK 开始比较,如果小于当前日期的 DAY_OF_WEEK,则需要向 WEEK_OF_YEAR 进一, 即将当前日期中的 WEEK_OF_YEAR 加一并覆盖旧值;如果等于当前的 DAY_OF_WEEK, 则继续比较 HOUR_OF_DAY;如果大于当前的 DAY_OF_WEEK,则直接调用 java.util.calenda 的 calendar.set(field, value) 函数将当前日期的 DAY_OF_WEEK, HOUR_OF_DAY, MINUTE, SECOND 赋值为输入值,依次类推,直到比较至 SECOND。读者可以根据输入需求选择不同的组合方式来计算最近执行时间。

    可以看出,用上述方法实现该任务调度比较麻烦,这就需要一个更加完善的任务调度框架来解决这些复杂的调度问题。幸运的是,开源工具包 Quartz 与 JCronTab 提供了这方面强大的支持。


    --------------------------------------------------------------------------------
    回页首
    Quartz

    Quartz 可以满足更多更复杂的调度需求,首先让我们看看如何用 Quartz 实现每星期二 16:38 的调度安排:


    清单 4. 使用 Quartz 进行任务调度

    package com.ibm.scheduler;
    import java.util.Date;

    import org.quartz.Job;
    import org.quartz.JobDetail;
    import org.quartz.JobExecutionContext;
    import org.quartz.JobExecutionException;
    import org.quartz.Scheduler;
    import org.quartz.SchedulerFactory;
    import org.quartz.Trigger;
    import org.quartz.helpers.TriggerUtils;

    public class QuartzTest implements Job {

    @Override
    //该方法实现需要执行的任务
    public void execute(JobExecutionContext arg0) throws JobExecutionException {
    System.out.println("Generating report - "
    + arg0.getJobDetail().getFullName() + ", type ="
    + arg0.getJobDetail().getJobDataMap().get("type"));
    System.out.println(new Date().toString());
    }
    public static void main(String[] args) {
    try {
    // 创建一个Scheduler
    SchedulerFactory schedFact =
    new org.quartz.impl.StdSchedulerFactory();
    Scheduler sched = schedFact.getScheduler();
    sched.start();
    // 创建一个JobDetail,指明name,groupname,以及具体的Job类名,
    //该Job负责定义需要执行任务
    JobDetail jobDetail = new JobDetail("myJob", "myJobGroup",
    QuartzTest.class);
    jobDetail.getJobDataMap().put("type", "FULL");
    // 创建一个每周触发的Trigger,指明星期几几点几分执行
    Trigger trigger = TriggerUtils.makeWeeklyTrigger(3, 16, 38);
    trigger.setGroup("myTriggerGroup");
    // 从当前时间的下一秒开始执行
    trigger.setStartTime(TriggerUtils.getEvenSecondDate(new Date()));
    // 指明trigger的name
    trigger.setName("myTrigger");
    // 用scheduler将JobDetail与Trigger关联在一起,开始调度任务
    sched.scheduleJob(jobDetail, trigger);

    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    }
    Output:
    Generating report - myJobGroup.myJob, type =FULL
    Tue Feb 8 16:38:00 CST 2011
    Generating report - myJobGroup.myJob, type =FULL
    Tue Feb 15 16:38:00 CST 2011




    清单 4 非常简洁地实现了一个上述复杂的任务调度。Quartz 设计的核心类包括 Scheduler, Job 以及 Trigger。其中,Job 负责定义需要执行的任务,Trigger 负责设置调度策略,Scheduler 将二者组装在一起,并触发任务开始执行。

    Job

    使用者只需要创建一个 Job 的继承类,实现 execute 方法。JobDetail 负责封装 Job 以及 Job 的属性,并将其提供给 Scheduler 作为参数。每次 Scheduler 执行任务时,首先会创建一个 Job 的实例,然后再调用 execute 方法执行。Quartz 没有为 Job 设计带参数的构造函数,因此需要通过额外的 JobDataMap 来存储 Job 的属性。JobDataMap 可以存储任意数量的 Key,Value 对,例如:


    清单 5. 为 JobDataMap 赋值

    jobDetail.getJobDataMap().put("myDescription", "my job description");
    jobDetail.getJobDataMap().put("myValue", 1998);
    ArrayList<String> list = new ArrayList<String>();
    list.add("item1");
    jobDetail.getJobDataMap().put("myArray", list);



    JobDataMap 中的数据可以通过下面的方式获取:


    清单 6. 获取 JobDataMap 的值

    public class JobDataMapTest implements Job {

    @Override
    public void execute(JobExecutionContext context)
    throws JobExecutionException {
    //从context中获取instName,groupName以及dataMap
    String instName = context.getJobDetail().getName();
    String groupName = context.getJobDetail().getGroup();
    JobDataMap dataMap = context.getJobDetail().getJobDataMap();
    //从dataMap中获取myDescription,myValue以及myArray
    String myDescription = dataMap.getString("myDescription");
    int myValue = dataMap.getInt("myValue");
    ArrayList<String> myArray = (ArrayListlt;Strin>) dataMap.get("myArray");
    System.out.println("
    Instance =" + instName + ", group = " + groupName
    + ", description = " + myDescription + ", value =" + myValue
    + ", array item0 = " + myArray.get(0));

    }
    }
    Output:
    Instance = myJob, group = myJobGroup,
    description = my job description,
    value =1998, array item0 = item1




    Trigger

    Trigger 的作用是设置调度策略。Quartz 设计了多种类型的 Trigger,其中最常用的是 SimpleTrigger 和 CronTrigger。

    SimpleTrigger 适用于在某一特定的时间执行一次,或者在某一特定的时间以某一特定时间间隔执行多次。上述功能决定了 SimpleTrigger 的参数包括 start-time, end-time, repeat count, 以及 repeat interval。

    Repeat count 取值为大于或等于零的整数,或者常量 SimpleTrigger.REPEAT_INDEFINITELY。

    Repeat interval 取值为大于或等于零的长整型。当 Repeat interval 取值为零并且 Repeat count 取值大于零时,将会触发任务的并发执行。

    Start-time 与 dnd-time 取值为 java.util.Date。当同时指定 end-time 与 repeat count 时,优先考虑 end-time。一般地,可以指定 end-time,并设定 repeat count 为 REPEAT_INDEFINITELY。

    以下是 SimpleTrigger 的构造方法:

    public SimpleTrigger(String name,
    String group,
    Date startTime,
    Date endTime,
    int repeatCount,
    long repeatInterval)



    举例如下:

    创建一个立即执行且仅执行一次的 SimpleTrigger:

    SimpleTrigger trigger=
    new SimpleTrigger("myTrigger", "myGroup", new Date(), null, 0, 0L);



    创建一个半分钟后开始执行,且每隔一分钟重复执行一次的 SimpleTrigger:

    SimpleTrigger trigger=
    new SimpleTrigger("myTrigger", "myGroup",
    new Date(System.currentTimeMillis()+30*1000), null, 0, 60*1000);



    创建一个 2011 年 6 月 1 日 8:30 开始执行,每隔一小时执行一次,一共执行一百次,一天之后截止的 SimpleTrigger:

    Calendar calendar = Calendar.getInstance();
    calendar.set(Calendar.YEAR, 2011);
    calendar.set(Calendar.MONTH, Calendar.JUNE);
    calendar.set(Calendar.DAY_OF_MONTH, 1);
    calendar.set(Calendar.HOUR, 8);
    calendar.set(Calendar.MINUTE, 30);
    calendar.set(Calendar.SECOND, 0);
    calendar.set(Calendar.MILLISECOND, 0);
    Date startTime = calendar.getTime();
    Date endTime = new Date (calendar.getTimeInMillis() +24*60*60*1000);
    SimpleTrigger trigger=new SimpleTrigger("myTrigger",
    "myGroup", startTime, endTime, 100, 60*60*1000);



    上述最后一个例子中,同时设置了 end-time 与 repeat count,则优先考虑 end-time,总共可以执行二十四次。

    CronTrigger 的用途更广,相比基于特定时间间隔进行调度安排的 SimpleTrigger,CronTrigger 主要适用于基于日历的调度安排。例如:每星期二的 16:38:10 执行,每月一号执行,以及更复杂的调度安排等。

    CronTrigger 同样需要指定 start-time 和 end-time,其核心在于 Cron 表达式,由七个字段组成:

    Seconds
    Minutes
    Hours
    Day-of-Month
    Month
    Day-of-Week
    Year (Optional field)



    举例如下:

    创建一个每三小时执行的 CronTrigger,且从每小时的整点开始执行:

    0 0 0/3 * * ?



    创建一个每十分钟执行的 CronTrigger,且从每小时的第三分钟开始执行:

    0 3/10 * * * ?



    创建一个每周一,周二,周三,周六的晚上 20:00 到 23:00,每半小时执行一次的 CronTrigger:

    0 0/30 20-23 ? * MON-WED,SAT



    创建一个每月最后一个周四,中午 11:30-14:30,每小时执行一次的 trigger:

    0 30 11-14/1 ? * 5L



    解释一下上述例子中各符号的含义:

    首先所有字段都有自己特定的取值,例如,Seconds 和 Minutes 取值为 0 到 59,Hours 取值为 0 到 23,Day-of-Month 取值为 0-31, Month 取值为 0-11,或者 JAN,FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC,Days-of-Week 取值为 1-7 或者 SUN, MON, TUE, WED, THU, FRI, SAT。每个字段可以取单个值,多个值,或一个范围,例如 Day-of-Week 可取值为“MON,TUE,SAT”,“MON-FRI”或者“TUE-THU,SUN”。

    通配符 * 表示该字段可接受任何可能取值。例如 Month 字段赋值 * 表示每个月,Day-of-Week 字段赋值 * 表示一周的每天。

    / 表示开始时刻与间隔时段。例如 Minutes 字段赋值 2/10 表示在一个小时内每 20 分钟执行一次,从第 2 分钟开始。

    ? 仅适用于 Day-of-Month 和 Day-of-Week。? 表示对该字段不指定特定值。适用于需要对这两个字段中的其中一个指定值,而对另一个不指定值的情况。一般情况下,这两个字段只需对一个赋值。

    L 仅适用于 Day-of-Month 和 Day-of-Week。L 用于 Day-of-Month 表示该月最后一天。L 单独用于 Day-of-Week 表示周六,否则表示一个月最后一个星期几,例如 5L 或者 THUL 表示该月最后一个星期四。

    W 仅适用于 Day-of-Month,表示离指定日期最近的一个工作日,例如 Day-of-Month 赋值为 10W 表示该月离 10 号最近的一个工作日。

    # 仅适用于 Day-of-Week,表示该月第 XXX 个星期几。例如 Day-of-Week 赋值为 5#2 或者 THU#2,表示该月第二个星期四。

    CronTrigger 的使用如下:

    CronTrigger cronTrigger = new CronTrigger("myTrigger", "myGroup");
    try {
    cronTrigger.setCronExpression("0 0/30 20-13 ? * MON-WED,SAT");
    } catch (Exception e) {
    e.printStackTrace();
    }



    Job 与 Trigger 的松耦合设计是 Quartz 的一大特点,其优点在于同一个 Job 可以绑定多个不同的 Trigger,同一个 Trigger 也可以调度多个 Job,灵活性很强。

    Listener

    除了上述基本的调度功能,Quartz 还提供了 listener 的功能。主要包含三种 listener:JobListener,TriggerListener 以及 SchedulerListener。当系统发生故障,相关人员需要被通知时,Listener 便能发挥它的作用。最常见的情况是,当任务被执行时,系统发生故障,Listener 监听到错误,立即发送邮件给管理员。下面给出 JobListener 的实例:


    清单 7. JobListener 的实现

    import org.quartz.JobExecutionContext;
    import org.quartz.JobExecutionException;
    import org.quartz.JobListener;
    import org.quartz.SchedulerException;


    public class MyListener implements JobListener{

    @Override
    public String getName() {
    return "My Listener";
    }
    @Override
    public void jobWasExecuted(JobExecutionContext context,
    JobExecutionException jobException) {
    if(jobException != null){
    try {
    //停止Scheduler
    context.getScheduler().shutdown();
    System.out.println("
    Error occurs when executing jobs, shut down the scheduler ");
    // 给管理员发送邮件…
    } catch (SchedulerException e) {
    e.printStackTrace();
    }
    }
    }
    }




    从清单 7 可以看出,使用者只需要创建一个 JobListener 的继承类,重载需要触发的方法即可。当然,需要将 listener 的实现类注册到 Scheduler 和 JobDetail 中:

    sched.addJobListener(new MyListener());
    jobDetail.addJobListener("My Listener"); // listener 的名字



    使用者也可以将 listener 注册为全局 listener,这样便可以监听 scheduler 中注册的所有任务 :

    sched.addGlobalJobListener(new MyListener());



    为了测试 listener 的功能,可以在 job 的 execute 方法中强制抛出异常。清单 7 中,listener 接收到异常,将 job 所在的 scheduler 停掉,阻止后续的 job 继续执行。scheduler、jobDetail 等信息都可以从 listener 的参数 context 中检索到。

    清单 7 的输出结果为:

    Generating report - myJob.myJob, type =FULL
    Tue Feb 15 18:57:35 CST 2011
    2011-2-15 18:57:35 org.quartz.core.JobRunShell run
    信息 : Job myJob.myJob threw a JobExecutionException:
    org.quartz.JobExecutionException
    at com.ibm.scheduler.QuartzListenerTest.execute(QuartzListenerTest.java:22)
    at org.quartz.core.JobRunShell.run(JobRunShell.java:191)
    at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:516)
    2011-2-15 18:57:35 org.quartz.core.QuartzScheduler shutdown
    信息 : Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED shutting down.
    Error occurs when executing jobs, shut down the scheduler



    TriggerListener、SchedulerListener 与 JobListener 有类似的功能,只是各自触发的事件不同,如 JobListener 触发的事件为:

    Job to be executed, Job has completed execution 等

    TriggerListener 触发的事件为:

    Trigger firings, trigger mis-firings, trigger completions 等

    SchedulerListener 触发的事件为:

    add a job/trigger, remove a job/trigger, shutdown a scheduler 等

    读者可以根据自己的需求重载相应的事件。

    JobStores

    Quartz 的另一显著优点在于持久化,即将任务调度的相关数据保存下来。这样,当系统重启后,任务被调度的状态依然存在于系统中,不会丢失。默认情况下,Quartz 采用的是 org.quartz.simpl.RAMJobStore,在这种情况下,数据仅能保存在内存中,系统重启后会全部丢失。若想持久化数据,需要采用 org.quartz.simpl.JDBCJobStoreTX。

    实现持久化的第一步,是要创建 Quartz 持久化所需要的表格。在 Quartz 的发布包 docs/dbTables 中可以找到相应的表格创建脚本。Quartz 支持目前大部分流行的数据库。本文以 DB2 为例,所需要的脚本为 tables_db2.sql。首先需要对脚本做一点小的修改,即在开头指明 Schema:

    SET CURRENT SCHEMA quartz;



    为了方便重复使用 , 创建表格前首先删除之前的表格:
    drop table qrtz_job_details;

    drop table qrtz_job_listeners;





    然后创建数据库 sched,执行 tables_db2.sql 创建持久化所需要的表格。

    第二步,配置数据源。数据源与其它所有配置,例如 ThreadPool,均放在 quartz.properties 里:


    清单 8. Quartz 配置文件

    # Configure ThreadPool
    org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
    org.quartz.threadPool.threadCount = 5
    org.quartz.threadPool.threadPriority = 4

    # Configure Datasources
    org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
    org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
    org.quartz.jobStore.dataSource = db2DS
    org.quartz.jobStore.tablePrefix = QRTZ_

    org.quartz.dataSource.db2DS.driver = com.ibm.db2.jcc.DB2Driver
    org.quartz.dataSource.db2DS.URL = jdbc:db2://localhost:50001/sched
    org.quartz.dataSource.db2DS.user = quartz
    org.quartz.dataSource.db2DS.password = passw0rd
    org.quartz.dataSource.db2DS.maxConnections = 5



    使用时只需要将 quatz.properties 放在 classpath 下面,不用更改一行代码,再次运行之前的任务调度实例,trigger、job 等信息便会被记录在数据库中。

    将清单 4 中的 makeWeeklyTrigger 改成 makeSecondlyTrigger,重新运行 main 函数,在 sched 数据库中查询表 qrtz_simple_triggers 中的数据。其查询语句为“db2 ‘ select repeat_interval, times_triggered from qrtz_simple_triggers ’”。结果 repeat_interval 为 1000,与程序中设置的 makeSecondlyTrigger 相吻合,times_triggered 值为 21。

    停掉程序,将数据库中记录的任务调度数据重新导入程序运行:


    清单 9. 从数据库中导入任务调度数据重新运行

    package com.ibm.scheduler;
    import org.quartz.Scheduler;
    import org.quartz.SchedulerException;
    import org.quartz.SchedulerFactory;
    import org.quartz.Trigger;
    import org.quartz.impl.StdSchedulerFactory;

    public class QuartzReschedulerTest {
    public static void main(String[] args) throws SchedulerException {
    // 初始化一个 Schedule Factory
    SchedulerFactory schedulerFactory = new StdSchedulerFactory();
    // 从 schedule factory 中获取 scheduler
    Scheduler scheduler = schedulerFactory.getScheduler();
    // 从 schedule factory 中获取 trigger
    Trigger trigger = scheduler.getTrigger("myTrigger", "myTriggerGroup");
    // 重新开启调度任务
    scheduler.rescheduleJob("myTrigger", "myTriggerGroup", trigger);
    scheduler.start();
    }
    }



    清单 9 中,schedulerFactory.getScheduler() 将 quartz.properties 的内容加载到内存,然后根据数据源的属性初始化数据库的链接,并将数据库中存储的数据加载到内存。之后,便可以在内存中查询某一具体的 trigger,并将其重新启动。这时候重新查询 qrtz_simple_triggers 中的数据,发现 times_triggered 值比原来增长了。


    --------------------------------------------------------------------------------
    回页首
    JCronTab

    习惯使用 unix/linux 的开发人员应该对 crontab 都不陌生。Crontab 是一个非常方便的用于 unix/linux 系统的任务调度命令。JCronTab 则是一款完全按照 crontab 语法编写的 java 任务调度工具。

    首先简单介绍一下 crontab 的语法,与上面介绍的 Quartz 非常相似,但更加简洁 , 集中了最常用的语法。主要由六个字段组成(括弧中标识了每个字段的取值范围):

    Minutes (0-59)
    Hours (0-23)
    Day-of-Month (1-31)
    Month (1-12/JAN-DEC)
    Day-of-Week (0-6/SUN-SAT)
    Command



    与 Quartz 相比,省略了 Seconds 与 Year,多了一个 command 字段,即为将要被调度的命令。JCronTab 中也包含符号“*”与“/”, 其含义与 Quartz 相同。

    举例如下:

    每天 12 点到 15 点 , 每隔 1 小时执行一次 Date 命令:

    0 12-15/1 * * * Date



    每月 2 号凌晨 1 点发一封信给 zhjingbj@cn.ibm.com:

    0 1 2 * * mail -s “good” zhjingbj@cn.ibm.com



    每周一,周二,周三,周六的晚上 20:00 到 23:00,每半小时打印“normal”:

    0/30 20-23 * * MON-WED,SAT echo “normal”



    JCronTab 借鉴了 crontab 的语法,其区别在于 command 不再是 unix/linux 的命令,而是一个 Java 类。如果该类带参数,例如“com.ibm.scheduler.JCronTask2#run”,则定期执行 run 方法;如果该类不带参数,则默认执行 main 方法。此外,还可以传参数给 main 方法或者构造函数,例如“com.ibm.scheduler.JCronTask2#run Hello World“表示传两个参数 Hello 和 World 给构造函数。

    JCronTab 与 Quartz 相比,其优点在于,第一,支持多种任务调度的持久化方法,包括普通文件、数据库以及 XML 文件进行持久化;第二,JCronTab 能够非常方便地与 Web 应用服务器相结合,任务调度可以随 Web 应用服务器的启动自动启动;第三,JCronTab 还内置了发邮件功能,可以将任务执行结果方便地发送给需要被通知的人。

    JCronTab 与 Web 应用服务器的结合非常简单,只需要在 Web 应用程序的 web.xml 中添加如下行:


    清单 10. 在 web.xml 中配置 JCronTab 的属性

    <servlet>
    <servlet-name>LoadOnStartupServlet</servlet-name>
    <servlet-class>org.jcrontab.web.loadCrontabServlet</servlet-class>
    <init-param>
    <param-name>PROPERTIES_FILE</param-name>
    <param-value>D:/Scheduler/src/jcrontab.properties</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
    </servlet>
    <!-- Mapping of the StartUp Servlet -->
    <servlet-mapping>
    <servlet-name>LoadOnStartupServlet</servlet-name>
    <url-pattern>/Startup</url-pattern>
    </servlet-mapping>



    在清单 10 中,需要注意两点:第一,必须指定 servlet-class 为 org.jcrontab.web.loadCrontabServlet,因为它是整个任务调度的入口;第二,必须指定一个参数为 PROPERTIES_FILE,才能被 loadCrontabServlet 识别。

    接下来,需要撰写 D:/Scheduler/src/jcrontab.properties 的内容,其内容根据需求的不同而改变。

    当采用普通文件持久化时,jcrontab.properties 的内容主要包括:

    org.jcrontab.data.file = D:/Scheduler/src/crontab
    org.jcrontab.data.datasource = org.jcrontab.data.FileSource



    其中数据来源 org.jcrontab.data.datasource 被描述为普通文件,即 org.jcrontab.data.FileSource。具体的文件即 org.jcrontab.data.file 指明为 D:/Scheduler/src/crontab。

    Crontab 描述了任务的调度安排:

    */2 * * * * com.ibm.scheduler.JCronTask1
    * * * * * com.ibm.scheduler.JCronTask2#run Hello World



    其中包含了两条任务的调度,分别是每两分钟执行一次 JCronTask1 的 main 方法,每一分钟执行一次 JCronTask2 的 run 方法。


    清单 11. JcronTask1 与 JCronTask2 的实现

    package com.ibm.scheduler;

    import java.util.Date;

    public class JCronTask1 {

    private static int count = 0;

    public static void main(String[] args) {
    System.out.println("--------------Task1-----------------");
    System.out.println("Current Time = " + new Date() + ", Count = "
    + count++);
    }
    }

    package com.ibm.scheduler;

    import java.util.Date;

    public class JCronTask2 implements Runnable {

    private static int count = 0;

    private static String[] args;

    public JCronTask2(String[] args) {
    System.out.println("--------------Task2-----------------");
    System.out.println("Current Time = " + new Date() + ", Count = "
    + count++);
    JCronTask2.args = args;
    }

    @Override
    public void run() {
    System.out.println("enter into run method");
    if (args != null && args.length > 0) {
    for (int i = 0; i < args.length; i++) {
    System.out.print("This is arg " + i + " " + args[i] + "\n");
    }
    }
    }
    }




    到此为止,基于普通文件持久化的 JCronTab 的实例就全部配置好了。启动 Web 应用服务器,便可以看到任务调度的输出结果:


    --------------Task2-----------------
    Current Time = Tue Feb 15 09:22:00 CST 2011, Count = 0
    enter into run method
    This is arg 0 Hello
    This is arg 1 World
    --------------Task1-----------------
    Current Time = Tue Feb 15 09:22:00 CST 2011, Count = 0
    --------------Task2-----------------
    Current Time = Tue Feb 15 09:23:00 CST 2011, Count = 1
    enter into run method
    This is arg 0 Hello
    This is arg 1 World
    --------------Task2-----------------
    Current Time = Tue Feb 15 09:24:00 CST 2011, Count = 2
    enter into run method
    This is arg 0 Hello
    This is arg 1 World
    --------------Task1-----------------
    Current Time = Tue Feb 15 09:24:00 CST 2011, Count = 1



    通过修改 jcrontab.properties 中 datasource,可以选择采用数据库或 xml 文件持久化,感兴趣的读者可以参考 进阶学习 JCronTab。

    此外,JCronTab 还内置了发邮件功能,可以将任务执行结果方便地发送给需要被通知的人。其配置非常简单,只需要在 jcontab.properties 中添加几行配置即可:

    org.jcrontab.sendMail.to= Ther email you want to send to
    org.jcrontab.sendMail.from=The email you want to send from
    org.jcrontab.sendMail.smtp.host=smtp server
    org.jcrontab.sendMail.smtp.user=smtp username
    org.jcrontab.sendMail.smtp.password=smtp password



    --------------------------------------------------------------------------------
    回页首
    结束语

    本文介绍了四种常用的对任务进行调度的 Java 实现方法,即 Timer,ScheduledExecutor, Quartz 以及 JCronTab。文本对每种方法都进行了实例解释,并对其优缺点进行比较。对于简单的基于起始时间点与时间间隔的任务调度,使用 Timer 就足够了;如果需要同时调度多个任务,基于线程池的 ScheduledTimer 是更为合适的选择;当任务调度的策略复杂到难以凭借起始时间点与时间间隔来描述时,Quartz 与 JCronTab 则体现出它们的优势。熟悉 Unix/Linux 的开发人员更倾向于 JCronTab,且 JCronTab 更适合与 Web 应用服务器相结合。Quartz 的 Trigger 与 Job 松耦合设计使其更适用于 Job 与 Trigger 的多对多应用场景。[/size][/size]
    展开全文
  • 分布式任务调度

    千次阅读 2018-10-01 22:31:50
    什么是定时任务? 指定时间去执行任务 Java实现定时任务方式 1.Thread public class Demo01 { static long count = 0; public static void main(String[] args) { Runnable runnable = new Runnable() { ...

    什么是定时任务?
    指定时间去执行任务

    Java实现定时任务方式

    1.Thread

    public class Demo01 {
    	static long count = 0;
    	public static void main(String[] args) {
    		Runnable runnable = new Runnable() {
    			@Override
    			public void run() {
    				while (true) {
    					try {
    						Thread.sleep(1000);
    						count++;
    						System.out.println(count);
    					} catch (Exception e) {
    						// TODO: handle exception
    					}
    				}
    			}
    		};
    		Thread thread = new Thread(runnable);
    		thread.start();
    	}
    }
    

    2.TimerTask

    /**
     * 使用TimerTask类实现定时任务
    */
    public class Demo02 {
    	static long count = 0;
    	public static void main(String[] args) {
    		TimerTask timerTask = new TimerTask() {
    			@Override
    			public void run() {
    				count++;
    				System.out.println(count);
    			}
    		};
    		Timer timer = new Timer();
    		// 天数
    		long delay = 0;
    		// 秒数
    		long period = 1000;
    		timer.scheduleAtFixedRate(timerTask, delay, period);
    	}    
    }
    

    3.ScheduledExecutorService
    使用ScheduledExecutorService是从Java
    JavaSE5的java.util.concurrent里,做为并发工具类被引进的,这是最理想的定时任务实现方式。

    public class Demo003 {
    	public static void main(String[] args) {
    		Runnable runnable = new Runnable() {
    			public void run() {
    				// task to run goes here
    				System.out.println("Hello !!");
    			}
    		};
    		ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
    		// 第二个参数为首次执行的延时时间,第三个参数为定时执行的间隔时间
    		service.scheduleAtFixedRate(runnable, 1, 1, TimeUnit.SECONDS);
    	}
    }
    

    分布式情况下定时任务会出现哪些问题?

    答:分布式集群的情况下,怎么保证定时任务不被重复执行(解释:分布式情况下,一个项目被打成war包部署到多台服务器上,这个时候如果有任务调度的话就有可能产生幂等性(一个任务被重复执行))
    在这里插入图片描述

    分布式定时任务解决方案

    ①使用zookeeper实现分布式锁,保证只有一台服务器执行job, 缺点(需要创建临时节点、和事件通知不易于扩展)
    ②使用配置文件做一个开关 ,加一个配置start=true或者start=false,如果为true,执行job,如果为false,不执行job,缺点;发布后,需要重启
    ③数据库唯一约束(意思就是说每台服务器(例如tomcat)执行job之前必须先往数据库中插入一个唯一id(主键id),插入成功那么就执行,其他服务器就不能插入成功了,因为插入的id是唯一的),缺点效率低
    ④使用分布式任务调度平台XXLJOB

    传统任务调度的缺点:

    1.没有补偿机制
    2.不支持集群
    3.不支持路由策略(分发)
    4.不支持统计
    5.不支持管理平台
    6.没有报警邮箱(当服务器瘫痪,要及时报警邮箱给运维人员)
    7.没有状态监控

    XXLJOB介绍

    XXL-JOB是一个轻量级分布式任务调度框架,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品线,开箱即用
    1、简单:支持通过Web页面对任务进行CRUD操作,操作简单,一分钟上手;
    2、动态:支持动态修改任务状态、暂停/恢复任务,以及终止运行中任务,即时生效;
    3、调度中心HA(中心式):调度采用中心式设计,“调度中心”基于集群Quartz实现,可保证调度中心HA;
    4、执行器HA(分布式):任务分布式执行,任务"执行器"支持集群部署,可保证任务执行HA;
    5、任务Failover:执行器集群部署时,任务路由策略选择"故障转移"情况下调度失败时将会平滑切换执行器进行Failover;
    6、一致性:“调度中心”通过DB锁保证集群分布式调度的一致性, 一次任务调度只会触发一次执行;
    7、自定义任务参数:支持在线配置调度任务入参,即时生效;
    8、调度线程池:调度系统多线程触发调度运行,确保调度精确执行,不被堵塞;
    9、弹性扩容缩容:一旦有新执行器机器上线或者下线,下次调度时将会重新分配任务;
    10、邮件报警:任务失败时支持邮件报警,支持配置多邮件地址群发报警邮件;
    11、状态监控:支持实时监控任务进度;
    12、Rolling执行日志:支持在线查看调度结果,并且支持以Rolling方式实时查看执行器输出的完整的执行日志;
    13、GLUE:提供Web IDE,支持在线开发任务逻辑代码,动态发布,实时编译生效,省略部署上线的过程。支持30个版本的历史版本回溯。
    14、数据加密:调度中心和执行器之间的通讯进行数据加密,提升调度信息安全性;
    15、任务依赖:支持配置子任务依赖,当父任务执行结束且执行成功后将会主动触发一次子任务的执行, 多个子任务用逗号分隔;
    16、推送maven中央仓库: 将会把最新稳定版推送到maven中央仓库, 方便用户接入和使用;
    17、任务注册: 执行器会周期性自动注册任务, 调度中心将会自动发现注册的任务并触发执行。同时,也支持手动录入执行器地址;
    18、路由策略:执行器集群部署时提供丰富的路由策略,包括:第一个、最后一个、轮询、随机、一致性HASH、最不经常使用、最近最久未使用、故障转移、忙碌转移等;
    19、运行报表:支持实时查看运行数据,如任务数量、调度次数、执行器数量等;以及调度报表,如调度日期分布图,调度成功分布图等;
    20、脚本任务:支持以GLUE模式开发和运行脚本任务,包括Shell、Python等类型脚本;
    21、阻塞处理策略:调度过于密集执行器来不及处理时的处理策略,策略包括:单机串行(默认)、丢弃后续调度、覆盖之前调度;
    22、失败处理策略;调度失败时的处理策略,策略包括:失败告警(默认)、失败重试;
    23、分片广播任务:执行器集群部署时,任务路由策略选择"分片广播"情况下,一次任务调度将会广播触发对应集群中所有执行器执行一次任务,同时传递分片参数;可根据分片参数开发分片任务;
    24、动态分片:分片广播任务以执行器为维度进行分片,支持动态扩容执行器集群从而动态增加分片数量,协同进行业务处理;在进行大数据量业务操作时可显著提升任务处理能力和速度。
    25、事件触发:除了"Cron方式"和"任务依赖方式"触发任务执行之外,支持基于事件的触发任务方式。调度中心提供触发任务单次执行的API服务,可根据业务事件灵活触发。

    XXLJOBGitHub地址
    https://github.com/xuxueli/xxl-job
    在这里插入图片描述
    在这里插入图片描述
    然后把xxl-job-master导入进去
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    然后把xxl-job-admin添加到tomcat上运行:然后在浏览器输入地址访问效果如下:
    在这里插入图片描述

    分布式任务调度原理:
    执行器:就是执行任务的机器
    在这里插入图片描述

    调度中心集群

    (可选):
    调度中心支持集群部署,提升调度系统可用性。
    集群部署唯一要求为:保证每个集群节点配置(db和登陆账号等)保持一致。调度中心通过db配置区分不同集群。
    调度中心在集群部署时可通过nginx负载均衡,此时可以为集群分配一个域名。该域名一方面可以用于访问,另一方面也可以用于配置执行器回调地址。
    注意:这里的xxl.job.admin.address后面的ip地址+端口号是我们部署xxl-job-admin所在的tomcat+端口号
    在这里插入图片描述
    这个时候我们新建一个执行器(这里有一个小错误,就是机器地址的ip和端口号之间的冒号应该填英文的,但是我下面的这个图却填错了)
    在这里插入图片描述
    新建一个任务(下图有一个小错误,就是Cron应该是0/2 * * * * ?,2和*之间有空格,之间也有空格,*和?之间也有空格)
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    然后启动xxl-job-admin(发布到tomcat上,然后启动tomcat即可),这个时候控制台可能会报错,这是因为任务会默认启动,而执行器却没有启动(任务首先在任务管理平台启动。之后再由任务管理平台分发到执行器上执行),解决办法很简单,那就是点击“暂停”按钮,暂停之后再启动xxl-job-executer,即可
    在这里插入图片描述
    看到如下效果说明成功了!!!
    在这里插入图片描述

    也可以根据上面的执行器sample自己编一个执行器。

    在这里插入图片描述

    展开全文
  • 任务调度的实现总结

    2018-08-16 11:53:35
    任务调度的实现总结 前言 我们的应用程序有些定时任务(例如想在凌晨十二点半统计某个互联网金融公司一款借款APP前一天的借款、还款以及逾期情况)需要在指定时间内执行或者周期性执行某个任务(比如每月最后...
  • 任务调度

    2019-02-20 16:04:00
    Quartz对任务调度进行了高度抽象,提出了3个核心概念,并在org.quartz包中通过接口和类进行了描述 任务:就是执行的工作内容。Quartz提供Job接口来支持任务定义 触发器:定义触发Job执行的时间触发规则。Quartz...
  • 分布式任务调度

    2019-02-02 14:51:09
    前言任务调度可以说是所有系统都必须要依赖的一个中间系统,主要负责触发一些需要定时执行的任务。传统的非分布式系统中,只需要在应用内部内置一些定时任务框架,比如 spring 整合 quartz,就可以完成一些定时任务...
  • 总结篇-定时调度任务的几种方式

    千次阅读 2018-08-07 12:31:58
    任务调度是指基于给定时间点,给定时间间隔或者给定执行次数自动执行任 1.前言 我们举一个简单的例子:创建一个thread,然后让它在while循环里一直运行着,通过sleep方法来达到定时任务的效果。这样可以快速简单的...
  • Linux的任务调度机制

    万次阅读 2016-04-29 11:07:20
    通用Linux系统支持实时和非实时两种进程,实时进程相...在调度算法的实现上,Linux中的每个任务有四个与调度相关的参数,它们是rt_priority、policy、priority(nice)、counter。调度程序根据这四个参数进行进程调度
  • 实习生张大胖 这是个代码写得很烂的电商系统,只要运行一段时间,服务器就会出现Out Of Memory。 别人都忙得四脚朝天,于是实习生张大胖被抓了壮丁去研究为什么会出现OOM。 刚入行的张大胖技术水平一般,...
  • 异步任务 @Service public class AsyncService { @Async //告诉Spring,这是一个异步方法 public void hello() { try { Thread.sleep(3000); } catch (InterruptedException e) { ...
  • 使用vs2019创建Worker Service程序 首先nuget安装 Microsoft.Extensions.Hosting.Windows 在Program中添加UseWindowsService() public class Program { public static void Main(string[] args) ...
  • 一、简介 crond是Linux下用来周期性的执行某种任务或等待处理某些事件的一个守护进程,与windows... crond进程定期(每分钟)检查是否有要执行的任务,如果有要执行的任务,则自动执行该任务。用户在cron表 (也被称...
  • FreeRTOS任务调度研究

    千次阅读 2017-08-17 22:51:32
    所以不涉及对FreeRTOS整体的介绍,而只是分析任务调度这一块的机制。对应的Demo参考自CORTEX_A9_Zynq_ZC702。 一、触发任务调度的两种机制 taskYIELD() 这种机制其实是通过ARM swi 异常触发task context switch...
  • 【UCOSIII】UCOSIII的任务调度和切换

    万次阅读 2018-06-22 18:27:44
    UCOSIII任务调度 可剥夺型任务调度 任务调度就是中止当前正在运行的任务转而去执行其他的任务。 UCOSIII是可剥夺型内核,因此当一个高优先级的任务准备就绪,并且此时发生了任务调度,那么这个高优先级的任务就会...
  • 定时任务调度

    千次阅读 2017-06-02 18:00:26
    cron4j特点:最大的特点就是小巧,简单,... 可以执行一些简单的定时调度功能,太复杂的还是用quartz比较好。定时任务:public class CronJob implements Runnable{ public void run() { System.out.println("任务执行
  • Quartz 是个开源的作业调度框架,为在 Java 应用程序中进行作业调度提供了简单却强大的机制。Quartz框架包含了调度器监听、作业和触发器监听。你可以配置作业和触发器监听为全局监听或者是特定于作业和触发器的监听...
  • ucosIII任务调度原理解析

    千次阅读 2019-01-22 21:12:39
    μcosIII任务调度原理解析前言μcosIII任务调度相关的数据结构任务控制块 OS_TCB就绪任务表结构μcosIII时间节拍轮任务阻塞表任务调度实现细节任务调度点时钟节拍轮相关调度任务阻塞表相关调度就绪任务表相关调度...
  • 集群资源管理与任务调度系统综述

    千次阅读 2019-05-03 23:49:49
    0. 集群资源管理与任务调度系统出现的背景 (1)出现背景 信息技术快速发展,各行各业都慢慢于互联网进行深度融合,即所谓的“互联网+”。为了提供更好的服务以吸引更多的消费者进行更多维度的消费,各个互联网公司...
  • 实时操作系统任务调度

    千次阅读 2014-11-18 18:55:00
    最近看了一些实时操作系统的源码,关于任务调度是实时操作系统的重要组成部分,但是何时发生调度,怎样才能发生调度却不是非常的清晰,书中一本而言所说的都是“如果有更高优先级任务就绪,就会发生调度”,这会让很...

空空如也

1 2 3 4 5 ... 20
收藏数 357,437
精华内容 142,974
关键字:

任务调度