相关概念
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 = GlobalScheduler
org.quartz.threadPool.threadCount = 10
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.tablePrefix = QRTZ_
org.quartz.jobStore.dataSource = globalJobDataSource
org.quartz.dataSource.globalJobDataSource.driver = com.mysql.cj.jdbc.Driver
org.quartz.dataSource.globalJobDataSource.URL = jdbc:mysql://xxx:xxx/xxx?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&autoReconnect=true&failOverReadOnly=false
org.quartz.dataSource.globalJobDataSource.user = xxx
org.quartz.dataSource.globalJobDataSource.password = xxx
org.quartz.dataSource.globalJobDataSource.maxConnections = 5
AutowireBeanJobFactory(一般放启动类同级)
**
* QuartzConfiguration
*
* @author Shenzhen Greatonce Co Ltd
* @author ginta
* @version 2019/4/29
*/
@Configuration
public class QuartzConfiguration {
@Bean
public 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会覆盖现在的Job
bean.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
@Setter
public 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;
@Override
public Connection getConnection() throws SQLException {
return datasource.getConnection();
}
@Override
public void shutdown() throws SQLException {
if (this.datasource != null) {
datasource.close();
}
}
@Override
public 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
@Autowired
private 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 {
@Override
public 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);
@Autowired
private PublishProductService publishProductService;
@Override
public 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
*/
@Configuration
public class JobConfiguration {
private static final Logger LOGGER = TuyaLoggerFactory.getLogger(JobConfiguration.class);
private static final String PRODUCT_IMAGE_DAILY_ACCOUNT = "PRODUCT_IMAGE_DAILY_ACCOUNT";
@Autowired
private Scheduler globalScheduler;
@Value("${tuya.quarz.productImageDailyAccountJobCorn:0 30 23 * * ?}")
private String productImageDailyAccountJobCorn;
@PostConstruct
public 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";
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private TenantService tenantService;
@Autowired
private TenantSettingService tenantSettingService;
@Autowired
private StoreService storeService;
@Autowired
private ProductBillService productBillService;
@Autowired
private ProductBalanceService productBalanceService;
@Autowired
private JavaMailSender javaMailSender;
@Value("${spring.mail.username}")
private String sender;
@Override
public 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的阈值,这里设置为4S
org.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);
@Autowired
private StoreService storeService;
@Autowired
private 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);
}
@Override
public 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