相关概念
Quartz是一个任务调度框架
Job 表示一个工作,要执行的具体内容
JobDetail 表示一个具体的可执行的调度程序,Job 是这个可执行程调度程序所要执行的内容,另外 JobDetail 还包含了这个任务调度的方案和策略
Trigger 代表一个调度参数的配置,什么时候去调。
Scheduler 代表一个调度容器,一个调度容器中可以注册多个 JobDetail 和 Trigger。当 Trigger 与 JobDetail 组合,就可以被 Scheduler 容器调度了。
JobBuilder 定义和创建JobDetail实例的接口;
TriggerBuilder 定义和创建Trigger实例的接口;
使用示例
Demo
1.导依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-quartz</artifactId></dependency>
2.配置以及配置类
application.yml
tuya:quartz:config: config/quartz-dev.properties
quartz-dev.properties
org.quartz.scheduler.instanceName = GlobalSchedulerorg.quartz.threadPool.threadCount = 10org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTXorg.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegateorg.quartz.jobStore.tablePrefix = QRTZ_org.quartz.jobStore.dataSource = globalJobDataSourceorg.quartz.dataSource.globalJobDataSource.driver = com.mysql.cj.jdbc.Driverorg.quartz.dataSource.globalJobDataSource.URL = jdbc:mysql://xxx:xxx/xxx?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&autoReconnect=true&failOverReadOnly=falseorg.quartz.dataSource.globalJobDataSource.user = xxxorg.quartz.dataSource.globalJobDataSource.password = xxxorg.quartz.dataSource.globalJobDataSource.maxConnections = 5
AutowireBeanJobFactory(一般放启动类同级)
*** QuartzConfiguration** @author Shenzhen Greatonce Co Ltd* @author ginta* @version 2019/4/29*/@Configurationpublic class QuartzConfiguration {@Beanpublic AutowireBeanJobFactory autowireBeanJobFactory() {return new AutowireBeanJobFactory();}/*** attention:* Details:定义quartz调度工厂*/@Bean(name = "globalScheduler")public SchedulerFactoryBean schedulerFactory() {SchedulerFactoryBean bean = new SchedulerFactoryBean();// Quartz中的job自动注入spring容器托管的对象bean.setJobFactory(autowireBeanJobFactory());// 任意一个已定义的Job会覆盖现在的Jobbean.setOverwriteExistingJobs(true);// 延时启动,应用启动1秒后,定时器才开始启动bean.setStartupDelay(1);return bean;}}
DruidConnectionProvider
package top.xinzhang0618.buge.core.quartz;import com.alibaba.druid.pool.DruidDataSource;import lombok.Getter;import lombok.Setter;import org.quartz.utils.ConnectionProvider;import java.sql.Connection;import java.sql.SQLException;/*** @author xinzhang* @date 2020/8/20 15:05*/@Getter@Setterpublic class DruidConnectionProvider implements ConnectionProvider {private String driver;private String url;private String user;private String password;private String validationQuery;private int maxConnection;private int idleConnectionValidationSeconds;private boolean validateOnCheckout;private DruidDataSource datasource;@Overridepublic Connection getConnection() throws SQLException {return datasource.getConnection();}@Overridepublic void shutdown() throws SQLException {if (this.datasource != null) {datasource.close();}}@Overridepublic void initialize() throws SQLException {if (this.url == null) {throw new SQLException("DBPool could not be created: DB URL cannot be null");}if (this.driver == null) {throw new SQLException("DBPool driver could not be created: DB driver class name cannot be null!");}if (this.maxConnection < 0) {throw new SQLException("DBPool maxConnectins could not be created: Max connections must be greater than zero!");}datasource = new DruidDataSource();datasource.setDriverClassName(this.driver);datasource.setUrl(this.url);datasource.setUsername(this.user);datasource.setPassword(this.password);datasource.setMaxActive(this.maxConnection);if (this.validationQuery != null) {datasource.setValidationQuery(this.validationQuery);if (!this.validateOnCheckout) {datasource.setTestOnReturn(true);} else {datasource.setTestOnBorrow(true);}datasource.setValidationQueryTimeout(this.idleConnectionValidationSeconds);}}}
测试demo
@Autowiredprivate Scheduler globalScheduler;@PostMapping("/addJob/{productPublishId}")public void addJob(@PathVariable("productPublishId") Long productPublishId) throws SchedulerException {PublishProduct publishProduct = productPublishService.getByKey(productPublishId);JobKey jobKey = JobKey.jobKey("PUBLISH_PRODUCT_" + publishProduct.getPublishProductId());JobDetail jobDetail = JobBuilder.newJob(PublishJob.class).withIdentity(jobKey).build();jobDetail.getJobDataMap().put("PUBLISH_ID", productPublishId);jobDetail.getJobDataMap().put("PUBLISH_TITLE", publishProduct.getPublishTitle());TriggerKey triggerKey = TriggerKey.triggerKey("PUBLISH_PRODUCT_" + publishProduct.getPublishProductId());CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(CronScheduleBuilder.cronSchedule("0 58 14 29 4 ? 2019")).startNow().build();globalScheduler.scheduleJob(jobDetail, trigger);}public static class PublishJob implements Job {@Overridepublic void execute(JobExecutionContext context) throws JobExecutionException {long publishId = context.getJobDetail().getJobDataMap().getLong("PUBLISH_ID");String publishTitle = context.getJobDetail().getJobDataMap().getString("PUBLISH_TITLE");System.out.println("PublishJob------------>" + publishTitle + " " + publishId);}}
定时投放任务
/*** 定时投放*/private void timedPublish(PublishProduct publishProduct) {JobKey jobKey = JobKey.jobKey("PUBLISH_PRODUCT_" + publishProduct.getPublishProductId());JobDetail jobDetail = JobBuilder.newJob(PublishJob.class).withIdentity(jobKey).build();jobDetail.getJobDataMap().put("PUBLISH_PRODUCT_ID", publishProduct.getPublishProductId());jobDetail.getJobDataMap().put("PUBLISH_TITLE", publishProduct.getPublishTitle());jobDetail.getJobDataMap().put("TENANT_ID", BizContext.getTenantId());jobDetail.getJobDataMap().put("OSS_SETTING", BizContext.getOssSetting());TriggerKey triggerKey = TriggerKey.triggerKey("PUBLISH_PRODUCT_" + publishProduct.getPublishProductId());LocalDateTime planTime = publishProduct.getPlanTime();DateTimeFormatter formatter = DateTimeFormatter.ofPattern("ss mm HH dd MM ? yyyy");String formatTimeStr = planTime.format(formatter);CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(CronScheduleBuilder.cronSchedule(formatTimeStr)).startNow().build();try {globalScheduler.scheduleJob(jobDetail, trigger);publishProduct.setPublishStatus(PublishStatus.ON_PUBLISH);update(publishProduct);LOGGER.log(publishProduct.getPublishProductId(), ActionConstants.MODIFY);} catch (SchedulerException e) {publishLOGGER.error("定时投放失败", e);throw new BizException("定时投放失败", e);}}
/*** 定时投放任务*/public class PublishJob implements Job {private static final Logger LOGGER = TuyaLoggerFactory.getLogger(PublishJob.class);@Autowiredprivate PublishProductService publishProductService;@Overridepublic void execute(JobExecutionContext context) throws JobExecutionException {long publishProductId = context.getJobDetail().getJobDataMap().getLong("PUBLISH_PRODUCT_ID");String publishTitle = context.getJobDetail().getJobDataMap().getString("PUBLISH_TITLE");BizContext.setTenantId(context.getJobDetail().getJobDataMap().getLong("TENANT_ID"));BizContext.setOssSetting((TenantOssSetting) context.getJobDetail().getJobDataMap().get("OSS_SETTING"));PublishProduct publishProduct = publishProductService.getByKey(publishProductId);publishProductService.goPublish(publishProduct);LOGGER.info("定时投放, publishProductId:{},publishTitle:{}", publishProductId, publishTitle);}}
定时计费
JobConfiguration
package com.greatonce.tuya.biz.impl.job;import com.greatonce.core.supports.quartz.QuartzTool;import com.greatonce.tuya.util.logging.TuyaLoggerFactory;import javax.annotation.PostConstruct;import org.quartz.CronTrigger;import org.quartz.JobDetail;import org.quartz.JobKey;import org.quartz.Scheduler;import org.quartz.SchedulerException;import org.slf4j.Logger;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Configuration;/*** @author Shenzhen Greatonce Co Ltd* @author xinzhang* @version 2019/7/15*/@Configurationpublic class JobConfiguration {private static final Logger LOGGER = TuyaLoggerFactory.getLogger(JobConfiguration.class);private static final String PRODUCT_IMAGE_DAILY_ACCOUNT = "PRODUCT_IMAGE_DAILY_ACCOUNT";@Autowiredprivate Scheduler globalScheduler;@Value("${tuya.quarz.productImageDailyAccountJobCorn:0 30 23 * * ?}")private String productImageDailyAccountJobCorn;@PostConstructpublic void init() {executeProductImageDailyAccountJob();}private void executeProductImageDailyAccountJob() {try {JobKey jobKey = JobKey.jobKey(PRODUCT_IMAGE_DAILY_ACCOUNT);if (globalScheduler.checkExists(jobKey)) {globalScheduler.deleteJob(jobKey);}JobDetail jobDetail = QuartzTool.buildJobDetail(PRODUCT_IMAGE_DAILY_ACCOUNT, ProductImageDailyAccountJob.class);CronTrigger trigger = QuartzTool.buildCronTrigger(PRODUCT_IMAGE_DAILY_ACCOUNT, productImageDailyAccountJobCorn);globalScheduler.scheduleJob(jobDetail, trigger);} catch (SchedulerException e) {LOGGER.error("定时计费异常, 异常信息: {}", e.getMessage());}}}
ProductImageDailyAccountJob
package com.greatonce.tuya.biz.impl.job;import com.greatonce.core.util.Assert;import com.greatonce.core.util.JsonUtil;import com.greatonce.core.util.StringUtil;import com.greatonce.tuya.biz.ProductBalanceService;import com.greatonce.tuya.biz.ProductBillService;import com.greatonce.tuya.biz.StoreService;import com.greatonce.tuya.biz.TenantService;import com.greatonce.tuya.biz.TenantSettingService;import com.greatonce.tuya.schema.domain.ProductBalance;import com.greatonce.tuya.schema.domain.ProductBill;import com.greatonce.tuya.schema.domain.Tenant;import com.greatonce.tuya.schema.domain.TenantSetting;import com.greatonce.tuya.schema.enums.ProductType;import com.greatonce.tuya.schema.enums.TenantSettingType;import com.greatonce.tuya.schema.query.ProductBalanceQuery;import com.greatonce.tuya.schema.query.TenantQuery;import com.greatonce.tuya.util.BizContext;import com.greatonce.tuya.util.logging.TuyaLoggerFactory;import com.greatonce.tuya.util.velocity.VelocityUtil;import java.time.LocalDate;import java.util.HashMap;import java.util.List;import java.util.Map;import java.util.Set;import java.util.stream.Collectors;import javax.mail.MessagingException;import javax.mail.internet.MimeMessage;import org.quartz.Job;import org.quartz.JobExecutionContext;import org.quartz.JobExecutionException;import org.slf4j.Logger;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.mail.javamail.JavaMailSender;import org.springframework.mail.javamail.MimeMessageHelper;/*** 商品主图日结任务** @author Shenzhen Greatonce Co Ltd* @author xinzhang* @version 2019/7/15*/public class ProductImageDailyAccountJob implements Job {private static final Logger LOGGER = TuyaLoggerFactory.getLogger(ProductImageDailyAccountJob.class);private static final String GENERATE_SUCCESS_TIMES = "GENERATE_SUCCESS_TIMES";private static final String SYSTEM = "SYSTEM";private static final String PRODUCT_BALANCE_NOTIFY_MAIL_TEMPLATE = "productBalanceNotifyMail.vm";@Autowiredprivate RedisTemplate<String, Object> redisTemplate;@Autowiredprivate TenantService tenantService;@Autowiredprivate TenantSettingService tenantSettingService;@Autowiredprivate StoreService storeService;@Autowiredprivate ProductBillService productBillService;@Autowiredprivate ProductBalanceService productBalanceService;@Autowiredprivate JavaMailSender javaMailSender;@Value("${spring.mail.username}")private String sender;@Overridepublic void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {List<Tenant> tenantList = tenantService.list(new TenantQuery());for (Tenant tenant : tenantList) {BizContext.setTenantId(tenant.getTenantId());int totalTimes = 0;Map<Long, Integer> entries = redisTemplate.<Long, Integer>opsForHash().entries(tenant.getTenantId() + ":" + ProductType.PRODUCT_IMAGE + ":" + GENERATE_SUCCESS_TIMES);if (!Assert.isEmpty(entries)) {for (Long storeId : entries.keySet()) {String storeName = storeService.getByKey(storeId).getStoreName();ProductBill productBill = new ProductBill();productBill.setTenantId(tenant.getTenantId());productBill.setCreator(SYSTEM);productBill.setConsumer(storeName);productBill.setTimes(entries.get(storeId));totalTimes += entries.get(storeId);productBill.setProductType(ProductType.PRODUCT_IMAGE);productBill.setAccountPeriod(LocalDate.now());productBillService.create(productBill);redisTemplate.<Long, Integer>opsForHash().delete(tenant.getTenantId() + ":" + ProductType.PRODUCT_IMAGE + ":" + GENERATE_SUCCESS_TIMES, storeId);}productBalanceService.reduceProductBalanceRemainTimesAndFreezeTimes(tenant.getTenantId(), ProductType.PRODUCT_IMAGE, totalTimes);}}List<ProductBalance> productBalanceList = productBalanceService.list(new ProductBalanceQuery());Set<Long> tenantIds = productBalanceList.stream().filter(productBalance -> {TenantSetting eg = new TenantSetting();eg.setTenantId(productBalance.getTenantId());eg.setTenantSettingType(TenantSettingType.APPLICATION);String tenantSettingContent = tenantSettingService.getByExample(eg).getTenantSettingContent();Integer warningValue = JsonUtil.toJSONObject(tenantSettingContent).getInteger("warningValue");return productBalance.getRemainTimes() <= warningValue;}).map(ProductBalance::getTenantId).collect(Collectors.toSet());sendRemainTimesNotifyEmail(tenantIds);}/*** 发送剩余次数提醒邮件** @param tenantIds 租户id列表*/private void sendRemainTimesNotifyEmail(Set<Long> tenantIds) {try {for (Long tenantId : tenantIds) {TenantSetting eg = new TenantSetting();eg.setTenantSettingType(TenantSettingType.APPLICATION);eg.setTenantId(tenantId);TenantSetting tenantSetting = tenantSettingService.getByExample(eg);String email = JsonUtil.toJSONObject(tenantSetting.getTenantSettingContent()).getString("email");List<String> receivers = StringUtil.words(email);String[] toAddress = receivers.toArray(new String[receivers.size()]);String subject = "图鸦提醒邮件,产品余额不足,请及时充值!";String tenantName = tenantService.getByKey(tenantId).getTenantName();HashMap<String, Object> params = new HashMap<>(2);params.put("tenantName", tenantName);ProductBalanceQuery query = new ProductBalanceQuery();query.setTenantId(tenantId);List<ProductBalance> productBalanceList = productBalanceService.list(query);params.put("productBalanceList", productBalanceList);String text = VelocityUtil.generateStringByTemplate(PRODUCT_BALANCE_NOTIFY_MAIL_TEMPLATE, params);MimeMessage message = javaMailSender.createMimeMessage();MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");helper.setFrom(sender);helper.setTo(toAddress);helper.setSubject(subject);helper.setText(text, true);javaMailSender.send(message);}} catch (MessagingException e) {LOGGER.error("发送图片生成剩余次数提醒邮件失败, 堆栈信息: {}", e);}}}
QuarzTool
核心corn包的工具类参考
QuartzTool
public abstract class QuartzTool {public QuartzTool() {}public static Scheduler scheduler(String name, JobFactory jobFactory) throws Exception {SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();schedulerFactoryBean.setSchedulerName(name);schedulerFactoryBean.setJobFactory(jobFactory);schedulerFactoryBean.setAutoStartup(true);schedulerFactoryBean.afterPropertiesSet();schedulerFactoryBean.start();return schedulerFactoryBean.getObject();}public static JobDetail jobDetail(String name, Class<? extends Job> clazz) {JobDetailFactoryBean jobDetailFactoryBean = new JobDetailFactoryBean();jobDetailFactoryBean.setName(name);jobDetailFactoryBean.setJobClass(clazz);jobDetailFactoryBean.afterPropertiesSet();return jobDetailFactoryBean.getObject();}public static SimpleTrigger simpleTrigger(String name, int interval, JobDetail jobDetail) {SimpleTriggerFactoryBean simpleTriggerFactoryBean = new SimpleTriggerFactoryBean();simpleTriggerFactoryBean.setName(name);simpleTriggerFactoryBean.setJobDetail(jobDetail);simpleTriggerFactoryBean.setRepeatInterval((long)(interval * 1000));simpleTriggerFactoryBean.afterPropertiesSet();return simpleTriggerFactoryBean.getObject();}public static CronTrigger cronTrigger(String name, String cron, JobDetail jobDetail) throws ParseException {CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean();cronTriggerFactoryBean.setName(name);cronTriggerFactoryBean.setJobDetail(jobDetail);cronTriggerFactoryBean.setCronExpression(cron);cronTriggerFactoryBean.afterPropertiesSet();return cronTriggerFactoryBean.getObject();}public static String buildCron(LocalDateTime time) {DateTimeFormatter formatter = DateTimeFormatter.ofPattern("ss mm HH dd MM ? yyyy");return time.format(formatter);}public static JobDetail buildJobDetail(String jobKey, Class<? extends Job> clazz) {return buildJobDetail(jobKey, clazz, (Map)null);}public static JobDetail buildJobDetail(String jobKey, Class<? extends Job> clazz, Map<String, Object> jobData) {JobBuilder jobBuilder = JobBuilder.newJob(clazz).withIdentity(jobKey);if (jobData != null) {jobBuilder.usingJobData(new JobDataMap(jobData));}return jobBuilder.build();}public static CronTrigger buildCronTrigger(String triggerKey, String cron) {return (CronTrigger)TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(CronScheduleBuilder.cronSchedule(cron)).startNow().build();}public static SimpleTrigger buildSimpleTrigger(String triggerKey, int seconds) {return (SimpleTrigger)TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(seconds).repeatForever()).startNow().build();}public static <T> T getJobData(JobExecutionContext jobExecutionContext, String key) {return jobExecutionContext.getJobDetail().getJobDataMap() != null && jobExecutionContext.getJobDetail().getJobDataMap().containsKey(key) ? jobExecutionContext.getJobDetail().getJobDataMap().get(key) : null;}}
AutowireBeanJobFactory
public class AutowireBeanJobFactory extends SpringBeanJobFactory implements ApplicationContextAware {private transient AutowireCapableBeanFactory beanFactory;public AutowireBeanJobFactory() {}public void setApplicationContext(ApplicationContext context) {this.beanFactory = context.getAutowireCapableBeanFactory();}protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {Object job = super.createJobInstance(bundle);this.beanFactory.autowireBean(job);return job;}}
工具类使用参考
String name = giftStrategy.getGiftStrategyId() + EXECUTE + execute.getGiftStrategyExecuteId();JobDetail jobDetail = QuartzTool.jobDetail(name, StaticStrategyExecuteJob.class);jobDetail.getJobDataMap().put(GIFT_STRATEGY_ID, giftStrategy.getGiftStrategyId());jobDetail.getJobDataMap().put(GIFT_STRATEGY_EXECUTE_ID, execute.getGiftStrategyExecuteId());SimpleTrigger simpleTrigger = QuartzTool.simpleTrigger(name, 0, jobDetail);try {globalScheduler.scheduleJob(jobDetail, simpleTrigger);} catch (SchedulerException e) {throw new OmsException("静态策略执行定时任务添加失败, giftStrategyId: {0}, GiftStrategyExecuteId: {1}",giftStrategy.getGiftStrategyId(), execute.getGiftStrategyExecuteId());}
quarz错过触发时机的处理
参考: https://yq.aliyun.com/articles/114262
触发器超时的情况
1.系统重启错过定时任务
2.Trigger被暂停时, 有任务被错过
3.线程池中的所有线程被占用, 导致misfire
4.有状态任务在下次触发时间到达时, 上次执行还没结束
#判定job为misfire的阈值,这里设置为4Sorg.quartz.jobStore.misfireThreshold = 4000
调度器怎么处理超时
- timeout < misfireThreshold
超时的触发器(超时时间小于misfireThreshold)在获取到运行线程后,将会立即运行前面错过的作业job,然后按照前面制定的周期性任务正常运行。
2. timeout >= misfireThreshold
调度器引擎为简单触发器SimpleTrigger和表达式CronTrigger提供了多种处理策略,我们可以在定义触发器时指定需要的策略。
2.1 对于SimpleTrigger的处理策略
- MISFIRE_INSTRUCTION_FIRE_NOW : 调度引擎在MisFire的情况下,将任务(JOB)马上执行一次。需要注意的是 这个指令通常被用做只执行一次的Triggers,也就是没有重复的情况(non-repeating),如果这个Triggers的被安排的执行次数大于0。那么这个执行与 MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT 相同。
- MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT: 调度引擎重新调度该任务,repeat count 保持不变,按照原有制定的执行方案执行repeat count次,但是,如果当前时间,已经晚于 end-time,那么这个触发器将不会再被触发。举个例子:比如一个触发器设置的时间是 10:00 执行时间间隔10秒 重复10次。那么当10:07秒的时候调度引擎可以执行这个触发器的任务,然后按照原有制定的时间间隔执行10次。但是如果触发器设置的执行时间是10:00,结束时间为10:10,由于种种原因导致该触发器在10:11分才能被调度引擎触发,这时,触发器将不会被触发了。
- MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT: 这个策略跟上面的 MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT 策略类似,唯一的区别就是调度器触发触发器的时间不是“现在” 而是下一个 scheduled time。
- MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT: 这个策略跟上面的策略 MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT 比较类似,调度引擎重新调度该任务,repeat count 是剩余应该执行的次数,也就是说本来这个任务应该执行10次,但是已经错过了3次,那么这个任务就还会执行7次。
- MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT: 这个策略跟上面的 MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT 策略类似,区别就是repeat count 是剩余应该执行的次数而不是全部的执行次数。比如一个任务应该在2:00执行,repeat count=5,时间间隔5秒, 但是在2:07才获得执行的机会,那任务不会立即执行,而是按照机会在2点10秒执行。
- MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY: 这个策略跟上面的 MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT 策略类似,但这个策略是忽略所有的超时状态,快速执行之前错过的次数,然后再按照之前制定的周期触发触发器。举个例子,一个SimpleTrigger 每个15秒钟触发, 但是超时了5分钟才获得执行的机会,那么这个触发器会被快速连续调用20次, 追上前面落下的执行次数。
2.2 对于CronTrigger的处理策略
- MISFIRE_INSTRUCTION_FIRE_ONCE_NOW: 指示触发器超时后会被立即安排执行。
- MISFIRE_INSTRUCTION_DO_NOTHING: 这个策略与策略 MISFIRE_INSTRUCTION_FIRE_ONCE_NOW 正好相反,它不会被立即触发,而是获取下一个被触发的时间,并且如果下一个被触发的时间超出了end-time 那么触发器就不会被执行。
定时任务调度

注意理解:
间隔秒数==>下多长时间
延迟分钟==>下什么时候的, 当前时间-延迟时间
任务间隔==>多久下一次
package com.greatonce.oms.job.executor;import com.greatonce.oms.biz.base.StoreDownloadConfigService;import com.greatonce.oms.biz.base.StoreService;import com.greatonce.oms.domain.base.Store;import com.greatonce.oms.domain.base.StoreDownloadConfig;import com.greatonce.oms.job.executor.download.StoreDownloadJob;import java.time.LocalDateTime;import org.quartz.Job;import org.quartz.JobExecutionContext;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;/*** 下载抽象类.** @author ginta* @author Shenzhen Greatonce Co Ltd* @version 2018-07-22*/public abstract class AbstractDownloader implements Job {private final Logger LOGGER = LoggerFactory.getLogger(AbstractDownloader.class);@Autowiredprivate StoreService storeService;@Autowiredprivate StoreDownloadConfigService storeDownloadConfigService;/*** 日志*/protected abstract Logger getLogger();/*** 执行下载** 如果未到延迟时间不下载* 如果开始时间+间隔秒数>当前时间则结束时间为当前时间,否则为开始时间+间隔秒数** @param config 下载配置* @param store 店铺* @param endTime 计算好的结束时间*/protected abstract void doDownload(StoreDownloadConfig config, Store store, LocalDateTime endTime);public void download(StoreDownloadConfig config) {Store store = storeService.getByKey(config.getStoreId());if (store == null || !store.isEnable()) {getLogger().debug("店铺不存在或已禁用:{}", config.getStoreName());return;}LocalDateTime now = LocalDateTime.now();LocalDateTime endTime = config.getBeginTime().plusSeconds(config.getIntervalSeconds());endTime = endTime.isAfter(now) ? now : endTime;LocalDateTime deadline = now.minusMinutes(config.getDelayMinutes());if (endTime.isAfter(deadline)) {if (getLogger().isDebugEnabled()) {getLogger().debug("未到延迟时间:{},{},{}", config.getStoreName(), config.getDownloadType().caption(),config.getBeginTime());}return;}doDownload(config, store, endTime);config.setBeginTime(endTime);storeDownloadConfigService.modify(config);}@Overridepublic void execute(JobExecutionContext jobExecutionContext) {Long configId = jobExecutionContext.getJobDetail().getJobDataMap().getLong(StoreDownloadJob.DOWNLOAD_CONFIG_KEY);StoreDownloadConfig config = storeDownloadConfigService.getByKey(configId);try {download(config);} catch (Exception e) {LOGGER.info(config.getStoreName() + ":" + config.getDownloadType().caption() + "下载异常,堆栈信息:", e);}}}
举例:
问: 有品发票的下载配置初始化的时候给多少合适==>这种不需要太及时的就可以跑的频次低一点, 可以5分钟跑一次,拉10分钟的数据
答: 可以设置延迟分钟10, 间隔秒数1060和任务间隔560
