前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >定时任务实现的几种方式

定时任务实现的几种方式

作者头像
疯狂的KK
发布2019-08-16 15:25:01
2K0
发布2019-08-16 15:25:01
举报
文章被收录于专栏:Java项目实战

Quartz:翻译是石英,手上石英表表示天生是个定时框架,既然网上资料一大堆,不如查查官网记录。

资料地址:https://www.w3cschool.cn/quartz_doc/quartz_doc-1xbu2clr.html

定时任务实现的几种方式:

Timer:这是java自带的java.util.Timer类,这个类允许你调度一个java.util.TimerTask任务。使用这种方式可以让你的程序按照某一个频度执行,但不能在指定时间运行。一般用的较少。

ScheduledExecutorService:也是jdk自带的一个类;是基于线程池设计的定时任务类,每个调度任务都会分配到线程池中的一个线程去执行,也就是说,任务是并发执行,互不影响。

Spring Task:Spring3.0以后自带的task,可以将它看成一个轻量级的Quartz,而且使用起来比Quartz简单许多。

Quartz:这是一个功能比较强大的的调度器,可以让你的程序在指定时间执行,也可以按照某一个频度执行,配置起来稍显复杂。

一、Quartz API,Jobs和Triggers

什么是Quartz?

Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,完全由Java开发,可以用来执行定时任务,类似于java.util.Timer。但是相较于Timer, Quartz增加了很多功能:持久性作业 - 就是保持调度定时的状态;作业管理 - 对调度作业进行有效的管理;

定时任务的平时需求还是比较多,查询资料转载后整理。例如:定时打印token,下单后定时查看订单状态,外卖平台,优惠券等

在我们实际的项目中,当Job过多的时候就需要一个任务调度框架,帮我们自动去执行这些程序。那么该如何实现这个功能呢?

(1)首先我们需要定义实现一个定时功能的接口,我们可以称之为Task(或Job),如定时发送邮件的task(Job),重启机器的task(Job),优惠券到期发送短信提醒的task(Job),实现接口如下:

(2)有了任务之后,还需要一个能够实现触发任务去执行的触发器,触发器Trigger最基本的功能是指定Job的执行时间,执行间隔,运行次数等。

(3)有了Job和Trigger后,怎么样将两者结合起来呢?即怎样指定Trigger去执行指定的Job呢?这时需要一个Schedule,来负责这个功能的实现。

调度器:Scheduler

任务:JobDetail

触发器:Trigger,包括SimpleTrigger和CronTrigger

二、Quartz快速入门

下面来利用Quartz搭建一个最基本的Demo。

1、导入依赖的jar包:

org.quartz-scheduler quartz 2.3.0

2、新建一个能够打印任意内容的类:

public class PrintWordsJob implements Job{ @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { String printTime = new SimpleDateFormat("yy-MM-dd HH-mm-ss").format(new Date()); System.out.println("PrintWordsJob start at:*************" + printTime + ", prints: Hello Job---------------" + new Random().nextInt(100)); } }

3、实例化并启动一个scheduler,调度执行一个job

SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory(); Scheduler sched = schedFact.getScheduler(); sched.start(); // define the job and tie it to our HelloJob class JobDetail job = newJob(HelloJob.class) .withIdentity("myJob", "group1") .build(); // Trigger the job to run now, and then every 40 seconds Trigger trigger = newTrigger() .withIdentity("myTrigger", "group1") .startNow() .withSchedule(simpleSchedule() .withIntervalInSeconds(40) .repeatForever()) .build(); // Tell quartz to schedule the job using our trigger sched.scheduleJob(job, trigger);

三、使用Quartz

下面就程序中出现的几个参数,看一下Quartz框架中的几个重要参数:

Job和JobDetail

JobExecutionContext

JobDataMap

Trigger、SimpleTrigger、CronTrigger

(1)Job和JobDetail

Job是Quartz中的一个接口,接口下只有execute方法,在这个方法中编写业务逻辑。

接口中的源码:

package org.quartz;public interface Job { void execute(JobExecutionContext var1) throws JobExecutionException;}

JobDetail用来绑定Job,为Job实例提供许多属性:

name

group

jobClass

jobDataMap

JobDetail绑定指定的Job,每次Scheduler调度执行一个Job的时候,首先会拿到对应的Job,然后创建该Job实例,再去执行Job中的execute()的内容,任务执行结束后,关联的Job对象实例会被释放,且会被JVM GC清除。

为什么设计成JobDetail + Job,不直接使用Job

我们传给scheduler一个JobDetail实例,因为我们在创建JobDetail时,将要执行的job的类名传给了JobDetail,所以scheduler就知道了要执行何种类型的job;每次当scheduler执行job时,在调用其execute(…)方法之前会创建该类的一个新的实例;执行完毕,对该实例的引用就被丢弃了,实例会被垃圾回收;这种执行策略带来的一个后果是,job必须有一个无参的构造函数(当使用默认的JobFactory时);另一个后果是,在job类中,不应该定义有状态的数据属性,因为在job的多次执行中,这些属性的值不会保留

(2)JobExecutionContext

JobExecutionContext中包含了Quartz运行时的环境以及Job本身的详细数据信息。

当Schedule调度执行一个Job的时候,就会将JobExecutionContext传递给该Job的execute()中,Job就可以通过JobExecutionContext对象获取信息。

主要信息有:

public interface JobExecutionContext { Scheduler getScheduler(); Trigger getTrigger(); Calendar getCalendar(); boolean isRecovering(); TriggerKey getRecoveringTriggerKey() throws IllegalStateException; int getRefireCount(); JobDataMap getMergedJobDataMap(); JobDetail getJobDetail(); Job getJobInstance(); Date getFireTime(); Date getScheduledFireTime(); Date getPreviousFireTime(); Date getNextFireTime(); String getFireInstanceId(); Object getResult(); void setResult(Object var1); long getJobRunTime(); void put(Object var1, Object var2); Object get(Object var1);}

(3)JobExecutionContext

Jobs很容易实现,在接口中只有一个“execute”方法。本节主要关注:Job的特点、Job接口的execute方法以及JobDetail。

你定义了一个实现Job接口的类,这个类仅仅表明该job需要完成什么类型的任务,除此之外,Quartz还需要知道该Job实例所包含的属性;这将由JobDetail类来完成。

JobDetail实例是通过JobBuilder类创建的,导入该类下的所有静态方法,会让你编码时有DSL的感觉:

如:

obDetail jobDetail = JobBuilder.newJob(PrintWordsJob.class) .usingJobData("jobDetail1", "这个Job用来测试的") .withIdentity("job1", "group1").build(); Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "triggerGroup1") .usingJobData("trigger1", "这是jobDetail1的trigger") .startNow()//立即生效 .withSchedule(SimpleScheduleBuilder.simpleSchedule() .withIntervalInSeconds(1)//每隔1s执行一次 .repeatForever()).build();//一直执行

Job执行的时候,可以获取到这些参数信息:

@Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { System.out.println(jobExecutionContext.getJobDetail().getJobDataMap().get("jobDetail1")); System.out.println(jobExecutionContext.getTrigger().getJobDataMap().get("trigger1")); String printTime = new SimpleDateFormat("yy-MM-dd HH-mm-ss").format(new Date()); System.out.println("start:" + printTime + " 第-" + new Random().nextInt(100)); }

(4)Trigger

Trigger

与job一样,trigger也很容易使用,但是还有一些扩展选项需要理解,以便更好地使用quartz。trigger也有很多类型,我们可以根据实际需要来选择。。

new Trigger().startAt():表示触发器首次被触发的时间;new Trigger().endAt():表示触发器结束触发的时间;

SimpleTrigger

SimpleTrigger可以满足的调度需求是:在具体的时间点执行一次,或者在具体的时间点执行,并且以指定的间隔重复执行若干次。比如,你有一个trigger,你可以设置它在2015年1月13日的上午11:23:54准时触发,或者在这个时间点触发,并且每隔2秒触发一次,一共重复5次。

下面的程序就实现了程序运行5s后开始执行Job,执行Job 5s后结束执行:

/** * @author zhaokkstart * @create 2019-07-02 16:13 */public class MyScheduler { public static void main(String[] args) throws SchedulerException { StdSchedulerFactory stdSchedulerFactory = new StdSchedulerFactory(); Scheduler scheduler = stdSchedulerFactory.getScheduler(); JobDetail jobDetail = JobBuilder.newJob(PrintWordsJob.class) .withIdentity("job1", "group1") .build(); Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("trigger1", "triggerGroup1") .startNow() .withSchedule(SimpleScheduleBuilder.simpleSchedule() .withIntervalInSeconds(1)//每隔1s执行一次 .repeatForever()).build(); scheduler.scheduleJob(jobDetail,trigger); System.out.println("--------start ! ------------"); scheduler.start(); try{ TimeUnit.SECONDS.sleep(10);}catch(InterruptedException e) {e.printStackTrace();} scheduler.shutdown(); System.out.println("--------end ! ------------"); }}

CronTrigger

CronTrigger通常比Simple Trigger更有用,如果您需要基于日历的概念而不是按照SimpleTrigger的精确指定间隔进行重新启动的作业启动计划。CroTrigger是基于Cron表达式的,先了解下Cron表达式:

其中每个元素可以是一个值(如6),一个连续区间(9-12),一个间隔时间(8-18/4)(/表示每隔4小时),一个列表(1,3,5),通配符。由于”月份中的日期”和”星期中的日期”这两个元素互斥的,必须要对其中一个设置

如:* 30 10 ? * 1/5 *

表示(从后往前看)

[指定年份] 的[ 周一到周五][指定月][不指定日][上午10时][30分][指定秒]

又如:00 00 00 ?* 10,11,12 1#5 2018

表示2018年10、11、12月的第一周的星期五这一天的0时0分0秒去执行任务。

可通过在线生成Cron表达式的工具:

http://cron.qqe2.com/

Quartz高级(企业)功能

由 Alma 创建, 最后一次修改 2017-09-19

Clustering

Clustering目前与JDBC-Jobstore(JobStoreTX或JobStoreCMT)和TerracottaJobStore一起使用。功能包括负载平衡和 job故障转移(如果JobDetail的“请求恢复”标志设置为true)。

####使用JobStoreTX或JobStoreCMT进行聚类通过将“org.quartz.jobStore.isClustered”属性设置为“true”来启用Clustering。Clustering中的每个实例都应该使用相同的quartz.properties文件。这样做的例外是使用相同的属性文件,具有以下允许的异常:不同的线程池大小,以及“org.quartz.scheduler.instanceId”属性的不同值。Clustering中的每个节点必须具有唯一的instanceId,通过将“AUTO”作为此属性的值,可以轻松完成(不需要不同的属性文件)。

不要在单独的机器上运行Clustering,除非它们的时钟使用某种形式的时间同步服务(守护进程)进行同步,而这些时间同步服务(守护进程)运行非常有限(时钟必须在彼此之间)。 如果您不熟悉如何执行此操作,

不要针对任何其他实例运行的相同的一组表来启动非群集实例。您可能会收到严重的数据损坏,一定会遇到不正常的行为。

每次触发只能有一个节点有效。我的意思是,如果job有一个重复的trigger,告诉它每10秒钟发射一次,那么在12:00:00,正好一个节点将运行这个job,在12:00:10,一个节点将运行job等。它不一定是每次相同的节点 - 它或多或少是随机的,哪个节点运行它。负载平衡机制对于繁忙的调度程序(大量的trigger)来说是近乎随机的,但是有利于于non-busy(例如一个或两个trigger)调度程序活动的同一个节点。

####使用TerracottaJobStore进行Clustering简单地将调度程序配置为使用TerracottaJobStore(第9课:JobStores中介绍),并且您的调度程序将全部设置为Clustering。

您可能还需要考虑如何设置Terracotta服务器,特别是打开诸如持久性等功能的配置选项,以及运行一系列用于HA的Terracotta服务器。

TerracottaJobStore的企业版提供了高级的Quartz Where功能,允许将作业的智能定位到适当的Clustering节点。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-07-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 赵KK日常技术记录 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
短信
腾讯云短信(Short Message Service,SMS)可为广大企业级用户提供稳定可靠,安全合规的短信触达服务。用户可快速接入,调用 API / SDK 或者通过控制台即可发送,支持发送验证码、通知类短信和营销短信。国内验证短信秒级触达,99%到达率;国际/港澳台短信覆盖全球200+国家/地区,全球多服务站点,稳定可靠。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档