Diving into the Quartz Task Scheduler

#1 Introduction

Quartz is an open source task scheduling framework. Its core function is to perform tasks based on timed and regular strategies. For example, on New Year's Eve in 2018, it will send red envelopes at 8 o'clock or open the [ Muzidao ] public account, and send red envelopes every 10 minutes. Quartz has three core elements: scheduler (Scheduler), task (Job), trigger (Trigger). Developed entirely in Java, Quartz can be integrated into applications of all sizes. It can host thousands of task schedulers and supports clusters. It supports storing data into a database for persistence and supports the vast majority of databases. It designs tasks and triggers to be loosely coupled, that is, a task can correspond to multiple triggers, so that extremely complex triggering strategies can be easily constructed.

#2. Quartz core API introduction (version 2.3.0)

Scheduler : The main interface for interacting with the scheduler.

Job : The task class that implements this interface component can be executed by the scheduler.

JobDetail : Used to define the instance of the Job.

Trigger : Trigger that defines how a Job is executed by the scheduler.

TriggerBuilder : Trigger builder for creating trigger Trigger instances.

JobBuilder : used to declare a task instance, you can also define details about the task such as task name, group name, etc. This declared instance will be used as an actual execution task.

Scheduler

void start() ;  开始启动Scheduler的线程Trigger。

void shutdown() ;关闭Scheduler的Trigger,并清理与调度程序相关联的所有资源。

boolean isShutdown(); Scheduler是否关闭

Date scheduleJob(JobDetail var1, Trigger var2) ;将该给定添加JobDetail到调度程序,并将给定Trigger与它关联

void triggerJob(JobKey var1, JobDataMap var2) ;触发标识JobDetail(立即执行)。

void pauseJob(JobKey var1) ; 暂停任务

void resumeJob(JobKey var1); 恢复任务

 boolean deleteJob(JobKey var1); 删除任务
 ......

Scheduler is created by SchedulerFactory and terminated with the invocation of shutdown method. Once created it can be used to add, delete or list Jobs and Triggers, or to perform some scheduling related work. After the Scheduler is created, it is in "standby" mode and must first call its start() method to trigger any Jobs. It will only work after it is started via the start() method.

Job

//这个接口由代表要执行的“工作”的类来实现。实例Job必须有一个public 无参数的构造函数。
void execute(JobExecutionContext var1) ;

When a trigger of the job is triggered, the execute() method will be called by a worker thread of the scheduler; the JobExecutionContext object passed to the execute() method holds some information about the job's runtime, the reference of the scheduler that executes the job, and triggers A reference to the trigger of the job, a reference to the JobDetail object, and some other information.

JobDetail

JobDataMap getJobDataMap(); //获取实例成员数据
JobBuilder getJobBuilder();//获得JobBuilder对象

The JobDetail object is created by the client program (your program) when the job is added to the scheduler. It contains various property settings for the job, as well as the JobDataMap for storing job instance state information.

Trigger

//获取触发器唯一标示,同分组key唯一,key= key + 组名称,默认组名称DEFAULT。
TriggerKey getKey(); 
//获取任务唯一标示,
JobKey getJobKey();
//获取实例数据。
JobDataMap getJobDataMap();
//触发优先级,默认是5,同时触发才会比较优先级。
int getPriority();

Trigger is used to trigger the execution of Job. When you are ready to schedule a job, you create an instance of Trigger and then set the scheduling-related properties. Trigger also has an associated JobDataMap, which is used to pass some trigger-related parameters to the Job. Quartz comes with various types of Triggers, the most commonly used are SimpleTrigger and CronTrigger.

CronTrigger CronTrigger is generally more useful than SimpleTrigger if you need a recurring schedule of work based on a calendar-like concept, rather than SimpleTrigger's precisely specified time intervals.

With CronTrigger, you can open my WeChat public account [Muzidao] at 9:00 am every Monday to view related technology sharing articles. You can also reply to me every Friday at 8pm.

Even so, just like SimpleTrigger, CronTrigger has a startTime that specifies when the scheduling takes effect, and an (optional) endTime that specifies when the scheduling should stop.

Cron Expressions Cron-Expressions are used to configure instances of CronTrigger. Cron-Expressions are strings of seven sub-expressions that describe various details of the plan. These subexpressions are separated by spaces and represent: seconds minutes hours day month day year year (optional)
An example of a full cron-expression is the string "0 0 0 1 * ?" - which means "every month 1 Day 00:00:00 Release statistics of last month's data".

Individual subexpressions can contain ranges and/or lists. For example, "MON-FRI", "MON, WED, FRI", or even "MON-WED, SAT" can be used to replace the previous (with "WED") example of the day of the week field.

Wildcards ("characters") can be used to represent "every" possible value for the field. So the characters in the "month" field in the example above are just "monthly". So the "*" in the "day of the week" field obviously means "every day of the week".

All fields have a set of valid values ​​that can be specified. The values ​​should be fairly obvious - such as the digits 0 to 59 for seconds and minutes, and 0 to 23 for hours. The day of the month can be any value from 1-31, but you need to be careful about how many days a given month has! Month can be specified as a value between 0 and 11, or using the strings JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, and DEC. The day of the week can be specified as a value between 1 and 7 (1 = Sunday), or using the strings SUN, MON, TUE, WED, THU, FRI, and SAT.

The "/" character can be used to specify the increment value. For example, if you enter "0/15" in the "minutes" field, it means "every minute for 15 minutes, starting at zero". If you use "3/20" in the "minutes" field, it means "one hour every 20 minutes starting at the third minute" - or in other words, specify "3,23,43" in the minutes field. Note that "/35" doesn't mean the subtle meaning of "every 35 minutes", which is "every 35 minutes, starting at zero", in other words specifying "0,35".

'? ' characters are allowed for date and day of the week fields. It is used to specify "no specific value". This is useful when you need to specify something in one of the two fields and not the other. See the example below (and the CronTrigger JavaDoc) for illustration.

The "L" character is allowed for the month and day of the week fields. The role is short-lived for "Final", but has a different meaning in each of the two realms. For example, the value "L" in the month field means "the last day of the month", which is January 31st on February 28th in non-leap years. If used alone in the day of the week field, it just means "7" or "SAT". However, if you use another value in the day of the week field, it means "the last xxx day of the month", for example "6L" or "FRIL" both mean "the last Friday of the month". You can also specify an offset for the last day of the month, such as "L-3", which means the third-to-last day of the calendar month. When using the "L" option, do not specify a list or range of values ​​as you will get confusing/unexpected results.

"W" is used to specify the day of the week (Monday to Friday) closest to the specified date. For example, if you were to specify "15W" as the value for the day of the month field, it would mean: "the nearest day of the week to the 15th of the month".

"#" is used to specify the nth "XXX" working day of the month. For example, a value of "6#3" or "FRI#3" in the day of the week field means "third Friday of the month".

Example Cron Expression

CronTrigger Example 1 - Create an expression for a trigger, check the [Muzidao] public account "0 0/5 * * *?" every 5 minutes

CronTrigger Example 2 - An expression to create a trigger that fires every 5 minutes 10 seconds after the minute (i.e. 10:00:10AM, 10:05:10AM, etc.). "10 0/5 * * *?"

CronTrigger Example 3 - An expression to create a trigger that occurs every Wednesday and Friday at 10:30, 11:30, 12:30 and 13:30. "0 30 10-13? *WED, FRI"

CronTrigger Example 4 - An expression to create a trigger that fires every half hour between 8AM and 10AM on the 5th and 20th of each month. Note that the trigger is not at 10am, only at 8, 8, 9 and 9:30 "0 0/30 8-9 5,20*?"

Note that some scheduling requirements are too complex to be represented by a single trigger - e.g. "every 5 minutes between 9am and 10am and every 20 minutes between 1pm and 10pm". The solution in this case is to simply create two triggers and register them both to run the same job.

JobStore The JobStore is responsible for keeping track of all "job data" given to the scheduler: jobs, triggers, calendars, etc. Choosing the right JobStore for your Quartz scheduler instance is a very important step. Fortunately, once you understand the differences, the choice should be a pretty straightforward one. You declare which JobStore (and its configuration settings) your scheduler should use in a properties file (or object) you provide to SchedulerFactory in order to generate scheduler instances.

Never use JobStore instances directly in your code. For some reason, many people try to do this. JobStore is used behind the scenes for quartz itself. You have to tell Quartz (through configuration) which JobStore to use, but you can only use the Scheduler interface in your code.

RAMJobStore RAMJobStore is the simplest JobStore and it is also the most performant (CPU time). RAMJobStore gets its name in the obvious way: it keeps all its data in RAM. That's why it's lightning fast and why it's so easy to configure. The downside is that when your application ends (or crashes), all scheduling information is lost - which means RAMJobStore cannot honor the "non-volatile" settings for jobs and triggers. For some applications this is acceptable - even the desired behavior, but for others it can be disastrous.

JDBCJobStore JDBCJobStore is also aptly named - it stores all its data in the database via JDBC. So configuration is a bit more complicated than RAMJobStore, and it's not that fast either. However, the performance degradation is not terribly bad, especially if you build your database table with an index on the primary key. On a fairly modern machine with a decent local area network (between the scheduler and the database), the time to retrieve and update triggers is usually less than 10ms.

JDBCJobStore can be used with almost any database and it has been widely used for Oracle, PostgreSQL, MySQL, MS SQLServer, HSQLDB and DB2. To use JDBCJobStore, you must first create a set of database tables for Quartz to use. You can find the SQL scripts that create the tables in the "docs/dbTables" directory of the Quartz distribution. If your database type doesn't have a script, just look at one of the scripts and modify it in any way necessary. One thing to note is that in these scripts, all tables start with the prefix "QRTZ_" (eg tables "QRTZ_TRIGGERS" and "QRTZ_JOB_DETAIL"). This prefix can actually be anything you want, as long as you tell the JDBCJobStore what the prefix is ​​(in your Quartz properties). Use different prefixes for creating multiple table sets, for multiple scheduler instances,

Once the table is created, there is one more important decision before configuring and starting the JDBCJobStore. You need to determine what type of transactions your application requires. If you don't need to tie scheduling commands (like add and remove triggers) to other transactions, then you can let Quartz use JobStoreTX as the JobStore (which is the most common choice) to manage transactions.

If you need Quartz to work with other transactions (ie in a J2EE application server) then you should use JobStoreCMT - in this case Quartz will let the application server container manage the transactions.

The final piece of the puzzle is setting up a DataSource that the JDBCJobStore can connect to the database. DataSources are defined in your Quartz properties using one of several different methods. One way is to have Quartz create and manage the DataSource itself - by providing all the connection information to the database. Another way is to have Quartz use a DataSource managed by the application server that Quartz runs on - by providing the JNDI name of the DataSource to the JDBCJobStore.

TerracottaJobStore TerracottaJobStore provides a means of scalability and robustness without using a database. This means that your database can be downloaded for free from within Quartz, and all its resources can be saved to the rest of your application.

TerracottaJobStore can run clustered or non-clustered, and in any case provides a storage medium for persistent job data between application restarts, as data is stored in Terracotta servers. It performs much better (about an order of magnitude better) than using the database via JDBCJobStore, but is much slower than RAMJobStore.

#3. Configuration and use of Quartz

//配置SchedulerFactory
    @Bean
    public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource) {
        SchedulerFactoryBean factory = new SchedulerFactoryBean();
        factory.setDataSource(dataSource);
        //quartz参数
        Properties prop = new Properties();
        prop.put("org.quartz.scheduler.instanceName", "AstaliScheduler");
        prop.put("org.quartz.scheduler.instanceId", "AUTO");
        //线程池配置
        prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");
        prop.put("org.quartz.threadPool.threadCount", "20");
        prop.put("org.quartz.threadPool.threadPriority", "5");
        //JobStore配置
       //SQL脚本。http://svn.terracotta.org/svn/quartz/tags/quartz-2.1.3/docs/dbTables/ 
        prop.put("org.quartz.jobStore.class", "org.quartz.impl.jdbcjobstore.JobStoreTX");
        //集群配置
        prop.put("org.quartz.jobStore.isClustered", "true"); //开启集群
        prop.put("org.quartz.jobStore.clusterCheckinInterval", "15000");
        prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "1");
        prop.put("org.quartz.jobStore.misfireThreshold", "12000");
        prop.put("org.quartz.jobStore.tablePrefix", "QRTZ_");  //表头
        factory.setQuartzProperties(prop);
        factory.setSchedulerName("AstaliScheduler");
        //延时启动
        factory.setStartupDelay(30);
        factory.setApplicationContextSchedulerContextKey("applicationContextKey");
        //可选,QuartzScheduler 启动时更新己存在的Job,
        //这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了
        factory.setOverwriteExistingJobs(true);
        //设置自动启动,默认为true
        factory.setAutoStartup(true);
        return factory;
    }
}
//构建Job实例   创建任务
//ScheduleJob extents QuartzJobBean,QuartzJobBean implements Job
 JobDetail jobDetail = JobBuilder.newJob(ScheduleJob.class)
                    .withIdentity(getJobKey(scheduleJobEntity.getJobId()))
                    .build();

//构建cron
  CronScheduleBuilder scheduleBuilde = CronScheduleBuilder
                                     .cronSchedule(scheduleJob.getCronExpression())
                                     .withMisfireHandlingInstructionDoNothing();
 //根据cron,构建一个CronTrigger
 CronTrigger trigger = TriggerBuilder.newTrigger()
                          .withIdentity(getTriggerKey(scheduleJob.getJobId()))
                          .withSchedule(scheduleBuilder)
                          .build();
//调度器的任务类与触发器关联
 scheduler.scheduleJob(jobDetail, trigger);
//立即执行任务
  JobDataMap dataMap = new JobDataMap();
  dataMap.put(ScheduleJobEntity.JOB_PARAM_KEY, new 
Gson().toJson(scheduleJob));
 scheduler.triggerJob(getJobKey(scheduleJob.getJobId()), dataMap);
//暂定任务
scheduler.pauseJob(getJobKey(jobId));
  //恢复任务
 scheduler.resumeJob(getJobKey(jobId));
  //删除任务
 scheduler.deleteJob(getJobKey(jobId));
    /**
     * 获取触发器key
     */
    private static TriggerKey getTriggerKey(Long jobId) {
        return TriggerKey.triggerKey(JOB_NAME + jobId);
    }
     /**
     * 获取jobKey
     */
    private static JobKey getJobKey(Long jobId) {
        return JobKey.jobKey(JOB_NAME + jobId);
    }
    /**
     * 获取表达式触发器
     */
    public static CronTrigger getCronTrigger(Scheduler scheduler, Long jobId) {
        try {
            return (CronTrigger) scheduler.getTrigger(getTriggerKey(jobId));
        } catch (SchedulerException e) {
            throw new Exception("getCronTrigger异常,请检查qrtz开头的表,是否有脏数据", e);
        }
    }
//上篇文章对newSingleThreadExecutor有说明
ExecutorService service = Executors.newSingleThreadExecutor();    
//执行定时任务实现Runnable并重写run方法
ScheduleRunnable task = new ScheduleRunnable(scheduleJob.getBeanName(),               
                              scheduleJob.getMethodName(),     
                              scheduleJob.getParams());
//提交任务并有结果
Future<?> future = service.submit(task); 
future.get();

	@Override
	public void run() {
		try {
   //利用反射执行方法
			ReflectionUtils.makeAccessible(method);
			if(StringUtils.isNotBlank(params)){
				method.invoke(target, params);
			}else{
				method.invoke(target);
			}
		}catch (Exception e) {
			throw new Exception("执行定时任务失败", e);
		}
	}
建立一个触发器,每隔上午8点到下午5点,每隔一分钟一次:
  trigger = newTrigger()
    .withIdentity("trigger3", "group1")
    .withSchedule(cronSchedule("0 0/2 8-17 * * ?"))
    .forJob("myJob", "group1")
    .build();
建立一个触发器,每天在上午10:42开枪:
  trigger = newTrigger()
    .withIdentity("trigger3", "group1")
    .withSchedule(dailyAtHourAndMinute(10, 42))
    .forJob(myJobKey)
    .build();

Front-end renderingsRenderings If you need the source code, please pay attention to the public account [Muzidao]▼Long press the following QR code to follow▼ Official account QR codePlease join me in fighting monsters and upgrading in 2018

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325477383&siteId=291194637