相关概念

Quartz是一个任务调度框架
Job 表示一个工作,要执行的具体内容
JobDetail 表示一个具体的可执行的调度程序,Job 是这个可执行程调度程序所要执行的内容,另外 JobDetail 还包含了这个任务调度的方案和策略
Trigger 代表一个调度参数的配置,什么时候去调。
Scheduler 代表一个调度容器,一个调度容器中可以注册多个 JobDetail 和 Trigger。当 Trigger 与 JobDetail 组合,就可以被 Scheduler 容器调度了。
JobBuilder 定义和创建JobDetail实例的接口;
TriggerBuilder 定义和创建Trigger实例的接口;

使用示例

Demo

1.导依赖

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-quartz</artifactId>
  4. </dependency>

2.配置以及配置类
application.yml

  1. tuya:
  2. quartz:
  3. config: config/quartz-dev.properties

quartz-dev.properties

  1. org.quartz.scheduler.instanceName = GlobalScheduler
  2. org.quartz.threadPool.threadCount = 10
  3. org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
  4. org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
  5. org.quartz.jobStore.tablePrefix = QRTZ_
  6. org.quartz.jobStore.dataSource = globalJobDataSource
  7. org.quartz.dataSource.globalJobDataSource.driver = com.mysql.cj.jdbc.Driver
  8. org.quartz.dataSource.globalJobDataSource.URL = jdbc:mysql://xxx:xxx/xxx?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&autoReconnect=true&failOverReadOnly=false
  9. org.quartz.dataSource.globalJobDataSource.user = xxx
  10. org.quartz.dataSource.globalJobDataSource.password = xxx
  11. org.quartz.dataSource.globalJobDataSource.maxConnections = 5

AutowireBeanJobFactory(一般放启动类同级)

  1. **
  2. * QuartzConfiguration
  3. *
  4. * @author Shenzhen Greatonce Co Ltd
  5. * @author ginta
  6. * @version 2019/4/29
  7. */
  8. @Configuration
  9. public class QuartzConfiguration {
  10. @Bean
  11. public AutowireBeanJobFactory autowireBeanJobFactory() {
  12. return new AutowireBeanJobFactory();
  13. }
  14. /**
  15. * attention:
  16. * Details:定义quartz调度工厂
  17. */
  18. @Bean(name = "globalScheduler")
  19. public SchedulerFactoryBean schedulerFactory() {
  20. SchedulerFactoryBean bean = new SchedulerFactoryBean();
  21. // Quartz中的job自动注入spring容器托管的对象
  22. bean.setJobFactory(autowireBeanJobFactory());
  23. // 任意一个已定义的Job会覆盖现在的Job
  24. bean.setOverwriteExistingJobs(true);
  25. // 延时启动,应用启动1秒后,定时器才开始启动
  26. bean.setStartupDelay(1);
  27. return bean;
  28. }
  29. }

DruidConnectionProvider

  1. package top.xinzhang0618.buge.core.quartz;
  2. import com.alibaba.druid.pool.DruidDataSource;
  3. import lombok.Getter;
  4. import lombok.Setter;
  5. import org.quartz.utils.ConnectionProvider;
  6. import java.sql.Connection;
  7. import java.sql.SQLException;
  8. /**
  9. * @author xinzhang
  10. * @date 2020/8/20 15:05
  11. */
  12. @Getter
  13. @Setter
  14. public class DruidConnectionProvider implements ConnectionProvider {
  15. private String driver;
  16. private String url;
  17. private String user;
  18. private String password;
  19. private String validationQuery;
  20. private int maxConnection;
  21. private int idleConnectionValidationSeconds;
  22. private boolean validateOnCheckout;
  23. private DruidDataSource datasource;
  24. @Override
  25. public Connection getConnection() throws SQLException {
  26. return datasource.getConnection();
  27. }
  28. @Override
  29. public void shutdown() throws SQLException {
  30. if (this.datasource != null) {
  31. datasource.close();
  32. }
  33. }
  34. @Override
  35. public void initialize() throws SQLException {
  36. if (this.url == null) {
  37. throw new SQLException("DBPool could not be created: DB URL cannot be null");
  38. }
  39. if (this.driver == null) {
  40. throw new SQLException("DBPool driver could not be created: DB driver class name cannot be null!");
  41. }
  42. if (this.maxConnection < 0) {
  43. throw new SQLException("DBPool maxConnectins could not be created: Max connections must be greater than zero!");
  44. }
  45. datasource = new DruidDataSource();
  46. datasource.setDriverClassName(this.driver);
  47. datasource.setUrl(this.url);
  48. datasource.setUsername(this.user);
  49. datasource.setPassword(this.password);
  50. datasource.setMaxActive(this.maxConnection);
  51. if (this.validationQuery != null) {
  52. datasource.setValidationQuery(this.validationQuery);
  53. if (!this.validateOnCheckout) {
  54. datasource.setTestOnReturn(true);
  55. } else {
  56. datasource.setTestOnBorrow(true);
  57. }
  58. datasource.setValidationQueryTimeout(this.idleConnectionValidationSeconds);
  59. }
  60. }
  61. }

测试demo

  1. @Autowired
  2. private Scheduler globalScheduler;
  3. @PostMapping("/addJob/{productPublishId}")
  4. public void addJob(@PathVariable("productPublishId") Long productPublishId) throws SchedulerException {
  5. PublishProduct publishProduct = productPublishService.getByKey(productPublishId);
  6. JobKey jobKey = JobKey.jobKey("PUBLISH_PRODUCT_" + publishProduct.getPublishProductId());
  7. JobDetail jobDetail = JobBuilder.newJob(PublishJob.class)
  8. .withIdentity(jobKey).build();
  9. jobDetail.getJobDataMap().put("PUBLISH_ID", productPublishId);
  10. jobDetail.getJobDataMap().put("PUBLISH_TITLE", publishProduct.getPublishTitle());
  11. TriggerKey triggerKey = TriggerKey.triggerKey("PUBLISH_PRODUCT_" + publishProduct.getPublishProductId());
  12. CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(CronScheduleBuilder
  13. .cronSchedule("0 58 14 29 4 ? 2019"))
  14. .startNow()
  15. .build();
  16. globalScheduler.scheduleJob(jobDetail, trigger);
  17. }
  18. public static class PublishJob implements Job {
  19. @Override
  20. public void execute(JobExecutionContext context) throws JobExecutionException {
  21. long publishId = context.getJobDetail().getJobDataMap().getLong("PUBLISH_ID");
  22. String publishTitle = context.getJobDetail().getJobDataMap().getString("PUBLISH_TITLE");
  23. System.out.println("PublishJob------------>" + publishTitle + " " + publishId);
  24. }
  25. }

定时投放任务

  1. /**
  2. * 定时投放
  3. */
  4. private void timedPublish(PublishProduct publishProduct) {
  5. JobKey jobKey = JobKey.jobKey("PUBLISH_PRODUCT_" + publishProduct.getPublishProductId());
  6. JobDetail jobDetail = JobBuilder.newJob(PublishJob.class)
  7. .withIdentity(jobKey).build();
  8. jobDetail.getJobDataMap().put("PUBLISH_PRODUCT_ID", publishProduct.getPublishProductId());
  9. jobDetail.getJobDataMap().put("PUBLISH_TITLE", publishProduct.getPublishTitle());
  10. jobDetail.getJobDataMap().put("TENANT_ID", BizContext.getTenantId());
  11. jobDetail.getJobDataMap().put("OSS_SETTING", BizContext.getOssSetting());
  12. TriggerKey triggerKey = TriggerKey.triggerKey("PUBLISH_PRODUCT_" + publishProduct.getPublishProductId());
  13. LocalDateTime planTime = publishProduct.getPlanTime();
  14. DateTimeFormatter formatter = DateTimeFormatter.ofPattern("ss mm HH dd MM ? yyyy");
  15. String formatTimeStr = planTime.format(formatter);
  16. CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(CronScheduleBuilder
  17. .cronSchedule(formatTimeStr))
  18. .startNow()
  19. .build();
  20. try {
  21. globalScheduler.scheduleJob(jobDetail, trigger);
  22. publishProduct.setPublishStatus(PublishStatus.ON_PUBLISH);
  23. update(publishProduct);
  24. LOGGER.log(publishProduct.getPublishProductId(), ActionConstants.MODIFY);
  25. } catch (SchedulerException e) {
  26. publishLOGGER.error("定时投放失败", e);
  27. throw new BizException("定时投放失败", e);
  28. }
  29. }
  1. /**
  2. * 定时投放任务
  3. */
  4. public class PublishJob implements Job {
  5. private static final Logger LOGGER = TuyaLoggerFactory.getLogger(PublishJob.class);
  6. @Autowired
  7. private PublishProductService publishProductService;
  8. @Override
  9. public void execute(JobExecutionContext context) throws JobExecutionException {
  10. long publishProductId = context.getJobDetail().getJobDataMap().getLong("PUBLISH_PRODUCT_ID");
  11. String publishTitle = context.getJobDetail().getJobDataMap().getString("PUBLISH_TITLE");
  12. BizContext.setTenantId(context.getJobDetail().getJobDataMap().getLong("TENANT_ID"));
  13. BizContext.setOssSetting((TenantOssSetting) context.getJobDetail().getJobDataMap().get("OSS_SETTING"));
  14. PublishProduct publishProduct = publishProductService.getByKey(publishProductId);
  15. publishProductService.goPublish(publishProduct);
  16. LOGGER.info("定时投放, publishProductId:{},publishTitle:{}", publishProductId, publishTitle);
  17. }
  18. }

定时计费

JobConfiguration

  1. package com.greatonce.tuya.biz.impl.job;
  2. import com.greatonce.core.supports.quartz.QuartzTool;
  3. import com.greatonce.tuya.util.logging.TuyaLoggerFactory;
  4. import javax.annotation.PostConstruct;
  5. import org.quartz.CronTrigger;
  6. import org.quartz.JobDetail;
  7. import org.quartz.JobKey;
  8. import org.quartz.Scheduler;
  9. import org.quartz.SchedulerException;
  10. import org.slf4j.Logger;
  11. import org.springframework.beans.factory.annotation.Autowired;
  12. import org.springframework.beans.factory.annotation.Value;
  13. import org.springframework.context.annotation.Configuration;
  14. /**
  15. * @author Shenzhen Greatonce Co Ltd
  16. * @author xinzhang
  17. * @version 2019/7/15
  18. */
  19. @Configuration
  20. public class JobConfiguration {
  21. private static final Logger LOGGER = TuyaLoggerFactory.getLogger(JobConfiguration.class);
  22. private static final String PRODUCT_IMAGE_DAILY_ACCOUNT = "PRODUCT_IMAGE_DAILY_ACCOUNT";
  23. @Autowired
  24. private Scheduler globalScheduler;
  25. @Value("${tuya.quarz.productImageDailyAccountJobCorn:0 30 23 * * ?}")
  26. private String productImageDailyAccountJobCorn;
  27. @PostConstruct
  28. public void init() {
  29. executeProductImageDailyAccountJob();
  30. }
  31. private void executeProductImageDailyAccountJob() {
  32. try {
  33. JobKey jobKey = JobKey.jobKey(PRODUCT_IMAGE_DAILY_ACCOUNT);
  34. if (globalScheduler.checkExists(jobKey)) {
  35. globalScheduler.deleteJob(jobKey);
  36. }
  37. JobDetail jobDetail = QuartzTool
  38. .buildJobDetail(PRODUCT_IMAGE_DAILY_ACCOUNT, ProductImageDailyAccountJob.class);
  39. CronTrigger trigger = QuartzTool
  40. .buildCronTrigger(PRODUCT_IMAGE_DAILY_ACCOUNT, productImageDailyAccountJobCorn);
  41. globalScheduler.scheduleJob(jobDetail, trigger);
  42. } catch (SchedulerException e) {
  43. LOGGER.error("定时计费异常, 异常信息: {}", e.getMessage());
  44. }
  45. }
  46. }

ProductImageDailyAccountJob

  1. package com.greatonce.tuya.biz.impl.job;
  2. import com.greatonce.core.util.Assert;
  3. import com.greatonce.core.util.JsonUtil;
  4. import com.greatonce.core.util.StringUtil;
  5. import com.greatonce.tuya.biz.ProductBalanceService;
  6. import com.greatonce.tuya.biz.ProductBillService;
  7. import com.greatonce.tuya.biz.StoreService;
  8. import com.greatonce.tuya.biz.TenantService;
  9. import com.greatonce.tuya.biz.TenantSettingService;
  10. import com.greatonce.tuya.schema.domain.ProductBalance;
  11. import com.greatonce.tuya.schema.domain.ProductBill;
  12. import com.greatonce.tuya.schema.domain.Tenant;
  13. import com.greatonce.tuya.schema.domain.TenantSetting;
  14. import com.greatonce.tuya.schema.enums.ProductType;
  15. import com.greatonce.tuya.schema.enums.TenantSettingType;
  16. import com.greatonce.tuya.schema.query.ProductBalanceQuery;
  17. import com.greatonce.tuya.schema.query.TenantQuery;
  18. import com.greatonce.tuya.util.BizContext;
  19. import com.greatonce.tuya.util.logging.TuyaLoggerFactory;
  20. import com.greatonce.tuya.util.velocity.VelocityUtil;
  21. import java.time.LocalDate;
  22. import java.util.HashMap;
  23. import java.util.List;
  24. import java.util.Map;
  25. import java.util.Set;
  26. import java.util.stream.Collectors;
  27. import javax.mail.MessagingException;
  28. import javax.mail.internet.MimeMessage;
  29. import org.quartz.Job;
  30. import org.quartz.JobExecutionContext;
  31. import org.quartz.JobExecutionException;
  32. import org.slf4j.Logger;
  33. import org.springframework.beans.factory.annotation.Autowired;
  34. import org.springframework.beans.factory.annotation.Value;
  35. import org.springframework.data.redis.core.RedisTemplate;
  36. import org.springframework.mail.javamail.JavaMailSender;
  37. import org.springframework.mail.javamail.MimeMessageHelper;
  38. /**
  39. * 商品主图日结任务
  40. *
  41. * @author Shenzhen Greatonce Co Ltd
  42. * @author xinzhang
  43. * @version 2019/7/15
  44. */
  45. public class ProductImageDailyAccountJob implements Job {
  46. private static final Logger LOGGER = TuyaLoggerFactory.getLogger(ProductImageDailyAccountJob.class);
  47. private static final String GENERATE_SUCCESS_TIMES = "GENERATE_SUCCESS_TIMES";
  48. private static final String SYSTEM = "SYSTEM";
  49. private static final String PRODUCT_BALANCE_NOTIFY_MAIL_TEMPLATE = "productBalanceNotifyMail.vm";
  50. @Autowired
  51. private RedisTemplate<String, Object> redisTemplate;
  52. @Autowired
  53. private TenantService tenantService;
  54. @Autowired
  55. private TenantSettingService tenantSettingService;
  56. @Autowired
  57. private StoreService storeService;
  58. @Autowired
  59. private ProductBillService productBillService;
  60. @Autowired
  61. private ProductBalanceService productBalanceService;
  62. @Autowired
  63. private JavaMailSender javaMailSender;
  64. @Value("${spring.mail.username}")
  65. private String sender;
  66. @Override
  67. public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
  68. List<Tenant> tenantList = tenantService.list(new TenantQuery());
  69. for (Tenant tenant : tenantList) {
  70. BizContext.setTenantId(tenant.getTenantId());
  71. int totalTimes = 0;
  72. Map<Long, Integer> entries = redisTemplate.<Long, Integer>opsForHash()
  73. .entries(tenant.getTenantId() + ":" + ProductType.PRODUCT_IMAGE + ":" + GENERATE_SUCCESS_TIMES);
  74. if (!Assert.isEmpty(entries)) {
  75. for (Long storeId : entries.keySet()) {
  76. String storeName = storeService.getByKey(storeId).getStoreName();
  77. ProductBill productBill = new ProductBill();
  78. productBill.setTenantId(tenant.getTenantId());
  79. productBill.setCreator(SYSTEM);
  80. productBill.setConsumer(storeName);
  81. productBill.setTimes(entries.get(storeId));
  82. totalTimes += entries.get(storeId);
  83. productBill.setProductType(ProductType.PRODUCT_IMAGE);
  84. productBill.setAccountPeriod(LocalDate.now());
  85. productBillService.create(productBill);
  86. redisTemplate.<Long, Integer>opsForHash()
  87. .delete(tenant.getTenantId() + ":" + ProductType.PRODUCT_IMAGE + ":" + GENERATE_SUCCESS_TIMES, storeId);
  88. }
  89. productBalanceService
  90. .reduceProductBalanceRemainTimesAndFreezeTimes(tenant.getTenantId(), ProductType.PRODUCT_IMAGE, totalTimes);
  91. }
  92. }
  93. List<ProductBalance> productBalanceList = productBalanceService.list(new ProductBalanceQuery());
  94. Set<Long> tenantIds = productBalanceList.stream()
  95. .filter(productBalance -> {
  96. TenantSetting eg = new TenantSetting();
  97. eg.setTenantId(productBalance.getTenantId());
  98. eg.setTenantSettingType(TenantSettingType.APPLICATION);
  99. String tenantSettingContent = tenantSettingService.getByExample(eg).getTenantSettingContent();
  100. Integer warningValue = JsonUtil.toJSONObject(tenantSettingContent).getInteger("warningValue");
  101. return productBalance.getRemainTimes() <= warningValue;
  102. })
  103. .map(ProductBalance::getTenantId).collect(Collectors.toSet());
  104. sendRemainTimesNotifyEmail(tenantIds);
  105. }
  106. /**
  107. * 发送剩余次数提醒邮件
  108. *
  109. * @param tenantIds 租户id列表
  110. */
  111. private void sendRemainTimesNotifyEmail(Set<Long> tenantIds) {
  112. try {
  113. for (Long tenantId : tenantIds) {
  114. TenantSetting eg = new TenantSetting();
  115. eg.setTenantSettingType(TenantSettingType.APPLICATION);
  116. eg.setTenantId(tenantId);
  117. TenantSetting tenantSetting = tenantSettingService.getByExample(eg);
  118. String email = JsonUtil.toJSONObject(tenantSetting.getTenantSettingContent()).getString("email");
  119. List<String> receivers = StringUtil.words(email);
  120. String[] toAddress = receivers.toArray(new String[receivers.size()]);
  121. String subject = "图鸦提醒邮件,产品余额不足,请及时充值!";
  122. String tenantName = tenantService.getByKey(tenantId).getTenantName();
  123. HashMap<String, Object> params = new HashMap<>(2);
  124. params.put("tenantName", tenantName);
  125. ProductBalanceQuery query = new ProductBalanceQuery();
  126. query.setTenantId(tenantId);
  127. List<ProductBalance> productBalanceList = productBalanceService.list(query);
  128. params.put("productBalanceList", productBalanceList);
  129. String text = VelocityUtil.generateStringByTemplate(PRODUCT_BALANCE_NOTIFY_MAIL_TEMPLATE, params);
  130. MimeMessage message = javaMailSender.createMimeMessage();
  131. MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
  132. helper.setFrom(sender);
  133. helper.setTo(toAddress);
  134. helper.setSubject(subject);
  135. helper.setText(text, true);
  136. javaMailSender.send(message);
  137. }
  138. } catch (MessagingException e) {
  139. LOGGER.error("发送图片生成剩余次数提醒邮件失败, 堆栈信息: {}", e);
  140. }
  141. }
  142. }

QuarzTool

核心corn包的工具类参考
QuartzTool

  1. public abstract class QuartzTool {
  2. public QuartzTool() {
  3. }
  4. public static Scheduler scheduler(String name, JobFactory jobFactory) throws Exception {
  5. SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
  6. schedulerFactoryBean.setSchedulerName(name);
  7. schedulerFactoryBean.setJobFactory(jobFactory);
  8. schedulerFactoryBean.setAutoStartup(true);
  9. schedulerFactoryBean.afterPropertiesSet();
  10. schedulerFactoryBean.start();
  11. return schedulerFactoryBean.getObject();
  12. }
  13. public static JobDetail jobDetail(String name, Class<? extends Job> clazz) {
  14. JobDetailFactoryBean jobDetailFactoryBean = new JobDetailFactoryBean();
  15. jobDetailFactoryBean.setName(name);
  16. jobDetailFactoryBean.setJobClass(clazz);
  17. jobDetailFactoryBean.afterPropertiesSet();
  18. return jobDetailFactoryBean.getObject();
  19. }
  20. public static SimpleTrigger simpleTrigger(String name, int interval, JobDetail jobDetail) {
  21. SimpleTriggerFactoryBean simpleTriggerFactoryBean = new SimpleTriggerFactoryBean();
  22. simpleTriggerFactoryBean.setName(name);
  23. simpleTriggerFactoryBean.setJobDetail(jobDetail);
  24. simpleTriggerFactoryBean.setRepeatInterval((long)(interval * 1000));
  25. simpleTriggerFactoryBean.afterPropertiesSet();
  26. return simpleTriggerFactoryBean.getObject();
  27. }
  28. public static CronTrigger cronTrigger(String name, String cron, JobDetail jobDetail) throws ParseException {
  29. CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean();
  30. cronTriggerFactoryBean.setName(name);
  31. cronTriggerFactoryBean.setJobDetail(jobDetail);
  32. cronTriggerFactoryBean.setCronExpression(cron);
  33. cronTriggerFactoryBean.afterPropertiesSet();
  34. return cronTriggerFactoryBean.getObject();
  35. }
  36. public static String buildCron(LocalDateTime time) {
  37. DateTimeFormatter formatter = DateTimeFormatter.ofPattern("ss mm HH dd MM ? yyyy");
  38. return time.format(formatter);
  39. }
  40. public static JobDetail buildJobDetail(String jobKey, Class<? extends Job> clazz) {
  41. return buildJobDetail(jobKey, clazz, (Map)null);
  42. }
  43. public static JobDetail buildJobDetail(String jobKey, Class<? extends Job> clazz, Map<String, Object> jobData) {
  44. JobBuilder jobBuilder = JobBuilder.newJob(clazz).withIdentity(jobKey);
  45. if (jobData != null) {
  46. jobBuilder.usingJobData(new JobDataMap(jobData));
  47. }
  48. return jobBuilder.build();
  49. }
  50. public static CronTrigger buildCronTrigger(String triggerKey, String cron) {
  51. return (CronTrigger)TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(CronScheduleBuilder.cronSchedule(cron)).startNow().build();
  52. }
  53. public static SimpleTrigger buildSimpleTrigger(String triggerKey, int seconds) {
  54. return (SimpleTrigger)TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(seconds).repeatForever()).startNow().build();
  55. }
  56. public static <T> T getJobData(JobExecutionContext jobExecutionContext, String key) {
  57. return jobExecutionContext.getJobDetail().getJobDataMap() != null && jobExecutionContext.getJobDetail().getJobDataMap().containsKey(key) ? jobExecutionContext.getJobDetail().getJobDataMap().get(key) : null;
  58. }
  59. }

AutowireBeanJobFactory

  1. public class AutowireBeanJobFactory extends SpringBeanJobFactory implements ApplicationContextAware {
  2. private transient AutowireCapableBeanFactory beanFactory;
  3. public AutowireBeanJobFactory() {
  4. }
  5. public void setApplicationContext(ApplicationContext context) {
  6. this.beanFactory = context.getAutowireCapableBeanFactory();
  7. }
  8. protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
  9. Object job = super.createJobInstance(bundle);
  10. this.beanFactory.autowireBean(job);
  11. return job;
  12. }
  13. }

工具类使用参考

  1. String name = giftStrategy.getGiftStrategyId() + EXECUTE + execute.getGiftStrategyExecuteId();
  2. JobDetail jobDetail = QuartzTool
  3. .jobDetail(name, StaticStrategyExecuteJob.class);
  4. jobDetail.getJobDataMap().put(GIFT_STRATEGY_ID, giftStrategy.getGiftStrategyId());
  5. jobDetail.getJobDataMap().put(GIFT_STRATEGY_EXECUTE_ID, execute.getGiftStrategyExecuteId());
  6. SimpleTrigger simpleTrigger = QuartzTool.simpleTrigger(name, 0, jobDetail);
  7. try {
  8. globalScheduler.scheduleJob(jobDetail, simpleTrigger);
  9. } catch (SchedulerException e) {
  10. throw new OmsException("静态策略执行定时任务添加失败, giftStrategyId: {0}, GiftStrategyExecuteId: {1}",
  11. giftStrategy.getGiftStrategyId(), execute.getGiftStrategyExecuteId());
  12. }

quarz错过触发时机的处理

参考: https://yq.aliyun.com/articles/114262

触发器超时的情况

1.系统重启错过定时任务
2.Trigger被暂停时, 有任务被错过
3.线程池中的所有线程被占用, 导致misfire
4.有状态任务在下次触发时间到达时, 上次执行还没结束

  1. #判定jobmisfire的阈值,这里设置为4S
  2. org.quartz.jobStore.misfireThreshold = 4000

调度器怎么处理超时

  1. 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 那么触发器就不会被执行。

定时任务调度

image.png

注意理解:
间隔秒数==>下多长时间
延迟分钟==>下什么时候的, 当前时间-延迟时间
任务间隔==>多久下一次

  1. package com.greatonce.oms.job.executor;
  2. import com.greatonce.oms.biz.base.StoreDownloadConfigService;
  3. import com.greatonce.oms.biz.base.StoreService;
  4. import com.greatonce.oms.domain.base.Store;
  5. import com.greatonce.oms.domain.base.StoreDownloadConfig;
  6. import com.greatonce.oms.job.executor.download.StoreDownloadJob;
  7. import java.time.LocalDateTime;
  8. import org.quartz.Job;
  9. import org.quartz.JobExecutionContext;
  10. import org.slf4j.Logger;
  11. import org.slf4j.LoggerFactory;
  12. import org.springframework.beans.factory.annotation.Autowired;
  13. /**
  14. * 下载抽象类.
  15. *
  16. * @author ginta
  17. * @author Shenzhen Greatonce Co Ltd
  18. * @version 2018-07-22
  19. */
  20. public abstract class AbstractDownloader implements Job {
  21. private final Logger LOGGER = LoggerFactory.getLogger(AbstractDownloader.class);
  22. @Autowired
  23. private StoreService storeService;
  24. @Autowired
  25. private StoreDownloadConfigService storeDownloadConfigService;
  26. /**
  27. * 日志
  28. */
  29. protected abstract Logger getLogger();
  30. /**
  31. * 执行下载
  32. *
  33. * 如果未到延迟时间不下载
  34. * 如果开始时间+间隔秒数>当前时间则结束时间为当前时间,否则为开始时间+间隔秒数
  35. *
  36. * @param config 下载配置
  37. * @param store 店铺
  38. * @param endTime 计算好的结束时间
  39. */
  40. protected abstract void doDownload(StoreDownloadConfig config, Store store, LocalDateTime endTime);
  41. public void download(StoreDownloadConfig config) {
  42. Store store = storeService.getByKey(config.getStoreId());
  43. if (store == null || !store.isEnable()) {
  44. getLogger().debug("店铺不存在或已禁用:{}", config.getStoreName());
  45. return;
  46. }
  47. LocalDateTime now = LocalDateTime.now();
  48. LocalDateTime endTime = config.getBeginTime().plusSeconds(config.getIntervalSeconds());
  49. endTime = endTime.isAfter(now) ? now : endTime;
  50. LocalDateTime deadline = now.minusMinutes(config.getDelayMinutes());
  51. if (endTime.isAfter(deadline)) {
  52. if (getLogger().isDebugEnabled()) {
  53. getLogger()
  54. .debug("未到延迟时间:{},{},{}", config.getStoreName(), config.getDownloadType().caption(),
  55. config.getBeginTime());
  56. }
  57. return;
  58. }
  59. doDownload(config, store, endTime);
  60. config.setBeginTime(endTime);
  61. storeDownloadConfigService.modify(config);
  62. }
  63. @Override
  64. public void execute(JobExecutionContext jobExecutionContext) {
  65. Long configId = jobExecutionContext.getJobDetail().getJobDataMap().getLong(StoreDownloadJob.DOWNLOAD_CONFIG_KEY);
  66. StoreDownloadConfig config = storeDownloadConfigService.getByKey(configId);
  67. try {
  68. download(config);
  69. } catch (Exception e) {
  70. LOGGER.info(config.getStoreName() + ":" + config.getDownloadType().caption() + "下载异常,堆栈信息:", e);
  71. }
  72. }
  73. }

举例:
问: 有品发票的下载配置初始化的时候给多少合适==>这种不需要太及时的就可以跑的频次低一点, 可以5分钟跑一次,拉10分钟的数据
答: 可以设置延迟分钟10, 间隔秒数1060和任务间隔560