任务调度_任务调度系统 - CSDN
精华内容
参与话题
  • 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

    展开全文
  • 任务调度

    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

     

     

    展开全文
  • 总结篇-定时调度任务的几种方式

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

    任务调度是指基于给定时间点,给定时间间隔或者给定执行次数自动执行任

    1.前言

    我们举一个简单的例子:创建一个thread,然后让它在while循环里一直运行着,通过sleep方法来达到定时任务的效果。这样可以快速简单的实现。

    public static void main(String[] args) {
            final long timeInterval = 1000;
            Runnable runnable = new Runnable() {
                public void run() {
                    while (true) {
                        System.out.println("Hello !!");
                        try {
                            Thread.sleep(timeInterval);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            };
            Thread thread = new Thread(runnable);
            thread.start();
        }

    但这样很耗费系统资源,扩展性,满足业务需要都不是很好。


    2.任务调度方式

    Timer
    ScheduledExecutor
    JCronTab
    开源工具包 Quartz


    1) Timer

    相信大家都已经非常熟悉 java.util.Timer 了,它是最简单的一种实现任务调度的方法
    特点:in JDK,简洁,单线程

    public static void main(String[] args){  
       Timer timer = new Timer();  
       timer.schedule(new TimerTask(){
            @Override  
            public void run() {  
               System.out.println("do sth...");  
            }  
        }, 1000, 2000);  
    }

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


    2) ScheduledExecutor

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

    这里写图片描述

    package io.renren.modules.job.task;
    
    import java.util.concurrent.Executors;
    import java.util.concurrent.ScheduledExecutorService;
    import java.util.concurrent.TimeUnit;
    
    /**
     * @author lanxinghua
     * @date 2018/08/07 12:45
     * @description
     */
    public class ScheduleExecutorTest implements Runnable {
    
        private String jobName="";
    
        public ScheduleExecutorTest(String jobName) {
            this.jobName = jobName;
        }
    
        @Override
        public void run() {
            System.out.println("executor "+jobName+Thread.currentThread().getName());
        }
    
        public static void main(String[] args) {
            ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
            ScheduleExecutorTest job1 = new ScheduleExecutorTest("job1");
            service.scheduleAtFixedRate(job1,1,1,TimeUnit.SECONDS);
            service.scheduleAtFixedRate(job1,2,1,TimeUnit.SECONDS);
        }
    }
    用 ScheduledExecutor 和 Calendar 实现复杂任务调度

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

    计算出间隔时间,计算从当前时间到最近一次执行时间的时间间隔

    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);

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


    3) JCronTab

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

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


    4) Quartz(重点了解)

    Quartz是个开源JAVA库,可以简单看做以上三种的结合的扩展。
    这里写图片描述
    这里写图片描述

    Scheduler:调度容器
    Job:Job接口类
    JobDetail :Job的描述类,job执行时的依据此对象的信息反射实例化出Job的具体执行对象。
    Trigger:存放Job执行的时间策略
    JobStore: 存储作业和调度期间的状态
    Calendar:指定排除的时间点(如排除法定节假日)

    这里写图片描述
    ]

    Quartz的主要线程有两类,负责调度的线程和负责Misfire(指错过了执行时间的作业)的线程,其中负责调度的线程RegularSchedulerThread是基于线程池的,而Misfire只有一个线程。 两类线程都会访问抽象为JobStore的层来获取作业策略或写入调度状态。JobStore也分持久化(JobStoreSupport)和非持久化(RAMJobStore)两种,使用场景大大不同.

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

    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);
    
    
    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");        

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

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

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

    Cron 表达式,由七个字段组成:

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

    创建一个每十分钟执行的 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

    通配符 * 表示该字段可接受任何可能取值。例如 Month 字段赋值 * 表示每个月,Day-of-Week 字段赋值 * 表示一周的每天。
    / 表示开始时刻与间隔时段。例如 Minutes 字段赋值 2/10 表示在一个小时内每 20 分钟执行一次,从第 2 分钟开始。
    ? 仅适用于 Day-of-Month 和 Day-of-Week。? 表示对该字段不指定特定值。

    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,灵活性很强。

    package io.renren.modules.job.task;
    
    import com.alibaba.fastjson.JSON;
    import org.quartz.*;
    import org.quartz.impl.JobDetailImpl;
    import org.quartz.impl.StdSchedulerFactory;
    
    import java.util.Date;
    import java.util.List;
    
    /**
     * @author lanxinghua
     * @date 2018/08/07 14:09
     * @description
     */
    public class QuartzTest implements Job {
        @Override
        public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
            JobDetail jobDetail = jobExecutionContext.getJobDetail();
            System.out.println(jobDetail.getKey());
            System.out.println(jobDetail.getJobDataMap().get("type"));
        }
    
    
        public static void main(String[] args) throws SchedulerException {
            //创建一个Scheduler
            SchedulerFactory schedulerFactory = new StdSchedulerFactory();
            Scheduler scheduler = schedulerFactory.getScheduler();
            //创建JobDetail,name,gruopname,类名
            JobDetail jobDetail = JobBuilder.newJob(QuartzTest.class)
                    .withIdentity("jobname","jobgroup")
                    .requestRecovery()
                    .build();
            jobDetail.getJobDataMap().put("type","FULL");
            //创建一个每周触发的Trigger
            SimpleTrigger trigger = (SimpleTrigger) TriggerBuilder.newTrigger()
                    .withIdentity("triggerName")
                    .startAt(new Date())
                    .build();
            scheduler.scheduleJob(jobDetail,trigger);
            scheduler.start();
            System.out.println(trigger.getJobKey());
        }
    }

    这里写图片描述

    补充一个知识点:jobDetail.getKey(), trigger.getJobKey()

     JobDetail jobDetail = JobBuilder.newJob(QuartzTest.class)
                    .withIdentity("task1")
                    .requestRecovery()
                    .build();
    scheduler.scheduleJob(jobDetail,trigger);

    jobDetail.getKey(): DEFAULT.task1
    trigger.getJobKey() : DEFAULT.task1
    TriggerKey triggerKey = TriggerKey.triggerKey(“task1”);
    这里写图片描述
    如果你设置withIdentity(“jobname”,”jobgroup”);那default就不是默认的组了。

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

    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();
                }
            }
        }
    }

    需要将 listener 的实现类注册到 Scheduler 和 JobDetail 中:

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

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

    sched.addGlobalJobListener(new MyListener());


    3.总结

    四种常用的对任务进行调度的 Java 实现方法,即 Timer,ScheduledExecutor, Quartz 以及 JCronTab。

    1. 对于简单的基于起始时间点与时间间隔的任务调度,使用 Timer 就足够了;
    2. 如果需要同时调度多个任务,基于线程池的 ScheduledTimer 是更为合适的选择;
    3. 当任务调度的策略复杂到难以凭借起始时间点与时间间隔来描述时,Quartz 与 JCronTab 则体现出它们的优势。

    熟悉 Unix/Linux 的开发人员更倾向于 JCronTab,且 JCronTab 更适合与 Web 应用服务器相结合。Quartz 的 Trigger 与 Job 松耦合设计使其更适用于 Job 与 Trigger 的多对多应用场景。

    展开全文
  • 实习生张大胖 这是个代码写得很烂的电商系统,只要运行一段时间,服务器就会出现Out Of Memory。 别人都忙得四脚朝天,于是实习生张大胖被抓了壮丁去研究为什么会出现OOM。 刚入行的张大胖技术水平一般,...

    实习生张大胖

     

    这是个代码写得很烂的电商系统,只要运行一段时间,服务器就会出现Out Of Memory。

     

    别人都忙得四脚朝天,于是实习生张大胖被抓了壮丁去研究为什么会出现OOM。

     

    刚入行的张大胖技术水平一般,“装模作样”地看代码,研究日志,请教老员工,一个星期过去了,还是一无所获。

     

    周一例行的项目会议上, 大家似乎要看张大胖的笑话了,没想到他却提了一个歪招:“这个OOM问题非常复杂,一时半会儿也解决不了,要不我们定时重启服务器怎么样?”

     

    一脸严肃的项目经理老梁点点头:“以目前的情况看,也只能如此了。但是不能让服务中断,这样吧,公司有两台服务器,一台在凌晨1点重启, 另外一台在凌晨2点重启。”

     

    得到了领导的首肯,张大胖赶紧行动,周末他其实已经做了准备,研究了Linux上的crontab,它的格式是这样样子:

     

     

    每天凌晨一点重启系统,可以这么写:

    0 1 * * *  restart.sh

     

    (注:这里只是个简单的例子, 实际上crontab及其灵活)

     

    这个OOM的问题被张大胖灵机一动给解决了,或者说,被临时隐藏了。

     

    crontab达人的烦恼

     

    大家知道张大胖擅长crontab, 都把一些定时的任务扔给他去做: 什么定时统计报表,定时同步数据,定时删除表中的无效订单...... 等等。

     

    张大胖整天面对的就是crontab和脚本,都快要吐了。

     

    不仅如此,同事们还经常提出一些“变态”的需求:

    “大胖,那个定时任务运行得怎么样了?”

    “大胖,我想把那个定时任务给停掉。”

    “大胖,那个定时任务今晚别运行啊!”

    “......”

     

    张大胖真是烦死了,他心想,要是提供个界面让大家使用就好了, 可是crontab似乎并不支持。

     

    要不自己开发一个?

     

    有一次张大胖偶然发现了JDK中的Timer类,似乎也是做这些定时任务的, 不由地眼前一亮,但是仔细研究以后就发现,JDK的Timer还是太简单了,做点简单的定时任务还行, 对于复杂的情况,尤其是复杂的时间策略,还是力不从心。

     

    另起炉灶

     

    看来自己需要从头设计了, 小张用“正交”的原则设计出了Logger, Appender, Formatter这些类。

     

    我也可以使用同样的原则啊,小张能行,我凭什么不行?

     

    说干就干,先想想需求,非常简单,不就是定时地执行任务嘛!

     

    “任务”应该是正交中的一个“维度”,我可以抽象出一个接口叫做Task , 嗯,还是叫做Job吧。

     

    对使用者来说,他需要提供一个实现类出来,在实现类中描述要做什么事情,比如:生成报表,复制数据......

     

     

    “定时”该怎么处理? 定时,定时触发,干脆叫做Trigger吧。

     

    这个Trigger 可以指定什么时间开始,时间间隔,运行多少次, 能覆盖大部分需求了。

     

    可是张大胖转念一想,如果有人要求类似日历的重复间隔该怎么处理? 比如每月的第一天运行,或者每周的最后一天运行,该怎么办?  crontab特别适合描述这种情况,对,可以搞一个类似于crontab的Trigger。

     

    看来Trigger最好也是个接口,我来提供几个默认的实现,比如SimpleTrigger,CronTrigger,用户还可以扩展,这样就灵活了。

     

     

    Job和Trigger也是正交的关系, 两者可以互不影响,可以独立扩展,真是不错, 张大胖不仅得意起来,这设计也很简单嘛!

     

    但是怎么把这两个家伙结合起来?

     

    必须得有个“大管家”才行,这个大管家应该可以接受Job, 然后按照各种Trigger去运行,嗯,叫做调度器Scheduler应该不错。

     

    张大胖画了个草图,来展示三者之间的关系:

     

     

    设计得差不多了,可以进入开发阶段了, 因为是自己要写一个类似于框架的东西,让别人去使用,张大胖开发起来非常有激情,即使是利用晚上和周末的时间来写代码,也是像打了鸡血一样,根本不觉得累。

     

    一个月过去了,第一版新鲜出炉。

     

    这个版本不仅有核心的API像Job, Trigger, Scheduler ,张大胖还专门开发了一个界面,用来展示定时任务的进展,例如什么时间运行,运行了几次,失败了几次......等等。

     

    张大胖把它叫做“大胖定时任务调度系统”。

     

    持久化

     

    他兴奋地拿去让项目经理老梁看, 可是老梁并不感冒,面无表情地说:“你这个小软件有啥用啊。”

     

    张大胖被泼了一盆冷水,依然热情满满地推销:“用了我的这个定时调度系统,任何人都可以轻松地启动,停止任务, 咱们项目中所有的定时任务一目了然。 大家就不用找我来手工调整了。”

     

    老梁开玩笑地说:“奥,那你的实习工作就可以结束了,哈哈。”

     

    正巧CTO Bill经过,他饶有兴趣地看了一会,提了一个问题:“假设你这个大胖调度系统在运行的时候,机器突然间Down掉了,怎么处理?”

     

    张大胖一脸懵逼:“什么怎么处理,重启机器呗。”

     

    Bill 说: “之前的任务还能接着运行吗,比如说一个任务需要运行100次,在机器down掉之前运行了90次,重启后能不能从第91次运行?”

     

    张大胖有点发窘,不好意思地挠挠头:“这一点我还真没考虑到,我现在都是在内存中记录运行的情况,看来得做持久化了。”

     

    Bill 听到持久化这个词,知道张大胖已经Get到了,他说,你把这个持久化实现了,到时候直接向我汇报。

     

    得到了CTO的赏识,张大胖不敢怠慢,赶紧进行新的设计, 他抽象了一个叫做JobStore的接口,表示Job的存储,像什么Job,Trigger, Job运行情况都存储在其中。 

     

    下面有两个实现,分别对应内存存储和数据库存储。

     

     

    虽然SQL是标准的,但是不同的数据库还是有细微的差异, 张大胖觉得得把这些差异给封装起来, 他又提取了一个接口叫做DriverDelegate, 屏蔽了数据库细节,让DbJobStore使用。

     

    他还提供了一个缺省的实现StdJDBCDelegate,如果那些数据库还有独特的实现,那就写个子类就行了。

     

     

    高可用

     

    “大胖定时任务调度系统 2.0” 开发完成以后,张大胖仔细地想了一遍,似乎没有什么漏洞了,决定正式向CTO Bill去汇报。

     

    Bill 亲切地询问了张大胖加班加点设计和开发的情况,对他这种不计较个人得失,一心一意为公司谋福利的精神表示了高度的赞赏。

     

    张大胖受宠若惊。

     

    Bill话锋一转:“我们的系统最近用户越来越多,老板特别提出了高可用的需求,系统的各个组件也得达到高可用!”

     

    “高可用? 拿我的定时调度系统来说,就是说可以部署在多个机器上,一个down掉了,其他的还可以运行,对吧?” 张大胖一点就透。

     

    Bill 赞许地点点头:“你想好怎么去实现了吗?”

     

    “很简单啊,把定时调度系统部署到多个机器上,形成几个备份就行了!”

     

    张大胖还在白板上画了这么一个图:

     

     

    “那同一个时刻,有多少个Scheduler 在运行?”  Bill 终于抛出了重磅炸弹。

     

    张大胖现在明白Bill的疑问了了,三个实例都在运行,那一个Job就有可能运行多次,这肯定是不行的!

     

    他说道:“要不让三个实例A,B,C都去访问同一个数据库吧!”

     

    Bill说:“那三个实例访问同一份数据,肯定会出现冲突,互相覆盖,那就乱套了!”

     

    其实,实例A,实例B,实例C组成一个类似集群的东西,但是同一时刻,一个Job只能在一个实例上运行。

     

    比如Job X 从凌晨1点开始,每隔1小时运行一次,那1:00 的时候Job X可能在实例A上运行, 2:00的时候可能在实例B上运行, 3:00的时候可能在实例C上运行。

     

    也就是说,这三个实例部分地实现了负载均衡。

     

    张大胖说:“这可就难办了。难道让这三个实例A,B,C之间互相通信?”

     

    Bill说道:“那样有点麻烦,就变成一个分布式系统下的通信问题了,我们要不用这个数据库做点文章? 反正这个数据库已经存了Job的信息,Trigger的信息,我们就多加一个表吧,就叫LOCKS,这个表里边每一行记录都可以当做一个‘锁’来用。”

     

    张大胖表示不太明白。

     

    “很简单,就是数据库的‘行’锁嘛, 比如SELECT * FROM LOCKS where LOCK_NAME='TRIGGER' FOR UPDATE ,这就把那一行记录给锁住了, 别的事务只能等待当前事务commit以后才能访问。”

     

    张大胖还是不太明白。

     

    “比如,服务器A的实例A在一个事务中先执行了上面SQL, 就把那一行给锁住了,当服务器B的实例B也去执行同样的SQL的时候, 只能等待,对吧? 这不就相当于实例A获得了锁吗?”

     

    “原来如此,以后任何一个调度器实例想要获取Job的运行时间,设置Job的下一次运行时间的时候,都必须先获得这个锁。这样这些分布式的调度器就不会冲突了,只会运行一个特定时间的Job。 我这就去做个详细设计,再来汇报。”

     

    开源

     

    两个月后,“大胖定时任务调度系统 3.0” 开发完毕,在Bill的大力支持和推动下,成功地应用在了公司的项目中。

     

    灵活的设计和扩展性,加上持久化,集群等强大的功能, 系统受到了大家的欢迎。

     

    考虑到很多公司都会有类似的需求,Bill决定把系统开源, 只是“大胖定时任务调度系统”这个名字有点俗,还有点长,Bill把它改名为“Quartz”。

     

    Quartz从此流行开来。

    展开全文
  • 分布式任务调度

    千次阅读 2019-08-27 14:56:54
    什么是定时任务? 指定时间去执行任务 Java实现定时任务方式 1.Thread public class Demo01 { static long count = 0; public static void main(String[] args) { Runnable runnable = new Runnable() { ...
  • 4种任务调度

    2011-10-26 21:18:42
    任务调度是指基于给定时间点,给定时间间隔或者给定执行次数自动执行任务。本文由浅入深介绍四种任务调度的 Java 实现: Timer ScheduledExecutor 开源工具包 Quartz 开源工具包 JCronTab 此外,为结...
  • java实现任务调度

    千次阅读 2018-09-23 00:33:40
    今天为什么要写点东西呢,因为我觉得自己现在开始有意思的去培养出写代码之外的能力,所以给自己定下一个目标,每个星期至少写点东西来。 聊些什么呢?那就聊自己的状态吧!因为觉得最近的状态自己特别二,干什么...
  • Java的任务调度

    千次阅读 2018-10-11 09:21:57
     JobDetail为Job实例提供了许多设置属性,以及JobDataMap成员属性变量,它用来存储特定的Job实例的状态信息,调度器需要借助JobDetail对象来添加Job实例 1.1 重要属性  name  jobClass  group  ...
  • Linux的任务调度机制

    万次阅读 2016-04-29 11:07:20
    通用Linux系统支持实时和非实时两种进程,实时进程相...在调度算法的实现上,Linux中的每个任务有四个与调度相关的参数,它们是rt_priority、policy、priority(nice)、counter。调度程序根据这四个参数进行进程调度
  • OSC开源社区 2017-04-21 11:27 ...分布式调度在互联网企业中占据着十分重要的作用,尤其是电子商务领域,由于...开源中国任务调度系统/框架类别下有非常多的开源项目,其中不乏出自国内外知名企业的优秀大作。
  • XXL-JOB是一个轻量级分布式任务调度平台,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。 二、使用细节 1、调度中心集群 调度中心支持集群部署,提升调度系统容灾和可用性。 调度中心集群部署时,几点要求和...
  • 各种调度框架介绍

    万次阅读 2017-03-01 15:25:57
    任务调度工具 Myriad Myriad Myriad把YARN和Mesos两者的优势结合起来。通过使用Myriad项目,让Mesos和YARN可以协作,你可以完成一个实时业务。数据分析可以在运行生产服务的相同硬件上执行。... 上次更新: ...
  • 任务调度算法汇总

    万次阅读 2016-10-05 13:54:54
    任务调度算法汇总
  • 轮询任务调度与抢占式任务调度的区别在于抢占式调度中的优先级可以抢占CPU,而轮询的不能。具体而言,轮询调度的原理是每一次把来自用户的请求轮流的分配给内部服务器,从1开始,直到N(内部服务器的个数),然
  • hera(赫拉)任务调度系统–为数据平台打造的任务调度系统 hera项目背景  在大数据部门,随着业务发展,每天承载着成千上万的ETL任务调度,这些任务集中在hive,shell脚本调度。怎么样让大量的ETL任务准确的完成...
  • 分布式任务调度系统选型

    千次阅读 2018-03-22 20:09:32
    分布式任务调度系统选型更多干货分布式实战(干货)spring cloud 实战(干货)mybatis 实战(干货)spring boot 实战(干货)React 入门实战(干货)构建中小型互联网企业架构(干货)python 学习持续更新分布式调度...
  • 第四节: VxWorks任务调度机制 在操作系统中,任务调度存在两种方式:基于优先级调度和基于时间片调度。嵌入式系统中任务调度一般都是基于优先级的调度方式,VxWorks也就是传说中的抢占式调度。有没有方法可以关闭...
  • linux内核的三种调度方法:1,SCHED_OTHER 分时调度策略,2,SCHED_FIFO实时调度策略,先到先服务3,SCHED_RR实时调度策略,时间片轮转 实时进程将得到优先调用,实时进程根据实时优先级决定调度权值,分时进程则...
  • oracle 查看调度作业和job的表

    万次阅读 2016-11-28 15:51:29
    user_SCHEDULER_JOBS --调度作业表 user_jobs --定时任务
  • 【操作系统】实时调度

    万次阅读 2016-12-03 15:56:24
    实现实时调度的基本条件 提供必要的调度信息(就绪时间、开始截止时间和完成截止时间、处理时间、资源要求、优先级) 系统处理能力强。在实时系统中,若处理机的处理能力不够强,则有可能因处理机忙不过来而致使某些...
1 2 3 4 5 ... 20
收藏数 345,047
精华内容 138,018
关键字:

任务调度