SpringBoot官方支持任務調度框架,輕量級用起來也挺香!

以前咱們講過一個分佈式任務調度框架PowerJob,能夠經過可視化的方式來進行任務調度。可是有時候咱們只是須要一個輕量級的任務調度功能,而PowerJob須要搭建調度中心未免有些重,這時候SpringBoot官方支持的任務調度框架Quartz就派上用場了!本文主要介紹Quartz在SpringBoot中的使用,讓你在實現任務調度上有更多的選擇!

SpringBoot實戰電商項目mall(40k+star)地址:https://github.com/macrozheng/malljava

Quartz簡介

Quartz是一款功能強大的開源任務調度框架,幾乎能夠集成到任何Java應用程序中(小到單機應用,大到分佈式應用)。Quartz可用於建立簡單或複雜的任務調度,用以執行數以萬計的任務。任務被定義爲標準化的Java組件,Java編寫的任務均可以被執行。mysql

核心概念

Quartz中有一些比較核心的概念,理解它們對使用Quartz頗有幫助!
  • Scheduler(調度器):Quartz中的任務調度器,經過Trigger和JobDetail能夠用來調度、暫停和刪除任務。
  • Trigger(觸發器):Quartz中的觸發器,能夠經過CRON表達式來指定任務執行的時間,時間到了會自動觸發任務執行。
  • JobDetail(任務詳情):Quartz中須要執行的任務詳情,包括了任務的惟一標識和具體要執行的任務,能夠經過JobDataMap往任務中傳遞數據。
  • Job(任務):Quartz中具體的任務,包含了執行任務的具體方法。

CRON表達式

Cron表達式是一個字符串,包括6~7個時間元素,在Quartz中能夠用於指定任務的執行時間。

CRON的語法格式

Seconds Minutes Hours DayofMonth Month DayofWeek

CRON格式中每一個時間元素的說明

時間元素 可出現的字符 有效數值範圍
Seconds , - * / 0-59
Minutes , - * / 0-59
Hours , - * / 0-23
DayofMonth , - * / ? L W 0-31
Month , - * / 1-12
DayofWeek , - * / ? L # 1-7或SUN-SAT

CRON格式中特殊字符說明

字符 做用 舉例
, 列出枚舉值 在Minutes域使用5,10,表示在5分和10分各觸發一次
\- 表示觸發範圍 在Minutes域使用5-10,表示從5分到10分鐘每分鐘觸發一次
\* 匹配任意值 在Minutes域使用*, 表示每分鐘都會觸發一次
/ 起始時間開始觸發,每隔固定時間觸發一次 在Minutes域使用5/10,表示5分時觸發一次,每10分鐘再觸發一次
? 在DayofMonth和DayofWeek中,用於匹配任意值 在DayofMonth域使用?,表示天天都觸發一次
\# 在DayofMonth中,肯定第幾個星期幾 1#3表示第三個星期日
L 表示最後 在DayofWeek中使用5L,表示在最後一個星期四觸發
W 表示有效工做日(週一到週五) 在DayofMonth使用5W,若是5日是星期六,則將在最近的工做日4日觸發一次

在線CRON表達式生成器

其實CRON表達式無需多記,須要使用的時候直接使用在線生成器就能夠了,地址:https://cron.qqe2.com/git

整合SpringBoot使用

接下來咱們講下如何在SpringBoot中使用Quartz來實現任務調度,在電商系統中每每會有須要定時發送郵件或者站內信的需求,咱們以此爲場景來實現下!
  • Quartz存儲任務信息有兩種方式,使用內存或者使用數據庫來存儲,這裏咱們採用數據庫存儲的方式,首先須要新建Quartz的相關表,建表腳本在項目的resources目錄下,名稱爲tables_mysql.sql,建立成功後數據庫中多出11張表;

  • 接下來在pom.xml中添加Quartz的相關依賴便可,SpringBoot官方已經給咱們提供好了相關Starter;
<!--SpringBoot集成QuartZ-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
  • application.yml中添加Quartz相關配置,配置說明直接看註釋就行了,主要是對schedulerjobStorethreadPool進行配置;
spring:
  quartz:
    job-store-type: jdbc # quartz任務存儲類型:jdbc或memory
    wait-for-jobs-to-complete-on-shutdown: true # 關閉時等待任務完成
    overwrite-existing-jobs: true # 能夠覆蓋已有的任務
    properties: # quartz原生配置
      org:
        quartz:
          scheduler:
            instanceName: scheduler # 調度器實例名稱
            instanceId: AUTO # 調度器實例ID自動生成
          jobStore:
            class: org.quartz.impl.jdbcjobstore.JobStoreTX # 調度信息存儲處理類
            driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate # 使用徹底兼容JDBC的驅動
            tablePrefix: QRTZ_ # quartz相關表前綴
            useProperties: false # 是否將JobDataMap中的屬性轉爲字符串存儲
          threadPool:
            class: org.quartz.simpl.SimpleThreadPool # 指定線程池實現類,對調度器提供固定大小的線程池
            threadCount: 10 # 設置併發線程數量
            threadPriority: 5 # 指定線程優先級
  • 建立任務調度業務接口,定義好三個方法,分別爲經過CRON表達式調度任務、調度指定時間的任務和取消定時任務;
/**
 * Quartz定時任務操做類
 * Created by macro on 2020/9/27.
 */
public interface ScheduleService {
    /**
     * 經過CRON表達式調度任務
     */
    String scheduleJob(Class<? extends Job> jobBeanClass, String cron, String data);

    /**
     * 調度指定時間的任務
     */
    String scheduleFixTimeJob(Class<? extends Job> jobBeanClass, Date startTime, String data);

    /**
     * 取消定時任務
     */
    Boolean cancelScheduleJob(String jobName);
}
  • 建立任務調度業務實現類,經過SchedulerCronTriggerJobDetail的API實現相關方法;
/**
 * Quartz定時任務操做實現類
 * Created by macro on 2020/9/27.
 */
@Slf4j
@Service
public class ScheduleServiceImpl implements ScheduleService {
    @Autowired
    private Scheduler scheduler;
    private String defaultGroup = "default_group";

    @Override
    public String scheduleJob(Class<? extends Job> jobBeanClass, String cron, String data) {
        // 建立須要執行的任務
        String jobName = UUID.fastUUID().toString();
        JobDetail jobDetail = JobBuilder.newJob(jobBeanClass)
                .withIdentity(jobName, defaultGroup)
                .usingJobData("data", data)
                .build();
        //建立觸發器,指定任務執行時間
        CronTrigger cronTrigger = TriggerBuilder.newTrigger()
                .withIdentity(jobName, defaultGroup)
                .withSchedule(CronScheduleBuilder.cronSchedule(cron))
                .build();
        //使用調度器進行任務調度
        try {
            scheduler.scheduleJob(jobDetail, cronTrigger);
        } catch (SchedulerException e) {
            e.printStackTrace();
            log.info("建立定時任務失敗!");
        }
        return jobName;
    }

    @Override
    public String scheduleFixTimeJob(Class<? extends Job> jobBeanClass, Date startTime, String data) {
        //日期轉CRON表達式
        String startCron = String.format("%d %d %d %d %d ? %d",
                DateUtil.second(startTime),
                DateUtil.minute(startTime),
                DateUtil.hour(startTime, true),
                DateUtil.dayOfMonth(startTime),
                DateUtil.month(startTime) + 1,
                DateUtil.year(startTime));
        return scheduleJob(jobBeanClass, startCron, data);
    }

    @Override
    public Boolean cancelScheduleJob(String jobName) {
        boolean success = false;
        try {
            // 暫停觸發器
            scheduler.pauseTrigger(new TriggerKey(jobName, defaultGroup));
            // 移除觸發器中的任務
            scheduler.unscheduleJob(new TriggerKey(jobName, defaultGroup));
            // 刪除任務
            scheduler.deleteJob(new JobKey(jobName, defaultGroup));
            success = true;
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
        return success;
    }
}
  • 定義好須要執行的任務,繼承QuartzJobBean類,實現executeInternal方法便可,這裏定義了三個任務,定時發送郵件、定時發送站內信和執行CRON表達式任務;
/**
 * 發送郵件定時任務執行器
 * Created by macro on 2020/9/27.
 */
@Slf4j
@Component
public class SendEmailJob extends QuartzJobBean {
    @Autowired
    private ScheduleService scheduleService;

    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        Trigger trigger = jobExecutionContext.getTrigger();
        JobDetail jobDetail = jobExecutionContext.getJobDetail();
        JobDataMap jobDataMap = jobDetail.getJobDataMap();
        String data = jobDataMap.getString("data");
        log.info("定時發送郵件操做:{}",data);
        //完成後刪除觸發器和任務
        scheduleService.cancelScheduleJob(trigger.getKey().getName());
    }
}
/**
 * 發送站內信定時任務執行器
 * Created by macro on 2020/9/27.
 */
@Slf4j
@Component
public class SendMessageJob extends QuartzJobBean {
    @Autowired
    private ScheduleService scheduleService;

    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        Trigger trigger = jobExecutionContext.getTrigger();
        JobDetail jobDetail = jobExecutionContext.getJobDetail();
        JobDataMap jobDataMap = jobDetail.getJobDataMap();
        String data = jobDataMap.getString("data");
        log.info("定時發送站內信操做:{}",data);
        //完成後刪除觸發器和任務
        scheduleService.cancelScheduleJob(trigger.getKey().getName());
    }
}
/**
 * 使用CRON表達式的任務執行器
 * Created by macro on 2020/9/29.
 */
@Slf4j
@Component
public class CronProcessJob extends QuartzJobBean {
    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        JobDetail jobDetail = jobExecutionContext.getJobDetail();
        JobDataMap jobDataMap = jobDetail.getJobDataMap();
        String data = jobDataMap.getString("data");
        log.info("CRON表達式任務執行:{}",data);
    }
}
  • 最後建立好任務調度相關接口,調用任務調度業務類便可。
/**
 * 定時任務調度相關接口
 * Created by macro on 2020/9/29.
 */
@Api(tags = "ScheduleController", description = "定時任務調度相關接口")
@RestController
@RequestMapping("/schedule")
public class ScheduleController {
    @Autowired
    private ScheduleService scheduleService;

    @ApiOperation("定時發送郵件")
    @PostMapping("/sendEmail")
    public CommonResult sendEmail(@RequestParam String startTime,@RequestParam String data) {
        Date date = DateUtil.parse(startTime, DatePattern.NORM_DATETIME_FORMAT);
        String jobName = scheduleService.scheduleFixTimeJob(SendEmailJob.class, date, data);
        return CommonResult.success(jobName);
    }

    @ApiOperation("定時發送站內信")
    @PostMapping("/sendMessage")
    public CommonResult sendMessage(@RequestParam String startTime,@RequestParam String data) {
        Date date = DateUtil.parse(startTime, DatePattern.NORM_DATETIME_FORMAT);
        String jobName = scheduleService.scheduleFixTimeJob(SendMessageJob.class, date, data);
        return CommonResult.success(jobName);
    }

    @ApiOperation("經過CRON表達式調度任務")
    @PostMapping("/scheduleJob")
    public CommonResult scheduleJob(@RequestParam String cron, @RequestParam String data) {
        String jobName = scheduleService.scheduleJob(CronProcessJob.class, cron, data);
        return CommonResult.success(jobName);
    }

    @ApiOperation("取消定時任務")
    @PostMapping("/cancelScheduleJob")
    public CommonResult cancelScheduleJob(@RequestParam String jobName) {
        Boolean success = scheduleService.cancelScheduleJob(jobName);
        return CommonResult.success(success);
    }
}

運行測試

  • 調用定時發送郵件接口測試;

  • 到點以後發現控制檯已經打印任務執行信息;
2020-09-30 11:23:00.035  INFO 10160 --- [eduler_Worker-1] com.macro.mall.tiny.job.SendEmailJob     : 定時發送郵件操做:發送郵件內容
  • 使用CRON表達式來啓動一個定時任務,從0s開始,每隔10s執行一次;

  • 控制檯每隔10s打印任務執行信息;
2020-09-30 11:26:30.024  INFO 10160 --- [eduler_Worker-2] com.macro.mall.tiny.job.CronProcessJob   : CRON表達式任務執行:CRON消息內容
2020-09-30 11:26:40.025  INFO 10160 --- [eduler_Worker-3] com.macro.mall.tiny.job.CronProcessJob   : CRON表達式任務執行:CRON消息內容
2020-09-30 11:26:50.017  INFO 10160 --- [eduler_Worker-4] com.macro.mall.tiny.job.CronProcessJob   : CRON表達式任務執行:CRON消息內容
2020-09-30 11:27:00.023  INFO 10160 --- [eduler_Worker-5] com.macro.mall.tiny.job.CronProcessJob   : CRON表達式任務執行:CRON消息內容
2020-09-30 11:27:10.019  INFO 10160 --- [eduler_Worker-6] com.macro.mall.tiny.job.CronProcessJob   : CRON表達式任務執行:CRON消息內容
  • 咱們能夠經過啓動任務返回的jobName,調用取消定時任務的接口來取消任務,調用成功後定時任務不在執行。

參考資料

官方文檔:http://www.quartz-scheduler.o...github

項目源碼地址

https://github.com/macrozheng...spring

本文 GitHub https://github.com/macrozheng/mall-learning 已經收錄,歡迎你們Star!
相關文章
相關標籤/搜索